2015-07-26 14:26:13 +00:00
|
|
|
/********************************************************************************
|
|
|
|
|
* *
|
|
|
|
|
* 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 <http://www.gnu.org/licenses/>. *
|
|
|
|
|
* *
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
|
2023-03-22 11:17:14 +01:00
|
|
|
#ifdef IFOPSH_WITH_OPENCASCADE
|
|
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
#include "../ifcgeom/abstract_mapping.h"
|
2021-10-08 15:38:46 +02:00
|
|
|
|
2015-07-26 14:26:13 +00:00
|
|
|
#include <string>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <limits>
|
|
|
|
|
#include <algorithm>
|
2020-12-31 11:31:05 +01:00
|
|
|
#include <numeric>
|
2015-07-26 14:26:13 +00:00
|
|
|
|
|
|
|
|
#include <gp_Pln.hxx>
|
2017-10-23 14:17:54 +02:00
|
|
|
#include <gp_Trsf.hxx>
|
2017-11-23 11:10:41 +01:00
|
|
|
#include <gp_Circ.hxx>
|
|
|
|
|
#include <gp_Elips.hxx>
|
2015-07-26 14:26:13 +00:00
|
|
|
#include <TopoDS.hxx>
|
|
|
|
|
#include <TopoDS_Edge.hxx>
|
|
|
|
|
#include <TopExp_Explorer.hxx>
|
|
|
|
|
#include <BRep_Tool.hxx>
|
|
|
|
|
#include <BRepTools.hxx>
|
|
|
|
|
#include <BRepAlgoAPI_Section.hxx>
|
|
|
|
|
#include <ShapeAnalysis_FreeBounds.hxx>
|
|
|
|
|
#include <TopTools_HSequenceOfShape.hxx>
|
2020-08-16 11:41:40 +02:00
|
|
|
#include <TopExp.hxx>
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2017-11-25 13:08:34 +01:00
|
|
|
#include <BRepAdaptor_Curve.hxx>
|
|
|
|
|
#include <GCPnts_QuasiUniformDeflection.hxx>
|
|
|
|
|
|
2015-07-26 14:26:13 +00:00
|
|
|
#include <Geom_Curve.hxx>
|
|
|
|
|
#include <Geom_Line.hxx>
|
2017-12-11 11:18:37 +01:00
|
|
|
#include <Geom_Plane.hxx>
|
2015-07-26 14:26:13 +00:00
|
|
|
#include <Geom_Circle.hxx>
|
|
|
|
|
#include <Geom_Ellipse.hxx>
|
|
|
|
|
#include <gp_Ax22d.hxx>
|
2015-10-28 21:25:23 +02:00
|
|
|
#include <Standard_Version.hxx>
|
2017-12-11 11:18:37 +01:00
|
|
|
#include <GeomAPI.hxx>
|
2017-12-20 12:40:15 +01:00
|
|
|
#include <TopoDS_Wire.hxx>
|
2017-12-11 11:18:37 +01:00
|
|
|
|
2020-03-11 14:45:58 +01:00
|
|
|
#include <BRepBuilderAPI_Transform.hxx>
|
2021-01-29 13:03:44 +01:00
|
|
|
#include <BRepBuilderAPI_MakeEdge.hxx>
|
2020-04-01 12:19:56 +02:00
|
|
|
#include <BRepBuilderAPI_MakeFace.hxx>
|
2021-01-29 13:03:44 +01:00
|
|
|
#include <BRepBuilderAPI_MakePolygon.hxx>
|
|
|
|
|
|
|
|
|
|
#include <BRepAlgoAPI_Cut.hxx>
|
|
|
|
|
#include <BRepPrimAPI_MakeHalfSpace.hxx>
|
|
|
|
|
|
2020-04-01 12:19:56 +02:00
|
|
|
#include <GProp_GProps.hxx>
|
|
|
|
|
#include <BRepGProp.hxx>
|
|
|
|
|
#include <BRepTopAdaptor_FClass2d.hxx>
|
|
|
|
|
|
2020-07-11 23:26:21 +02:00
|
|
|
#include <Bnd_Box.hxx>
|
|
|
|
|
#include <BRep_Builder.hxx>
|
|
|
|
|
#include <BRepBndLib.hxx>
|
|
|
|
|
|
2020-09-20 16:39:01 +02:00
|
|
|
#include <ShapeFix_Edge.hxx>
|
|
|
|
|
|
2021-01-02 12:34:10 +01:00
|
|
|
#include <HLRBRep_PolyHLRToShape.hxx>
|
|
|
|
|
|
2021-01-30 16:15:26 +01:00
|
|
|
#include <Extrema_ExtPElS.hxx>
|
|
|
|
|
|
2015-07-26 14:26:13 +00:00
|
|
|
#include "../ifcparse/IfcGlobalId.h"
|
2023-03-21 20:15:01 +01:00
|
|
|
#include "../ifcgeom/kernels/opencascade/base_utils.h"
|
2023-07-29 10:07:33 +08:00
|
|
|
#include "../ifcgeom/kernels/opencascade/boolean_utils.h"
|
2024-02-26 13:27:36 +01:00
|
|
|
#include "../ifcgeom/kernels/opencascade/wire_utils.h"
|
|
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
#include "../ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h"
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2021-03-05 21:50:32 +01:00
|
|
|
#include <boost/format.hpp>
|
2021-03-28 12:48:20 +02:00
|
|
|
#include <boost/tokenizer.hpp>
|
2021-03-05 21:50:32 +01:00
|
|
|
|
2015-07-26 14:26:13 +00:00
|
|
|
#include "SvgSerializer.h"
|
|
|
|
|
|
|
|
|
|
const double PI2 = M_PI * 2.;
|
|
|
|
|
|
|
|
|
|
bool SvgSerializer::ready() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
void SvgSerializer::write(path_object& p, const TopoDS_Shape& comp_or_wire, boost::optional<std::vector<double>> dash_array) {
|
2015-07-26 14:26:13 +00:00
|
|
|
/* 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(); */
|
|
|
|
|
|
|
|
|
|
util::string_buffer path;
|
2017-12-11 11:18:37 +01:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
std::list<TopoDS_Shape> wires;
|
|
|
|
|
if (comp_or_wire.ShapeType() == TopAbs_WIRE) {
|
|
|
|
|
wires.push_back(comp_or_wire);
|
|
|
|
|
} else if (comp_or_wire.ShapeType() == TopAbs_COMPOUND) {
|
|
|
|
|
TopoDS_Iterator it(comp_or_wire);
|
|
|
|
|
for (; it.More(); it.Next()) {
|
|
|
|
|
wires.push_back(it.Value());
|
2017-12-11 11:18:37 +01:00
|
|
|
}
|
2021-11-05 11:21:19 +01:00
|
|
|
}
|
2017-12-11 11:18:37 +01:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
bool first_wire = true;
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
for (auto& wire : wires) {
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
bool first = true;
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// Write the element as a svg circle or ellipse. This isn't possible
|
|
|
|
|
// when forced writing of polygonal output or when there are multiple
|
|
|
|
|
// wires to be written.
|
|
|
|
|
if (!polygonal_ && (conical && closed) && wires.size() == 1) {
|
|
|
|
|
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(" <circle r=\"");
|
|
|
|
|
radii.push_back(path.add(r));
|
|
|
|
|
path.add("\" cx=\"");
|
|
|
|
|
xcoords.push_back(path.add(center.X()));
|
|
|
|
|
path.add("\" cy=\"");
|
|
|
|
|
ycoords.push_back(path.add(center.Y()));
|
|
|
|
|
|
|
|
|
|
growBoundingBox(center.X() - r, center.Y() - r);
|
|
|
|
|
growBoundingBox(center.X() + r, center.Y() + r);
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
first = false;
|
|
|
|
|
continue;
|
|
|
|
|
} else if (ty == STANDARD_TYPE(Geom_Ellipse)) {
|
|
|
|
|
Handle(Geom_Ellipse) ellipse = Handle(Geom_Ellipse)::DownCast(curve);
|
|
|
|
|
gp_Elips e = ellipse->Elips();
|
|
|
|
|
gp_Pnt center = e.Location();
|
|
|
|
|
|
|
|
|
|
// Write the ellipse with major radius along X axis:
|
|
|
|
|
path.add(" <ellipse rx=\"");
|
|
|
|
|
radii.push_back(path.add(e.MajorRadius()));
|
|
|
|
|
path.add("\" ry=\"");
|
|
|
|
|
radii.push_back(path.add(e.MinorRadius()));
|
|
|
|
|
path.add("\" cx=\"");
|
|
|
|
|
xcoords.push_back(path.add(center.X()));
|
|
|
|
|
path.add("\" cy=\"");
|
|
|
|
|
ycoords.push_back(path.add(center.Y()));
|
|
|
|
|
path.add("\"");
|
|
|
|
|
|
|
|
|
|
// Rotate it with "transform":
|
|
|
|
|
gp_Ax1 major_axis = e.XAxis();
|
|
|
|
|
double z_rotation = major_axis.Direction().AngleWithRef(gp_Dir(1., 0., 0.), gp_Dir(0., 0., 1.));
|
|
|
|
|
path.add(" transform=\"rotate(");
|
|
|
|
|
path.add(z_rotation);
|
|
|
|
|
path.add(" ");
|
|
|
|
|
path.add(center.X());
|
|
|
|
|
path.add(" ");
|
|
|
|
|
path.add(center.Y());
|
|
|
|
|
// @todo isn't there a ")" missing here?
|
|
|
|
|
// @todo also X, Y are not added to {x,y}coords vector
|
|
|
|
|
// @todo also z_rotation is in radians, should be in degrees
|
|
|
|
|
|
|
|
|
|
// Bounding box:
|
|
|
|
|
// More important to have all geometry in bounding box than to be minimal
|
|
|
|
|
growBoundingBox(center.X() - e.MajorRadius(), center.Y() - e.MajorRadius());
|
|
|
|
|
growBoundingBox(center.X() + e.MajorRadius(), center.Y() + e.MajorRadius());
|
|
|
|
|
|
|
|
|
|
first = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
ss << "Skipping full circle/ellipse inside aggregated <path> (id "
|
|
|
|
|
<< p.first << ")";
|
|
|
|
|
Logger::Warning(ss.str());
|
|
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
2017-11-23 11:50:55 +01:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
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);
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
2017-11-23 11:50:55 +01:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if (first) {
|
|
|
|
|
if (first_wire) {
|
|
|
|
|
path.add(" <path d=\"");
|
|
|
|
|
} else {
|
|
|
|
|
path.add(" ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
path.add("M");
|
|
|
|
|
addXCoordinate(path.add(p1.X()));
|
2017-11-25 13:08:34 +01:00
|
|
|
path.add(",");
|
2021-11-05 11:21:19 +01:00
|
|
|
addYCoordinate(path.add(p1.Y()));
|
2017-11-25 13:08:34 +01:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
growBoundingBox(p1.X(), p1.Y());
|
2017-11-25 13:08:34 +01:00
|
|
|
}
|
2021-11-05 11:21:19 +01:00
|
|
|
|
|
|
|
|
growBoundingBox(p2.X(), p2.Y());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!polygonal_ && (ty == STANDARD_TYPE(Geom_Circle) || ty == STANDARD_TYPE(Geom_Ellipse))) {
|
|
|
|
|
Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(curve);
|
|
|
|
|
const bool mirrored = conic->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 = closed ? M_PI * 2. : 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<int>(larger_arc_segment)));
|
|
|
|
|
path.add(",");
|
|
|
|
|
path.add(std::string(1, '0' + static_cast<int>(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);
|
2023-11-15 10:32:20 +01:00
|
|
|
GCPnts_QuasiUniformDeflection tessellater(crv, geometry_settings().get<ifcopenshell::geometry::settings::MesherLinearDeflection>().get());
|
2021-11-05 11:21:19 +01:00
|
|
|
// 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;
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
first_wire = false;
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-06 09:14:40 +01:00
|
|
|
if (!path.empty()) {
|
|
|
|
|
path.add("\"");
|
2021-03-28 16:55:52 +02:00
|
|
|
|
2023-03-06 09:14:40 +01:00
|
|
|
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));
|
2021-03-28 16:55:52 +02:00
|
|
|
}
|
2023-03-06 09:14:40 +01:00
|
|
|
path.add("\"");
|
2021-03-28 16:55:52 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-06 09:14:40 +01:00
|
|
|
path.add("/>\n");
|
|
|
|
|
p.second.push_back(path);
|
|
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
SvgSerializer::path_object& SvgSerializer::start_path(const gp_Pln& pln, const IfcUtil::IfcBaseEntity* storey, const std::string& id) {
|
2020-09-20 15:08:52 +02:00
|
|
|
auto key = std::make_pair(std::make_pair(storey, ""), path_object());
|
|
|
|
|
SvgSerializer::path_object& p = paths.insert(key)->second;
|
2020-12-31 11:31:05 +01:00
|
|
|
drawing_metadata[key.first].pln_3d = pln;
|
2015-07-26 14:26:13 +00:00
|
|
|
p.first = id;
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-31 11:31:05 +01:00
|
|
|
SvgSerializer::path_object& SvgSerializer::start_path(const gp_Pln& pln, const std::string& drawing_name, const std::string& id) {
|
2020-09-20 15:08:52 +02:00
|
|
|
auto key = std::make_pair(std::make_pair(nullptr, drawing_name), path_object());
|
|
|
|
|
SvgSerializer::path_object& p = paths.insert(key)->second;
|
2020-12-31 11:31:05 +01:00
|
|
|
drawing_metadata[key.first].pln_3d = pln;
|
2020-09-20 15:08:52 +02:00
|
|
|
p.first = id;
|
|
|
|
|
return p;
|
|
|
|
|
}
|
2020-07-11 13:39:09 +02:00
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
namespace {
|
2023-03-21 20:15:01 +01:00
|
|
|
boost::optional<std::pair<const IfcUtil::IfcBaseEntity*, double>> storey_elevation_from_element(const IfcGeom::BRepElement* o) {
|
2020-07-11 13:39:09 +02:00
|
|
|
for (const auto& p : o->parents()) {
|
|
|
|
|
if (p->type() == "IfcBuildingStorey") {
|
|
|
|
|
try {
|
2024-08-23 20:29:07 +02:00
|
|
|
double e = p->product()->get("Elevation");
|
2023-11-15 10:32:20 +01:00
|
|
|
double storey_elevation = e * o->geometry().settings().get<ifcopenshell::geometry::settings::LengthUnit>().get();
|
2020-09-20 15:08:52 +02:00
|
|
|
return std::make_pair(p->product(), storey_elevation);
|
2020-07-11 13:39:09 +02:00
|
|
|
} catch (...) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2017-11-08 16:46:53 +01:00
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
return boost::none;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-04 14:39:32 +01:00
|
|
|
typedef std::pair<std::array<double, 3>, std::array<double, 3>> box_t;
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
boost::optional<TopoDS_Edge> 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;
|
|
|
|
|
}
|
2021-03-04 14:39:32 +01:00
|
|
|
|
|
|
|
|
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_t> box_from_compound(TopoDS_Shape& compound) {
|
2024-07-03 20:03:06 +02:00
|
|
|
/*
|
|
|
|
|
// in v0.8 apparently we don't get a solid/shell anymore because
|
|
|
|
|
// we no longer use PrimAPI, but rather resolve the box to an
|
|
|
|
|
// explicit shell with 6 faces in the mapping, which - depending
|
|
|
|
|
// on settings - may remain solely a compound of 6.
|
|
|
|
|
|
2021-03-04 14:39:32 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2024-07-03 20:03:06 +02:00
|
|
|
*/
|
|
|
|
|
auto& shell = compound;
|
2021-03-04 14:39:32 +01:00
|
|
|
|
2022-11-13 11:04:16 +01:00
|
|
|
if (IfcGeom::util::count(shell, TopAbs_FACE) != 6) {
|
2021-03-04 14:39:32 +01:00
|
|
|
return boost::none;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-03 20:03:06 +02:00
|
|
|
TopExp_Explorer it(shell, TopAbs_FACE);
|
2021-03-04 14:39:32 +01:00
|
|
|
for (; it.More(); it.Next()) {
|
2024-08-02 04:50:06 +02:00
|
|
|
const auto& face = TopoDS::Face(it.Current());
|
2021-03-04 14:39:32 +01:00
|
|
|
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}} };
|
|
|
|
|
}
|
2021-03-28 12:48:20 +02:00
|
|
|
|
|
|
|
|
struct string_property {
|
|
|
|
|
std::string pset_name, prop_name, value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <typename It>
|
2023-03-21 20:15:01 +01:00
|
|
|
void enumerate_string_properties(const IfcUtil::IfcBaseEntity* product, It output_it) {
|
2021-03-28 12:48:20 +02:00
|
|
|
auto rels = product->get_inverse("IsDefinedBy");
|
|
|
|
|
for (auto& rel : *rels) {
|
|
|
|
|
if (rel->declaration().is("IfcRelDefinesByProperties")) {
|
2024-08-23 20:29:07 +02:00
|
|
|
auto pset = ((IfcUtil::IfcBaseClass*) ((IfcUtil::IfcBaseEntity*) rel)->get("RelatingPropertyDefinition"))->as<IfcUtil::IfcBaseEntity>();
|
2025-10-31 09:56:54 +01:00
|
|
|
if (!pset->declaration().is("IfcPropertySet")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-03-28 12:48:20 +02:00
|
|
|
std::string pset_name;
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!pset->get("Name").isNull()) {
|
|
|
|
|
pset_name = (std::string) pset->get("Name");
|
2021-03-28 12:48:20 +02:00
|
|
|
}
|
2024-08-23 20:29:07 +02:00
|
|
|
aggregate_of_instance::ptr props = pset->get("HasProperties");
|
2021-03-28 12:48:20 +02:00
|
|
|
for (auto& prop : *props) {
|
|
|
|
|
if (prop->declaration().is("IfcPropertySingleValue")) {
|
2024-08-23 20:29:07 +02:00
|
|
|
std::string name = ((IfcUtil::IfcBaseEntity*) prop)->get("Name");
|
|
|
|
|
if (((IfcUtil::IfcBaseEntity*) prop)->get("NominalValue").isNull()) {
|
2024-03-04 17:53:36 +11:00
|
|
|
continue;
|
|
|
|
|
}
|
2024-08-23 20:29:07 +02:00
|
|
|
IfcUtil::IfcBaseClass* v = ((IfcUtil::IfcBaseEntity*) prop)->get("NominalValue");
|
2025-02-27 22:07:31 +01:00
|
|
|
auto value = v->get_attribute_value(0);
|
2024-08-23 20:29:07 +02:00
|
|
|
if (value.type() == IfcUtil::Argument_STRING) {
|
|
|
|
|
std::string v_str = value;
|
2021-03-28 12:48:20 +02:00
|
|
|
*output_it++ = string_property{ pset_name, name, v_str };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-28 16:55:52 +02:00
|
|
|
namespace {
|
|
|
|
|
boost::optional<std::string> get_curve_style_name(IfcUtil::IfcBaseEntity* item) {
|
|
|
|
|
auto refs = item->get_inverse("StyledByItem");
|
|
|
|
|
for (auto& ref : *refs) {
|
|
|
|
|
if (ref->declaration().is("IfcStyledItem")) {
|
2024-08-23 20:29:07 +02:00
|
|
|
aggregate_of_instance::ptr styles = ((IfcUtil::IfcBaseEntity*)ref)->get("Styles");
|
2021-03-28 16:55:52 +02:00
|
|
|
for (auto& s_ : *styles) {
|
|
|
|
|
auto s = (IfcUtil::IfcBaseEntity*) s_;
|
|
|
|
|
std::vector<IfcUtil::IfcBaseEntity*> pss;
|
|
|
|
|
if (s->declaration().is("IfcPresentationStyleAssignment")) {
|
2024-08-23 20:29:07 +02:00
|
|
|
aggregate_of_instance::ptr pstyles = s->get("Styles");
|
2021-03-28 16:55:52 +02:00
|
|
|
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");
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!arg.isNull()) {
|
|
|
|
|
return (std::string) arg;
|
2021-03-28 16:55:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return boost::none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-11 15:00:59 +02:00
|
|
|
void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) {
|
2020-09-20 15:08:52 +02:00
|
|
|
|
|
|
|
|
boost::optional<std::string> object_type;
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!brep_obj->product()->get("ObjectType").isNull()) {
|
|
|
|
|
object_type = static_cast<std::string>(brep_obj->product()->get("ObjectType"));
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-28 16:55:52 +02:00
|
|
|
std::vector<boost::optional<std::vector<double>>> dash_arrays;
|
|
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
auto itm = brep_obj->geometry().as_compound();
|
|
|
|
|
TopoDS_Shape compound_local = ((ifcopenshell::geometry::OpenCascadeShape*)itm)->shape();
|
|
|
|
|
delete itm;
|
|
|
|
|
|
2021-03-28 16:55:52 +02:00
|
|
|
for (auto& x : brep_obj->geometry()) {
|
|
|
|
|
dash_arrays.emplace_back();
|
|
|
|
|
|
2021-08-17 13:49:49 +02:00
|
|
|
boost::optional<std::string> curve_style_name;
|
|
|
|
|
if (file) {
|
|
|
|
|
auto item = (IfcUtil::IfcBaseEntity*) this->file->instance_by_id(x.ItemId());
|
|
|
|
|
curve_style_name = get_curve_style_name(item);
|
|
|
|
|
}
|
2021-03-28 16:55:52 +02:00
|
|
|
|
|
|
|
|
if (curve_style_name &&
|
|
|
|
|
(boost::starts_with(*curve_style_name, "LINE_") ||
|
|
|
|
|
boost::starts_with(*curve_style_name, "DASH_")))
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> 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<double>(tok);
|
|
|
|
|
} catch (boost::bad_lexical_cast&) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
dash_arrays.back()->push_back(d / 1000.);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
gp_Trsf trsf;
|
|
|
|
|
// @todo
|
2023-07-27 16:42:06 +08:00
|
|
|
const auto& m = brep_obj->transformation().data()->ccomponents();
|
2023-03-21 20:15:01 +01:00
|
|
|
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)
|
|
|
|
|
);
|
2020-09-20 15:08:52 +02:00
|
|
|
|
|
|
|
|
const bool is_section = (section_ref_ && object_type && *section_ref_ == *object_type);
|
2021-08-08 18:06:58 +02:00
|
|
|
bool is_elevation = false;
|
|
|
|
|
if (elevation_ref_ && object_type) {
|
|
|
|
|
is_elevation = *elevation_ref_ == *object_type;
|
|
|
|
|
} else if (elevation_ref_guid_) {
|
|
|
|
|
is_elevation = *elevation_ref_guid_ == brep_obj->guid();
|
|
|
|
|
}
|
2021-09-08 13:50:27 +02:00
|
|
|
|
|
|
|
|
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();
|
2020-09-20 15:08:52 +02:00
|
|
|
|
|
|
|
|
if (is_section || is_elevation) {
|
2021-03-28 12:48:20 +02:00
|
|
|
boost::optional<double> scale;
|
|
|
|
|
boost::optional<std::pair<double, double>> size;
|
|
|
|
|
|
2020-09-20 16:39:01 +02:00
|
|
|
auto e = edge_from_compound(compound_unmirrored);
|
2021-03-04 14:39:32 +01:00
|
|
|
boost::optional<gp_Pln> pln;
|
2020-09-20 15:08:52 +02:00
|
|
|
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);
|
2020-09-27 11:10:44 +02:00
|
|
|
auto N = V.Crossed(gp::DZ());
|
2021-03-04 14:39:32 +01:00
|
|
|
pln = gp_Pln(gp_Ax3(P, N, V));
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-28 12:48:20 +02:00
|
|
|
else if (boost::optional<box_t> b = box_from_compound(compound_local)) {
|
2021-03-04 14:39:32 +01:00
|
|
|
pln = gp_Pln().Transformed(trsf);
|
2021-03-28 12:48:20 +02:00
|
|
|
size = std::make_pair(
|
|
|
|
|
b->second[0] - b->first[0],
|
|
|
|
|
b->second[1] - b->first[1]
|
|
|
|
|
);
|
2021-04-24 11:05:18 +02:00
|
|
|
|
|
|
|
|
#if OCC_VERSION_HEX >= 0x70300
|
|
|
|
|
view_box_3d_.emplace();
|
|
|
|
|
BRepBndLib::AddOBB(compound_unmirrored, *view_box_3d_, false, false, false);
|
|
|
|
|
#endif
|
2025-08-06 09:22:47 +02:00
|
|
|
} else {
|
|
|
|
|
Logger::Error("Failed to box or edge from drawing annotation");
|
2021-03-28 12:48:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<string_property> props;
|
|
|
|
|
enumerate_string_properties(brep_obj->product(), std::back_inserter(props));
|
|
|
|
|
std::map<std::string, std::string> 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<boost::char_separator<char>> 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<int>(num) / boost::lexical_cast<int>(denum);
|
|
|
|
|
}
|
|
|
|
|
catch (boost::bad_lexical_cast&) {}
|
|
|
|
|
}
|
2021-03-04 14:39:32 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-18 15:03:28 +02:00
|
|
|
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_
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-04 14:39:32 +01:00
|
|
|
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.
|
2021-10-31 19:37:00 +11:00
|
|
|
double pu, pv;
|
2021-03-04 14:39:32 +01:00
|
|
|
Extrema_ExtPElS ext;
|
|
|
|
|
ext.Perform(gp::Origin(), *pln, 1.e-5);
|
2021-04-18 15:03:28 +02:00
|
|
|
auto P0 = pln->Location();
|
2021-03-04 14:39:32 +01:00
|
|
|
pln->SetLocation(ext.Point(1).Value());
|
2021-10-31 20:03:28 +11:00
|
|
|
ext.Point(1).Parameter(pu, pv);
|
2021-03-04 14:39:32 +01:00
|
|
|
|
2021-04-18 15:03:28 +02:00
|
|
|
if (!emit_building_storeys_ && scale && size) {
|
|
|
|
|
offset_2d_ = std::make_pair(
|
2021-10-31 19:37:00 +11:00
|
|
|
((-size->first / 2.) - pu) * 1000 * *scale_,
|
|
|
|
|
((-size->second / 2.) + pv) * 1000 * *scale_
|
2021-04-18 15:03:28 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-04 14:39:32 +01:00
|
|
|
if (!deferred_section_data_) {
|
|
|
|
|
deferred_section_data_.emplace();
|
|
|
|
|
}
|
|
|
|
|
std::string name = brep_obj->name();
|
|
|
|
|
if (name.empty()) {
|
|
|
|
|
name = boost::lexical_cast<std::string>(brep_obj->id());
|
|
|
|
|
}
|
|
|
|
|
if (is_section) {
|
2021-03-28 12:48:20 +02:00
|
|
|
deferred_section_data_->push_back(vertical_section{ *pln , "Section " + name, false, scale, size });
|
2021-03-04 14:39:32 +01:00
|
|
|
}
|
|
|
|
|
if (is_elevation) {
|
2021-03-28 12:48:20 +02:00
|
|
|
deferred_section_data_->push_back(vertical_section{ *pln , "Elevation " + name, true, scale, size });
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-03-04 14:39:32 +01:00
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto p = storey_elevation_from_element(brep_obj);
|
2023-03-21 20:15:01 +01:00
|
|
|
const IfcUtil::IfcBaseEntity* storey = p ? p->first : nullptr;
|
2020-09-20 15:08:52 +02:00
|
|
|
double elev = p ? p->second : std::numeric_limits<double>::quiet_NaN();
|
2021-03-05 21:50:32 +01:00
|
|
|
// @todo is it correct to call nameElement() here with a single storey (what if this element spans multiple?)
|
2023-08-25 12:48:45 +02:00
|
|
|
|
|
|
|
|
if (unify_inputs_) {
|
|
|
|
|
compound_local = IfcGeom::util::unify(compound_local, 1.e-6);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 13:27:36 +01:00
|
|
|
{
|
|
|
|
|
bool any_wires_converted_to_face = false;
|
|
|
|
|
BRep_Builder BB;
|
|
|
|
|
TopoDS_Compound comp2;
|
|
|
|
|
BB.MakeCompound(comp2);
|
|
|
|
|
TopoDS_Iterator it(compound_local);
|
|
|
|
|
for (; it.More(); it.Next()) {
|
|
|
|
|
auto& s = it.Value();
|
|
|
|
|
if (s.ShapeType() == TopAbs_WIRE && s.Closed()) {
|
|
|
|
|
IfcGeom::util::wire_tolerance_settings wts{ false, false, Precision::Confusion(), Precision::Confusion() };
|
|
|
|
|
TopoDS_Compound faces;
|
|
|
|
|
IfcGeom::util::convert_wire_to_faces(TopoDS::Wire(s), faces, wts);
|
|
|
|
|
BB.Add(comp2, faces);
|
|
|
|
|
any_wires_converted_to_face = true;
|
|
|
|
|
} else {
|
|
|
|
|
BB.Add(comp2, s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
compound_local = comp2;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-24 16:45:31 +01:00
|
|
|
if (only_valid_ && !IfcGeom::util::validate_shape(compound_local)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-28 16:55:52 +02:00
|
|
|
geometry_data data{ compound_local, dash_arrays, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) };
|
2020-09-20 15:08:52 +02:00
|
|
|
|
2021-08-17 14:50:38 +02:00
|
|
|
if (auto_section_ || auto_elevation_ || section_ref_ || elevation_ref_ || elevation_ref_guid_ || deferred_section_data_) {
|
2020-09-20 15:08:52 +02:00
|
|
|
element_buffer_.push_back(data);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-08 13:50:27 +02:00
|
|
|
// Augment bnd_ regardless of whether emitting storeys as we depend
|
|
|
|
|
// on the global bounds also for the storey height annotations.
|
|
|
|
|
BRepBndLib::Add(compound_unmirrored, bnd_);
|
|
|
|
|
|
2021-04-18 15:03:28 +02:00
|
|
|
if (emit_building_storeys_) {
|
|
|
|
|
write(data);
|
|
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-09 10:58:59 +02:00
|
|
|
namespace {
|
|
|
|
|
int infront_or_behind(const gp_Pln& pln, const gp_Pnt& p) {
|
|
|
|
|
auto d = (p.XYZ() - pln.Location().XYZ()).Dot(pln.Axis().Direction().XYZ());
|
|
|
|
|
int state;
|
|
|
|
|
if (std::abs(d) < 1.e-5) {
|
|
|
|
|
state = 0;
|
|
|
|
|
} else {
|
|
|
|
|
state = d < 0. ? -1 : 1;
|
|
|
|
|
}
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
void SvgSerializer::write(const geometry_data& data) {
|
|
|
|
|
std::vector<section_data> section_heights_storage;
|
|
|
|
|
const std::vector<section_data>* section_heights_used = §ion_heights_storage;
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
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);
|
2020-07-11 13:39:09 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2019-05-22 14:44:06 +02:00
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
BRepBuilderAPI_Transform make_transform_global(data.compound_local, data.trsf, true);
|
2020-07-11 23:26:21 +02:00
|
|
|
make_transform_global.Build();
|
|
|
|
|
// (When determinant < 0, copy is implied and the input is not mutated.)
|
2020-09-20 15:08:52 +02:00
|
|
|
auto compound_unmirrored = make_transform_global.Shape();
|
2020-07-11 23:26:21 +02:00
|
|
|
|
2021-04-24 11:05:18 +02:00
|
|
|
#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
|
|
|
|
|
|
2020-07-11 23:26:21 +02:00
|
|
|
// 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;
|
2023-08-11 16:01:01 +10:00
|
|
|
if (!mirror_y_) {
|
|
|
|
|
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
|
|
|
|
|
}
|
|
|
|
|
if (mirror_x_) {
|
|
|
|
|
gp_Trsf mirror_x;
|
|
|
|
|
mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX()));
|
|
|
|
|
trsf_mirror.PreMultiply(mirror_x);
|
|
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
BRepBuilderAPI_Transform make_transform_mirror(compound_unmirrored, trsf_mirror, true);
|
2020-07-11 23:26:21 +02:00
|
|
|
make_transform_mirror.Build();
|
|
|
|
|
// (When determinant < 0, copy is implied and the input is not mutated.)
|
2020-09-20 15:08:52 +02:00
|
|
|
auto compound = make_transform_mirror.Shape();
|
2020-07-11 23:26:21 +02:00
|
|
|
|
|
|
|
|
TopoDS_Wire annotation;
|
|
|
|
|
|
2020-09-20 15:37:47 +02:00
|
|
|
if (is_floor_plan_ && draw_door_arcs_ && data.product->declaration().is("IfcDoor")) {
|
2020-07-11 23:26:21 +02:00
|
|
|
|
|
|
|
|
boost::optional<std::string> operation_type;
|
|
|
|
|
|
|
|
|
|
try {
|
2021-07-29 14:54:51 +02:00
|
|
|
aggregate_of_instance::ptr rels;
|
2020-09-20 15:08:52 +02:00
|
|
|
if (data.product->declaration().schema()->name() == "IFC2X3") {
|
|
|
|
|
rels = data.product->get_inverse("IsDefinedBy");
|
2020-07-11 23:26:21 +02:00
|
|
|
} else {
|
|
|
|
|
// Damn you, IFC
|
2020-09-20 15:08:52 +02:00
|
|
|
rels = data.product->get_inverse("IsTypedBy");
|
2020-07-11 23:26:21 +02:00
|
|
|
}
|
|
|
|
|
for (auto& rel : *rels) {
|
|
|
|
|
if (rel->declaration().name() == "IfcRelDefinesByType") {
|
2024-08-23 20:29:07 +02:00
|
|
|
IfcUtil::IfcBaseClass* ty = ((IfcUtil::IfcBaseEntity*)rel)->get("RelatingType");
|
2020-07-11 23:26:21 +02:00
|
|
|
const std::string& ty_entity_name = ty->declaration().name();
|
|
|
|
|
// Damn you, IFC
|
|
|
|
|
if (ty_entity_name == "IfcDoorStyle" || ty_entity_name == "IfcDoorType") {
|
2024-08-23 20:29:07 +02:00
|
|
|
operation_type = (std::string)((IfcUtil::IfcBaseEntity*)ty)->get("OperationType");
|
2020-07-11 23:26:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (std::exception& e) {
|
|
|
|
|
Logger::Error(e);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-03 15:48:11 +01:00
|
|
|
if (operation_type && ((*operation_type == "SINGLE_SWING_LEFT") || (*operation_type == "SINGLE_SWING_RIGHT"))) {
|
2020-07-11 23:26:21 +02:00
|
|
|
const bool is_left = *operation_type == "SINGLE_SWING_LEFT";
|
|
|
|
|
|
|
|
|
|
Bnd_Box bb;
|
2020-09-20 15:08:52 +02:00
|
|
|
BRepBndLib::Add(data.compound_local, bb);
|
2020-07-11 23:26:21 +02:00
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
center.Transform(data.trsf);
|
|
|
|
|
p1.Transform(data.trsf);
|
|
|
|
|
p2.Transform(data.trsf);
|
2020-07-11 23:26:21 +02:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-16 11:41:40 +02:00
|
|
|
bool emitted = false;
|
|
|
|
|
|
|
|
|
|
for (auto sit = section_heights_used->begin(); sit != section_heights_used->end(); ++sit) {
|
2020-09-20 15:08:52 +02:00
|
|
|
const auto& variant = *sit;
|
2020-08-16 11:41:40 +02:00
|
|
|
|
|
|
|
|
// Elev + offset
|
2020-09-20 15:08:52 +02:00
|
|
|
double cut_z = std::numeric_limits<double>::infinity();
|
2020-08-16 11:41:40 +02:00
|
|
|
|
|
|
|
|
// Elev .. Elev(next)
|
2020-09-20 15:08:52 +02:00
|
|
|
std::pair<double, double> range;
|
|
|
|
|
|
|
|
|
|
gp_Vec projection_direction;
|
2020-12-30 14:25:08 +01:00
|
|
|
gp_Pln projection_plane;
|
2020-09-20 15:08:52 +02:00
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
const IfcUtil::IfcBaseEntity* storey = nullptr;
|
2020-09-20 15:08:52 +02:00
|
|
|
std::string drawing_name;
|
|
|
|
|
|
2021-01-27 22:34:19 +01:00
|
|
|
bool use_hlr = always_project_;
|
2020-09-20 15:08:52 +02:00
|
|
|
|
|
|
|
|
// @todo use visitor
|
|
|
|
|
// horizontal_plan, horizontal_plan_at_element, vertical_section
|
|
|
|
|
if (variant.which() == 0) {
|
|
|
|
|
const auto& plan = boost::get<horizontal_plan>(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<double>::infinity();
|
|
|
|
|
}
|
|
|
|
|
projection_direction = gp::DZ();
|
2021-01-27 22:34:19 +01:00
|
|
|
projection_plane = gp_Pln(gp_Ax3(gp_Pnt(0, 0, cut_z), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0)));
|
2020-09-20 15:08:52 +02:00
|
|
|
} else if (variant.which() == 1) {
|
|
|
|
|
projection_direction = gp::DZ();
|
2021-01-27 22:34:19 +01:00
|
|
|
projection_plane = gp_Pln(gp_Ax3(gp_Pnt(0, 0, cut_z), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0)));
|
2020-09-20 15:08:52 +02:00
|
|
|
} else if (variant.which() == 2) {
|
|
|
|
|
const auto& section = boost::get<vertical_section>(variant);
|
|
|
|
|
projection_direction = section.plane.Axis().Direction();
|
2020-12-30 14:25:08 +01:00
|
|
|
projection_plane = section.plane;
|
2020-09-20 15:08:52 +02:00
|
|
|
drawing_name = section.name;
|
|
|
|
|
use_hlr = section.with_projection;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-20 16:39:01 +02:00
|
|
|
auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored;
|
|
|
|
|
|
2021-01-27 22:34:19 +01:00
|
|
|
if (use_hlr) { // && (hlr.which())) {
|
2020-12-30 14:25:08 +01:00
|
|
|
|
|
|
|
|
// 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]);
|
|
|
|
|
|
2021-01-29 13:03:44 +01:00
|
|
|
bool any_in_front = false, any_behind = false;
|
2020-12-30 14:25:08 +01:00
|
|
|
|
2021-01-29 13:03:44 +01:00
|
|
|
// See if any of the vertices is in the negative Z-axis of the projection plane
|
2020-12-30 14:25:08 +01:00
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
|
|
gp_Pnt p(xs[(i & 1) == 1], ys[(i & 2) == 2], zs[(i & 4) == 4]);
|
2021-09-09 10:58:59 +02:00
|
|
|
int state = infront_or_behind(projection_plane, p);
|
2021-01-02 12:34:10 +01:00
|
|
|
if (state == -1) {
|
2021-01-29 13:03:44 +01:00
|
|
|
any_in_front = true;
|
|
|
|
|
} else if (state == +1) {
|
|
|
|
|
any_behind = true;
|
2020-12-30 14:25:08 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-30 16:15:54 +01:00
|
|
|
// 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")) {
|
2021-01-29 13:03:44 +01:00
|
|
|
|
|
|
|
|
TopoDS_Shape* compound_to_hlr = &compound_to_use;
|
|
|
|
|
TopoDS_Shape subtracted_shape;
|
2021-11-02 08:57:00 +01:00
|
|
|
|
|
|
|
|
bool should_subtract = false;
|
|
|
|
|
|
|
|
|
|
if (subtraction_settings_ == ON_SLABS_AT_FLOORPLANS) {
|
|
|
|
|
should_subtract = data.product->declaration().is("IfcSlab") && is_floor_plan_;
|
|
|
|
|
} else if (subtraction_settings_ == ON_SLABS_AND_WALLS) {
|
|
|
|
|
should_subtract = data.product->declaration().is("IfcSlab") || data.product->declaration().is("IfcWall");
|
|
|
|
|
} else if (subtraction_settings_ == ALWAYS) {
|
|
|
|
|
should_subtract = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (any_in_front && any_behind && should_subtract) {
|
2021-10-28 17:56:02 -04:00
|
|
|
// This is currently only for slanted roof slabs on floor plans
|
2021-01-29 13:03:44 +01:00
|
|
|
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);
|
2023-03-06 09:15:27 +01:00
|
|
|
gp_Pnt _;
|
2021-01-29 13:03:44 +01:00
|
|
|
gp_Vec normal_direction;
|
|
|
|
|
double u0, u1, v0, v1;
|
|
|
|
|
BRepTools::UVBounds(face, u0, u1, v0, v1);
|
2023-03-06 09:15:27 +01:00
|
|
|
prop.Normal((u0 + u1) / 2., (v0 + v1) / 2., _, normal_direction);
|
2021-01-29 13:03:44 +01:00
|
|
|
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;
|
|
|
|
|
|
2021-11-02 15:52:51 +01:00
|
|
|
if (subtraction_settings_ != ON_SLABS_AT_FLOORPLANS || largest < (1. - 1.e-5)) {
|
2021-01-29 13:03:44 +01:00
|
|
|
|
|
|
|
|
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()));
|
2021-09-09 10:58:59 +02:00
|
|
|
int state = infront_or_behind(projection_plane, p);
|
2021-01-29 13:03:44 +01:00
|
|
|
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) {
|
2021-11-05 13:27:27 +01:00
|
|
|
|
|
|
|
|
// Sample eight bounding box points, project on plane
|
|
|
|
|
// and take the min and max U, V parameters to form
|
|
|
|
|
// a 2d bounding box in parameter space on the plane.
|
|
|
|
|
|
|
|
|
|
// This is used to form a cutting plane (halfspace)
|
|
|
|
|
// to trim away parts behind the projection plane
|
|
|
|
|
// before performing HLR.
|
|
|
|
|
|
|
|
|
|
double min_u = +std::numeric_limits<double>::infinity();
|
|
|
|
|
double max_u = -std::numeric_limits<double>::infinity();
|
|
|
|
|
double min_v = +std::numeric_limits<double>::infinity();
|
|
|
|
|
double max_v = -std::numeric_limits<double>::infinity();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
|
|
gp_Pnt p(xs[(i & 1) == 1], ys[(i & 2) == 2], zs[(i & 4) == 4]);
|
|
|
|
|
Extrema_ExtPElS ext;
|
|
|
|
|
ext.Perform(p, projection_plane, 1.e-5);
|
|
|
|
|
if (ext.NbExt() == 1) {
|
|
|
|
|
double pu, pv;
|
|
|
|
|
ext.Point(1).Parameter(pu, pv);
|
|
|
|
|
if (pu < min_u) {
|
|
|
|
|
min_u = pu;
|
|
|
|
|
}
|
|
|
|
|
if (pu > max_u) {
|
|
|
|
|
max_u = pu;
|
|
|
|
|
}
|
|
|
|
|
if (pv < min_v) {
|
|
|
|
|
min_v = pv;
|
|
|
|
|
}
|
|
|
|
|
if (pv > max_v) {
|
|
|
|
|
max_v = pv;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-29 13:03:44 +01:00
|
|
|
try {
|
2021-11-05 13:27:27 +01:00
|
|
|
|
|
|
|
|
BRepBuilderAPI_MakeFace mf(new Geom_Plane(projection_plane), min_u - 1., max_u + 1., min_v - 1., max_v + 1., Precision::Confusion());
|
2021-01-29 13:03:44 +01:00
|
|
|
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();
|
2021-11-05 11:04:46 +01:00
|
|
|
|
|
|
|
|
BRep_Builder BB;
|
|
|
|
|
TopoDS_Compound C;
|
|
|
|
|
BB.MakeCompound(C);
|
|
|
|
|
|
|
|
|
|
// loop over parts to have better luck with co-planar parts
|
|
|
|
|
TopoDS_Iterator it(compound_to_use);
|
|
|
|
|
for (; it.More(); it.Next()) {
|
|
|
|
|
auto part = BRepAlgoAPI_Cut(it.Value(), s).Shape();
|
|
|
|
|
BB.Add(C, part);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
subtracted_shape = C;
|
|
|
|
|
|
2021-01-29 13:03:44 +01:00
|
|
|
compound_to_hlr = &subtracted_shape;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
Logger::Error("Failed to cut element for HLR", data.product);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-20 14:02:12 +01:00
|
|
|
TopoDS_Compound profile_edges;
|
2022-02-27 12:55:39 +01:00
|
|
|
if (profile_threshold_ != -1 && !(data.product->declaration().is("IfcWall") || data.product->declaration().is("IfcSlab"))) {
|
2021-11-20 14:02:12 +01:00
|
|
|
TopTools_IndexedDataMapOfShapeListOfShape map;
|
|
|
|
|
TopExp::MapShapesAndAncestors(*compound_to_hlr, TopAbs_EDGE, TopAbs_FACE, map);
|
|
|
|
|
if (map.Extent() > profile_threshold_) {
|
|
|
|
|
BRep_Builder BB;
|
|
|
|
|
BB.MakeCompound(profile_edges);
|
|
|
|
|
compound_to_hlr = &profile_edges;
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= map.Extent(); ++i) {
|
|
|
|
|
auto& edge = TopoDS::Edge(map.FindKey(i));
|
|
|
|
|
TopoDS_Vertex v0, v1;
|
|
|
|
|
TopExp::Vertices(edge, v0, v1);
|
|
|
|
|
auto pnt0 = BRep_Tool::Pnt(v0);
|
|
|
|
|
auto pnt1 = BRep_Tool::Pnt(v1);
|
|
|
|
|
|
|
|
|
|
// Exclude edges that have both vertices behind plane;
|
|
|
|
|
if (infront_or_behind(projection_plane, pnt0) != -1 && infront_or_behind(projection_plane, pnt1) != -1) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double u0, u1;
|
|
|
|
|
auto crv = BRep_Tool::Curve(edge, u0, u1);
|
|
|
|
|
gp_Pnt _;
|
|
|
|
|
gp_Vec crvd1;
|
|
|
|
|
crv->D1((u0 + u1) / 2., _, crvd1);
|
|
|
|
|
if (crvd1.SquareMagnitude() < 1.e-5) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
crvd1.Normalize();
|
|
|
|
|
// Exclude edges parallel to view direction
|
|
|
|
|
if (std::fabs(crvd1.Dot(projection_direction)) > 0.99) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto faces = map.FindFromIndex(i);
|
|
|
|
|
|
|
|
|
|
// Add non-manifold edges
|
|
|
|
|
bool add = faces.Extent() != 2;
|
|
|
|
|
|
|
|
|
|
// Add profile edges
|
|
|
|
|
if (!add) {
|
|
|
|
|
const auto& f0 = TopoDS::Face(faces.First());
|
|
|
|
|
const auto& f1 = TopoDS::Face(faces.Last());
|
|
|
|
|
|
|
|
|
|
auto s0 = BRep_Tool::Surface(f0);
|
|
|
|
|
auto s1 = BRep_Tool::Surface(f1);
|
|
|
|
|
|
|
|
|
|
// Only supported for planar faces at the moment
|
|
|
|
|
if (s0->DynamicType() != STANDARD_TYPE(Geom_Plane)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (s1->DynamicType() != STANDARD_TYPE(Geom_Plane)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Look up direction
|
|
|
|
|
auto p0 = Handle(Geom_Plane)::DownCast(s0);
|
|
|
|
|
auto p1 = Handle(Geom_Plane)::DownCast(s1);
|
|
|
|
|
auto d0 = p0->Axis().Direction();
|
|
|
|
|
auto d1 = p1->Axis().Direction();
|
|
|
|
|
|
|
|
|
|
auto dot0 = projection_direction.Dot(d0);
|
|
|
|
|
auto dot1 = projection_direction.Dot(d1);
|
|
|
|
|
|
|
|
|
|
if (std::fabs(dot0) < 1.e-5 || std::fabs(dot1)) {
|
|
|
|
|
// In case one face is co planar with the view
|
|
|
|
|
// direction, add the edge in between
|
|
|
|
|
add = true;
|
|
|
|
|
} else {
|
|
|
|
|
// Profile edges are adges where the sign of the
|
|
|
|
|
// dot product Vdir . Fnormal flips sign.
|
|
|
|
|
add = std::signbit(dot0) != std::signbit(dot1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (add) {
|
|
|
|
|
BB.Add(profile_edges, edge);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-27 20:57:05 +01:00
|
|
|
if (is_floor_plan_) {
|
|
|
|
|
if (storey) {
|
2023-06-29 13:08:22 +02:00
|
|
|
auto it = storey_hlr.find(storey);
|
|
|
|
|
if (it == storey_hlr.end()) {
|
2023-08-30 10:57:33 +02:00
|
|
|
it = storey_hlr.insert({ storey, hlr_t(use_prefiltering_, use_hlr_poly_, segment_projection_, projection_plane) }).first;
|
2021-01-27 22:34:19 +01:00
|
|
|
}
|
2023-08-30 10:57:33 +02:00
|
|
|
it->second.add(*compound_to_hlr, data.product);
|
2022-02-27 20:57:05 +01:00
|
|
|
} else {
|
|
|
|
|
Logger::Warning("Unable to invoke HLR due to absence of storey containment", data.product);
|
2021-01-27 22:34:19 +01:00
|
|
|
}
|
2023-06-29 13:08:22 +02:00
|
|
|
} else if (hlr) {
|
2023-08-30 10:57:33 +02:00
|
|
|
hlr->add(*compound_to_hlr, data.product);
|
2021-01-02 12:34:10 +01:00
|
|
|
}
|
2020-12-30 14:25:08 +01:00
|
|
|
}
|
2020-08-16 11:41:40 +02:00
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2020-09-20 16:39:01 +02:00
|
|
|
TopoDS_Iterator it(compound_to_use);
|
2021-03-28 16:55:52 +02:00
|
|
|
auto dash_it = data.dash_arrays.begin();
|
2017-12-20 12:40:15 +01:00
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
TopoDS_Face largest_closed_wire_face;
|
|
|
|
|
double largest_closed_wire_area = 0.;
|
2023-03-06 09:16:27 +01:00
|
|
|
|
|
|
|
|
gp_Pln pln;
|
|
|
|
|
if (variant.which() < 2) {
|
|
|
|
|
pln = gp_Pln(gp_Pnt(0, 0, cut_z), gp::DZ());
|
|
|
|
|
} else {
|
|
|
|
|
const auto& section = boost::get<vertical_section>(variant);
|
|
|
|
|
pln = section.plane;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto svg_name = data.svg_name;
|
|
|
|
|
|
|
|
|
|
path_object* po_ = nullptr;
|
|
|
|
|
auto po = [this, &po_, &pln, &storey, &drawing_name, &svg_name]() {
|
|
|
|
|
if (po_ == nullptr) {
|
|
|
|
|
if (storey) {
|
|
|
|
|
po_ = &start_path(pln, storey, svg_name);
|
|
|
|
|
} else {
|
|
|
|
|
po_ = &start_path(pln, drawing_name, svg_name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return po_;
|
|
|
|
|
};
|
2020-04-01 12:19:56 +02:00
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
// Iterate over components of compound to have better chance of matching section edges to closed wires
|
2021-03-28 16:55:52 +02:00
|
|
|
for (; it.More(); it.Next(), ++dash_it) {
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2023-08-25 12:48:45 +02:00
|
|
|
const TopoDS_Shape& subshape = it.Value();
|
2020-09-20 15:08:52 +02:00
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
Bnd_Box bb;
|
2020-08-13 12:22:03 +02:00
|
|
|
try {
|
|
|
|
|
BRepBndLib::Add(it.Value(), bb);
|
|
|
|
|
} catch (const Standard_Failure&) {}
|
2020-07-11 13:39:09 +02:00
|
|
|
|
|
|
|
|
// Empty geometry
|
|
|
|
|
if (bb.IsVoid()) {
|
|
|
|
|
continue;
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
double x1, y1, zmin, x2, y2, zmax;
|
|
|
|
|
bb.Get(x1, y1, zmin, x2, y2, zmax);
|
2020-09-20 15:08:52 +02:00
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
// 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
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
if (variant.which() == 1) {
|
2020-07-11 13:39:09 +02:00
|
|
|
cut_z = zmin + 1.;
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
|
|
|
|
|
gp_Vec bbmin(x1, y1, zmin);
|
|
|
|
|
gp_Vec bbmax(x2, y2, zmax);
|
|
|
|
|
auto bbdif = bbmax - bbmin;
|
|
|
|
|
auto proj = projection_direction ^ bbdif ^ projection_direction;
|
|
|
|
|
|
2021-01-29 13:03:44 +01:00
|
|
|
std::string object_type;
|
|
|
|
|
auto ot_arg = data.product->get("ObjectType");
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!ot_arg.isNull()) {
|
|
|
|
|
object_type = (std::string) ot_arg;
|
2021-02-09 14:04:01 +01:00
|
|
|
object_type.erase(std::remove_if(object_type.begin(), object_type.end(), [](char c) { return !std::isalnum(c); }), object_type.end());
|
2021-01-29 13:03:44 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-08 14:21:25 +02:00
|
|
|
auto z_global = gp::DZ().Transformed(data.trsf);
|
2021-09-09 10:58:59 +02:00
|
|
|
auto xyz_global = gp_Pnt().Transformed(data.trsf);
|
|
|
|
|
int state = infront_or_behind(projection_plane, xyz_global);
|
2021-04-29 07:53:05 +02:00
|
|
|
|
2021-03-05 21:50:32 +01:00
|
|
|
if (data.product->declaration().is("IfcAnnotation") && // is an Annotation
|
|
|
|
|
(proj.Magnitude() > 1.e-5) && // when projected onto the view has a length
|
2021-09-09 10:58:59 +02:00
|
|
|
(is_floor_plan_
|
2021-04-29 07:53:05 +02:00
|
|
|
? (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
|
2021-09-09 10:58:59 +02:00
|
|
|
: (projection_direction.Dot(z_global) > 0.99 && state == -1) // For elevations only include annotations that are "facing" the view direction
|
|
|
|
|
))
|
2021-01-29 13:03:44 +01:00
|
|
|
{
|
2021-02-09 14:04:01 +01:00
|
|
|
if (object_type.size()) {
|
|
|
|
|
// postfix the object_type for CSS matching
|
|
|
|
|
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:53:05 +02:00
|
|
|
auto subshape_to_use = subshape;
|
|
|
|
|
if (variant.which() == 2) {
|
|
|
|
|
// @todo remove duplication with code below.
|
|
|
|
|
|
|
|
|
|
gp_Trsf trsf;
|
|
|
|
|
trsf.SetTransformation(gp::XOY(), pln.Position());
|
|
|
|
|
subshape_to_use.Move(trsf);
|
|
|
|
|
|
2023-03-06 09:15:27 +01:00
|
|
|
BRepBuilderAPI_Transform make_transform_mirror_(subshape_to_use, trsf_mirror, true);
|
|
|
|
|
make_transform_mirror_.Build();
|
|
|
|
|
subshape_to_use = make_transform_mirror_.Shape();
|
2021-04-29 07:53:05 +02:00
|
|
|
}
|
|
|
|
|
|
2021-02-09 14:04:01 +01:00
|
|
|
if (object_type == "Dimension") {
|
2020-08-16 11:41:40 +02:00
|
|
|
|
2021-04-29 07:53:05 +02:00
|
|
|
TopExp_Explorer exp(subshape_to_use, TopAbs_EDGE, TopAbs_FACE);
|
2021-02-09 14:04:01 +01:00
|
|
|
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);
|
2023-03-06 09:16:27 +01:00
|
|
|
write(*po(), W);
|
2021-02-09 14:04:01 +01:00
|
|
|
|
|
|
|
|
// @todo should we take the average parameter value instead?
|
|
|
|
|
gp_XYZ center = (p0.XYZ() + p1.XYZ()) / 2.;
|
2021-03-08 14:19:07 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2021-02-09 14:04:01 +01:00
|
|
|
|
2021-04-02 21:21:27 +02:00
|
|
|
std::string text_offset = "8";
|
|
|
|
|
if (scale_) {
|
|
|
|
|
text_offset = "1";
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 14:04:01 +01:00
|
|
|
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(" <text class=\"IfcAnnotation\" text-anchor=\"middle\" x=\"");
|
2020-08-16 11:41:40 +02:00
|
|
|
xcoords.push_back(path.add(center.X()));
|
2021-02-09 14:04:01 +01:00
|
|
|
path.add("\" y=\"");
|
|
|
|
|
ycoords.push_back(path.add(center.Y()));
|
2021-03-08 14:19:07 +01:00
|
|
|
path.add("\" transform=\"rotate(");
|
|
|
|
|
path.add(z_rotation);
|
|
|
|
|
path.add(" ");
|
|
|
|
|
xcoords.push_back(path.add(center.X()));
|
|
|
|
|
path.add(" ");
|
|
|
|
|
ycoords.push_back(path.add(center.Y()));
|
2021-04-02 21:21:27 +02:00
|
|
|
path.add(") translate(0 -" + text_offset + ")\">");
|
2021-03-08 14:19:07 +01:00
|
|
|
|
2021-02-09 14:04:01 +01:00
|
|
|
std::vector<std::string> 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) {
|
2023-06-09 11:34:50 +02:00
|
|
|
auto l = *lit;
|
|
|
|
|
IfcUtil::escape_xml(l);
|
2021-02-09 14:04:01 +01:00
|
|
|
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("<tspan x=\"");
|
|
|
|
|
xcoords.push_back(path.add(center.X()));
|
|
|
|
|
path.add("\" dy=\"");
|
|
|
|
|
path.add(boost::lexical_cast<std::string>(dy));
|
|
|
|
|
path.add("em\">");
|
|
|
|
|
path.add(l);
|
|
|
|
|
path.add("</tspan>");
|
|
|
|
|
}
|
|
|
|
|
path.add("</text>");
|
2023-03-06 09:16:27 +01:00
|
|
|
po()->second.push_back(path);
|
2020-08-16 11:41:40 +02:00
|
|
|
}
|
2021-02-09 14:04:01 +01:00
|
|
|
|
|
|
|
|
} else if (object_type == "Symbol") {
|
|
|
|
|
|
2021-04-29 07:53:05 +02:00
|
|
|
TopExp_Explorer exp(subshape_to_use, TopAbs_WIRE, TopAbs_FACE);
|
2021-02-09 14:04:01 +01:00
|
|
|
for (; exp.More(); exp.Next()) {
|
|
|
|
|
const auto& W = TopoDS::Wire(exp.Current());
|
2023-03-06 09:16:27 +01:00
|
|
|
write(*po(), W, *dash_it);
|
2021-02-09 14:04:01 +01:00
|
|
|
}
|
|
|
|
|
|
2020-08-16 11:41:40 +02:00
|
|
|
}
|
2021-02-09 14:04:01 +01:00
|
|
|
|
|
|
|
|
// We're finished processing IfcAnnotation instances
|
2020-08-16 11:41:40 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (subshape.ShapeType() > TopAbs_FACE) {
|
|
|
|
|
// Except for annotations we only emit solids and surfaces to SVG.
|
|
|
|
|
emitted = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
// No intersection with bounding box, fail early
|
2020-09-20 15:08:52 +02:00
|
|
|
if (variant.which() < 2) {
|
|
|
|
|
if (zmin > cut_z || zmax < cut_z) continue;
|
|
|
|
|
}
|
2020-07-11 13:39:09 +02:00
|
|
|
|
2020-08-16 11:41:40 +02:00
|
|
|
emitted = true;
|
|
|
|
|
|
2021-08-08 20:21:01 +02:00
|
|
|
if (object_type.size()) {
|
|
|
|
|
// prefix class to indicate this is a cut element
|
2023-03-06 09:16:27 +01:00
|
|
|
// @todo this is getting out of control, use a proper xml/svg library.
|
|
|
|
|
if(svg_name.find("class=\"cut ") == std::string::npos) {
|
|
|
|
|
boost::replace_all(svg_name, "class=\"", "class=\"cut ");
|
2020-09-20 15:50:41 +02:00
|
|
|
}
|
2020-08-16 11:41:40 +02:00
|
|
|
}
|
2020-04-01 12:19:56 +02:00
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln);
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
if (variant.which() == 2) {
|
|
|
|
|
gp_Trsf trsf;
|
|
|
|
|
trsf.SetTransformation(gp::XOY(), pln.Position());
|
|
|
|
|
result.Move(trsf);
|
2020-09-20 16:39:01 +02:00
|
|
|
|
2023-03-06 09:15:27 +01:00
|
|
|
BRepBuilderAPI_Transform make_transform_mirror_(result, trsf_mirror, true);
|
|
|
|
|
make_transform_mirror_.Build();
|
|
|
|
|
result = make_transform_mirror_.Shape();
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
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;
|
|
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
TopoDS_Compound wires_compound;
|
|
|
|
|
BRep_Builder BB;
|
|
|
|
|
BB.MakeCompound(wires_compound);
|
|
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
for (int i = 1; i <= wires->Length(); ++i) {
|
2021-03-09 16:11:42 +01:00
|
|
|
|
|
|
|
|
// @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));
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
if (wire.Closed() && (print_space_names_ || print_space_areas_) && data.product->declaration().is("IfcSpace")) {
|
2020-07-11 13:39:09 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
2020-07-11 13:39:09 +02:00
|
|
|
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
2021-03-09 16:11:42 +01:00
|
|
|
|
2022-11-13 11:04:16 +01:00
|
|
|
if (file && data.product->declaration().is("IfcBuildingStorey") && storey_height_display_ != SH_NONE && wires->Length() == 1 && IfcGeom::util::count(wire, TopAbs_EDGE) == 1) {
|
2021-03-05 21:50:32 +01:00
|
|
|
|
|
|
|
|
std::string elev_str;
|
|
|
|
|
|
|
|
|
|
const double lu = file->getUnit("LENGTHUNIT").second;
|
|
|
|
|
auto a = data.product->get("Elevation");
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!a.isNull()) {
|
|
|
|
|
double elev = a;
|
2021-03-07 12:07:40 +01:00
|
|
|
|
|
|
|
|
// @nb we don't actually factor in the length unit.
|
|
|
|
|
// elev *= lu;
|
|
|
|
|
|
2021-03-05 21:50:32 +01:00
|
|
|
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<std::string> 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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-09 16:11:42 +01:00
|
|
|
// @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();
|
2021-03-28 21:38:29 +02:00
|
|
|
const double shll = storey_height_line_length_.get_value_or(2.);
|
|
|
|
|
d *= shll;
|
2021-03-09 16:11:42 +01:00
|
|
|
gp_Pnt p1x(p0.XYZ() + d);
|
|
|
|
|
|
|
|
|
|
wire = BRepBuilderAPI_MakePolygon(p0, p1x).Wire();
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-05 21:50:32 +01:00
|
|
|
// dominant-baseline="central" is not well supported in IE.
|
|
|
|
|
// so we add a 0.35 offset to the dy of the tspans
|
2021-03-09 16:11:42 +01:00
|
|
|
path.add(" <text text-anchor=\"" + anchor + "\" x=\"");
|
|
|
|
|
xcoords.push_back(path.add(anchor_pt->X()));
|
2021-03-05 21:50:32 +01:00
|
|
|
path.add("\" y=\"");
|
2021-03-09 16:11:42 +01:00
|
|
|
ycoords.push_back(path.add(anchor_pt->Y()));
|
2021-03-05 21:50:32 +01:00
|
|
|
path.add("\">");
|
|
|
|
|
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
|
2023-06-09 11:34:50 +02:00
|
|
|
auto l = *lit;
|
|
|
|
|
IfcUtil::escape_xml(l);
|
2021-03-05 21:50:32 +01:00
|
|
|
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("<tspan x=\"");
|
2021-03-09 16:11:42 +01:00
|
|
|
xcoords.push_back(path.add(anchor_pt->X()));
|
2021-03-05 21:50:32 +01:00
|
|
|
path.add("\" dy=\"");
|
|
|
|
|
path.add(boost::lexical_cast<std::string>(dy));
|
|
|
|
|
path.add("em\">");
|
|
|
|
|
path.add(l);
|
|
|
|
|
path.add("</tspan>");
|
|
|
|
|
}
|
|
|
|
|
path.add("</text>");
|
2023-03-06 09:16:27 +01:00
|
|
|
po()->second.push_back(path);
|
2021-03-05 21:50:32 +01:00
|
|
|
}
|
2021-03-09 16:11:42 +01:00
|
|
|
|
2021-11-05 11:21:19 +01:00
|
|
|
BB.Add(wires_compound, wire);
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
2021-11-05 11:21:19 +01:00
|
|
|
|
2023-03-06 09:16:27 +01:00
|
|
|
if (TopoDS_Iterator(wires_compound).More()) {
|
|
|
|
|
write(*po(), wires_compound);
|
|
|
|
|
}
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
if (!largest_closed_wire_face.IsNull()) {
|
|
|
|
|
std::vector<gp_Pnt> 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));
|
|
|
|
|
}
|
2020-05-10 11:10:30 +02:00
|
|
|
}
|
2020-07-11 13:39:09 +02:00
|
|
|
|
|
|
|
|
// we brute force the largest distance between pairs of points where
|
|
|
|
|
// the center is contained in the face.
|
|
|
|
|
|
|
|
|
|
std::pair<const gp_Pnt*, const gp_Pnt*> furthest_points = { nullptr, nullptr };
|
|
|
|
|
double furthest_points_distance = 0.;
|
|
|
|
|
boost::optional<gp_Pnt> 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,
|
2021-10-28 17:56:02 -04:00
|
|
|
// the distance is not simply euclidean, but we
|
2020-07-11 13:39:09 +02:00
|
|
|
// 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()))
|
|
|
|
|
);
|
2021-04-04 21:15:13 +02:00
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
if (d > furthest_points_distance) {
|
2021-04-04 21:15:13 +02:00
|
|
|
|
|
|
|
|
// Sample some points on the line and assure it's inside.
|
|
|
|
|
bool all_inside = true;
|
2023-03-06 09:15:27 +01:00
|
|
|
for (int n = 5; n < 95; ++n) {
|
|
|
|
|
gp_Pnt p3d((pa.XYZ() + (pb.XYZ() - pa.XYZ()) * n / 100.));
|
2021-04-04 21:15:13 +02:00
|
|
|
gp_Pnt2d p2d(p3d.X(), p3d.Y());
|
|
|
|
|
|
|
|
|
|
if (fcls.Perform(p2d) != TopAbs_IN) {
|
|
|
|
|
all_inside = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-11 13:39:09 +02:00
|
|
|
|
2021-04-04 21:15:13 +02:00
|
|
|
if (all_inside) {
|
|
|
|
|
gp_Pnt p3d((pa.XYZ() + pb.XYZ()) * 0.5);
|
|
|
|
|
gp_Pnt2d p2d(p3d.X(), p3d.Y());
|
2020-07-11 13:39:09 +02:00
|
|
|
furthest_points = { &pa, &pb };
|
|
|
|
|
furthest_points_distance = d;
|
|
|
|
|
center_point = p3d;
|
|
|
|
|
}
|
2021-04-04 21:15:13 +02:00
|
|
|
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-11 13:39:09 +02:00
|
|
|
if (center_point) {
|
|
|
|
|
std::vector<std::string> labels;
|
|
|
|
|
if (print_space_names_) {
|
2020-09-20 15:08:52 +02:00
|
|
|
labels.push_back(data.ifc_name);
|
2020-07-11 13:39:09 +02:00
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
if (print_space_names_ && data.product->declaration().is("IfcSpace")) {
|
|
|
|
|
auto attr = data.product->get("LongName");
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!attr.isNull()) {
|
|
|
|
|
std::string long_name = attr;
|
2020-07-11 13:39:09 +02:00
|
|
|
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(" <text text-anchor=\"middle\" x=\"");
|
2020-04-01 12:19:56 +02:00
|
|
|
xcoords.push_back(path.add(center_point->X()));
|
2020-07-11 13:39:09 +02:00
|
|
|
path.add("\" y=\"");
|
|
|
|
|
ycoords.push_back(path.add(center_point->Y()));
|
2021-03-28 22:16:58 +02:00
|
|
|
path.add("\"");
|
|
|
|
|
if (space_name_transform_) {
|
|
|
|
|
path.add(" transform=\"" + *space_name_transform_ + "\"");
|
|
|
|
|
}
|
|
|
|
|
path.add(">");
|
2020-07-11 13:39:09 +02:00
|
|
|
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
|
2023-06-09 11:34:50 +02:00
|
|
|
auto l = *lit;
|
|
|
|
|
IfcUtil::escape_xml(l);
|
2020-07-11 13:39:09 +02:00
|
|
|
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("<tspan x=\"");
|
|
|
|
|
xcoords.push_back(path.add(center_point->X()));
|
|
|
|
|
path.add("\" dy=\"");
|
|
|
|
|
path.add(boost::lexical_cast<std::string>(dy));
|
|
|
|
|
path.add("em\">");
|
|
|
|
|
path.add(l);
|
|
|
|
|
path.add("</tspan>");
|
|
|
|
|
}
|
|
|
|
|
path.add("</text>");
|
2023-03-06 09:16:27 +01:00
|
|
|
po()->second.push_back(path);
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-07-11 23:26:21 +02:00
|
|
|
|
2023-03-06 09:16:27 +01:00
|
|
|
if (!annotation.IsNull()) {
|
|
|
|
|
write(*po(), annotation);
|
2020-07-11 23:26:21 +02:00
|
|
|
}
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
2020-08-16 11:41:40 +02:00
|
|
|
|
|
|
|
|
if (!emitted) {
|
2020-09-20 15:08:52 +02:00
|
|
|
Logger::Warning("Element not written to SVG due to section heights", data.product);
|
2020-08-16 11:41:40 +02:00
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SvgSerializer::setBoundingRectangle(double width, double height) {
|
2021-03-28 12:48:20 +02:00
|
|
|
size_ = std::make_pair(width, height);
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-31 11:31:05 +01:00
|
|
|
std::array<std::array<double, 3>, 3> SvgSerializer::resize() {
|
|
|
|
|
// identity matrix;
|
|
|
|
|
std::array<std::array<double, 3>, 3> m = {{ {{1,0,0}},{{0,1,0}},{{0,0,1}} }};
|
|
|
|
|
|
2021-03-28 12:48:20 +02:00
|
|
|
if (size_) {
|
2015-07-26 14:26:13 +00:00
|
|
|
// Scale the resulting image to a bounding rectangle specified by command line arguments
|
2021-04-18 15:03:28 +02:00
|
|
|
// or specified by IfcAnnotation[ObjectType=DRAWING]
|
2015-07-26 14:26:13 +00:00
|
|
|
const double dx = xmax - xmin;
|
|
|
|
|
const double dy = ymax - ymin;
|
|
|
|
|
|
2020-08-25 15:01:07 +02:00
|
|
|
double sc, cx, cy;
|
2021-04-18 15:03:28 +02:00
|
|
|
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_) {
|
2020-08-25 15:01:07 +02:00
|
|
|
sc = (*scale_) * 1000;
|
2021-03-28 12:48:20 +02:00
|
|
|
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);
|
2021-04-18 15:03:28 +02:00
|
|
|
} else {
|
2020-09-20 15:08:52 +02:00
|
|
|
if (calculated_scale_) {
|
|
|
|
|
sc = *calculated_scale_;
|
2020-12-30 14:25:08 +01:00
|
|
|
}
|
|
|
|
|
else {
|
2021-03-28 12:48:20 +02:00
|
|
|
if (dx / size_->first > dy / size_->second) {
|
|
|
|
|
sc = size_->first / dx;
|
2020-12-30 14:25:08 +01:00
|
|
|
}
|
|
|
|
|
else {
|
2021-03-28 12:48:20 +02:00
|
|
|
sc = size_->second / dy;
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
calculated_scale_ = sc;
|
2020-08-25 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
cx = xmin * sc;
|
|
|
|
|
cy = ymin * sc;
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 16:01:01 +10:00
|
|
|
if (mirror_y_) {
|
|
|
|
|
cy = - size_->second - cy;
|
|
|
|
|
}
|
|
|
|
|
if (mirror_x_) {
|
|
|
|
|
cx = - size_->first - cx;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-31 11:31:05 +01:00
|
|
|
m = {{ {{sc,0,-cx}},{{0,sc,-cy}},{{0,0,1}} }};
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
float_item_list::const_iterator it;
|
|
|
|
|
for (it = xcoords.begin() + xcoords_begin; it != xcoords.end(); ++it, ++xcoords_begin) {
|
2015-07-26 14:26:13 +00:00
|
|
|
double& v = (*it)->value();
|
|
|
|
|
v = v * sc - cx;
|
|
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
for (it = ycoords.begin() + ycoords_begin; it != ycoords.end(); ++it, ++ycoords_begin) {
|
2015-07-26 14:26:13 +00:00
|
|
|
double& v = (*it)->value();
|
|
|
|
|
v = v * sc - cy;
|
|
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
for (it = radii.begin() + radii_begin; it != radii.end(); ++it, ++radii_begin) {
|
2015-07-26 14:26:13 +00:00
|
|
|
(*it)->value() *= sc;
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
2020-12-31 11:31:05 +01:00
|
|
|
|
|
|
|
|
return m;
|
2020-12-30 14:25:08 +01:00
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2021-01-27 22:34:19 +01:00
|
|
|
void SvgSerializer::draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name) {
|
2023-08-30 10:57:33 +02:00
|
|
|
auto hlr_items = (drawing_name.first ? this->storey_hlr.find(drawing_name.first)->second : *hlr).build();
|
2021-01-27 22:34:19 +01:00
|
|
|
|
2023-08-30 10:57:33 +02:00
|
|
|
for (auto& p : hlr_items) {
|
|
|
|
|
const TopoDS_Shape& hlr_compound_unmirrored = p.second;
|
2021-01-27 22:34:19 +01:00
|
|
|
|
2023-08-30 10:57:33 +02:00
|
|
|
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()));
|
2023-08-11 16:01:01 +10:00
|
|
|
}
|
2023-08-30 10:57:33 +02:00
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
if (!mirror_y_) {
|
|
|
|
|
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
|
|
|
|
|
}
|
|
|
|
|
if (mirror_x_) {
|
|
|
|
|
gp_Trsf mirror_x;
|
|
|
|
|
mirror_x.SetMirror(gp_Ax2(gp::Origin(), gp::DX()));
|
|
|
|
|
trsf_mirror.PreMultiply(mirror_x);
|
|
|
|
|
}
|
|
|
|
|
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;
|
2023-08-11 16:01:01 +10:00
|
|
|
}
|
2021-01-27 22:34:19 +01:00
|
|
|
|
2023-08-30 10:57:33 +02:00
|
|
|
exp.Init(hlr_compound, TopAbs_EDGE);
|
|
|
|
|
BRep_Builder B;
|
|
|
|
|
path_object* po;
|
|
|
|
|
std::string name;
|
|
|
|
|
if (p.first) {
|
|
|
|
|
name = nameElement(p.first);
|
|
|
|
|
boost::replace_all(name, "class=\"", "class=\"projection ");
|
|
|
|
|
} else {
|
|
|
|
|
name = "class=\"projection\"";
|
|
|
|
|
}
|
|
|
|
|
if (drawing_name.first) {
|
|
|
|
|
po = &start_path(pln, drawing_name.first, name);
|
|
|
|
|
} else {
|
|
|
|
|
po = &start_path(pln, drawing_name.second, name);
|
|
|
|
|
}
|
|
|
|
|
for (; exp.More(); exp.Next()) {
|
|
|
|
|
TopoDS_Wire w;
|
|
|
|
|
B.MakeWire(w);
|
|
|
|
|
B.Add(w, exp.Current());
|
|
|
|
|
write(*po, w);
|
|
|
|
|
}
|
2021-01-27 22:34:19 +01:00
|
|
|
|
2023-08-30 10:57:33 +02:00
|
|
|
}
|
2021-01-27 22:34:19 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-30 14:25:08 +01:00
|
|
|
void SvgSerializer::resetScale() {
|
2020-09-20 15:08:52 +02:00
|
|
|
// reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale.
|
2020-12-30 14:25:08 +01:00
|
|
|
// this is a separate call now as we first need to read drawing extents for automatically positioning sections and
|
|
|
|
|
// elevations
|
2020-09-20 15:08:52 +02:00
|
|
|
xmin = +std::numeric_limits<double>::infinity();
|
|
|
|
|
ymin = +std::numeric_limits<double>::infinity();
|
|
|
|
|
xmax = -std::numeric_limits<double>::infinity();
|
|
|
|
|
ymax = -std::numeric_limits<double>::infinity();
|
|
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
|
2021-04-02 21:59:49 +02:00
|
|
|
void SvgSerializer::addTextAnnotations(const drawing_key& k) {
|
|
|
|
|
auto& meta = drawing_metadata[k];
|
|
|
|
|
|
|
|
|
|
boost::optional<std::pair<double, double>> range;
|
|
|
|
|
|
|
|
|
|
if (k.first && section_data_) {
|
|
|
|
|
for (auto& sd : *section_data_) {
|
|
|
|
|
if (sd.which() == 0) {
|
|
|
|
|
const auto& plan = boost::get<horizontal_plan>(sd);
|
|
|
|
|
if (k.first == plan.storey) {
|
|
|
|
|
range = std::make_pair(plan.elevation, plan.next_elevation);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-17 13:49:49 +02:00
|
|
|
aggregate_of_instance::ptr annotations;
|
|
|
|
|
if (file) {
|
|
|
|
|
annotations = file->instances_by_type("IfcAnnotation");
|
|
|
|
|
}
|
2021-04-04 21:06:38 +02:00
|
|
|
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");
|
|
|
|
|
|
2024-08-23 20:29:07 +02:00
|
|
|
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;
|
2021-04-04 21:06:38 +02:00
|
|
|
|
|
|
|
|
if (object_type == "Text") {
|
2023-11-15 10:32:20 +01:00
|
|
|
auto mapping = ifcopenshell::geometry::impl::mapping_implementations().construct(file, geometry_settings_);
|
2024-08-23 20:29:07 +02:00
|
|
|
auto item = mapping->map(pl);
|
2023-07-27 16:42:06 +08:00
|
|
|
auto matrix = ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::matrix4>(item);
|
2023-03-21 20:15:01 +01:00
|
|
|
delete mapping;
|
|
|
|
|
if (item) {
|
|
|
|
|
gp_Trsf trsf;
|
|
|
|
|
auto& m = matrix->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)
|
|
|
|
|
);
|
2023-07-27 16:42:06 +08:00
|
|
|
#ifdef TAXONOMY_USE_NAKED_PTR
|
2023-03-21 20:15:01 +01:00
|
|
|
delete matrix;
|
2023-07-27 16:42:06 +08:00
|
|
|
#endif
|
2021-04-04 21:06:38 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
auto v = gp_Pnt(trsf.TranslationPart());
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-24 14:18:53 +02:00
|
|
|
auto z_local = gp::DZ().Transformed(trsf);
|
|
|
|
|
auto view_dir = z_local.Dot(meta.pln_3d.Axis().Direction());
|
|
|
|
|
|
|
|
|
|
if ((!range || (v.Z() >= range->first && v.Z() < range->second)) && view_dir > 0.99) {
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
gp_Trsf trsf_view;
|
|
|
|
|
trsf_view.SetTransformation(gp::XOY(), meta.pln_3d.Position());
|
|
|
|
|
v.Transform(trsf_view);
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
auto svg_name = nameElement(ann);
|
|
|
|
|
|
|
|
|
|
if (object_type.size()) {
|
|
|
|
|
// postfix the object_type for CSS matching
|
|
|
|
|
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
|
|
|
|
|
}
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2023-08-30 10:57:33 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
boost::optional<double> font_size;
|
|
|
|
|
std::vector<std::string> tokens;
|
|
|
|
|
boost::split(tokens, name, boost::is_any_of("_"));
|
|
|
|
|
if (tokens.size() == 2) {
|
|
|
|
|
try {
|
|
|
|
|
font_size = boost::lexical_cast<double>(tokens.back());
|
2021-04-02 21:59:49 +02:00
|
|
|
}
|
2021-04-29 16:16:41 +02:00
|
|
|
catch (...) {}
|
|
|
|
|
}
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
// @todo column or row?
|
|
|
|
|
double z_rotation = gp::DX().Transformed(trsf).AngleWithRef(
|
|
|
|
|
meta.pln_3d.Position().XDirection(),
|
|
|
|
|
meta.pln_3d.Position().Direction()
|
|
|
|
|
);
|
|
|
|
|
z_rotation *= 180. / M_PI;
|
|
|
|
|
|
|
|
|
|
auto y = -v.Y();
|
|
|
|
|
|
|
|
|
|
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(" <text text-anchor=\"left\" x=\"");
|
|
|
|
|
xcoords.push_back(path.add(v.X()));
|
|
|
|
|
path.add("\" y=\"");
|
|
|
|
|
ycoords.push_back(path.add(y));
|
|
|
|
|
|
|
|
|
|
path.add("\" transform=\"rotate(");
|
|
|
|
|
path.add(z_rotation);
|
|
|
|
|
path.add(" ");
|
|
|
|
|
xcoords.push_back(path.add(v.X()));
|
|
|
|
|
path.add(" ");
|
|
|
|
|
ycoords.push_back(path.add(y));
|
|
|
|
|
path.add(")\"");
|
|
|
|
|
|
|
|
|
|
if (font_size) {
|
|
|
|
|
path.add(" font-size=\"");
|
|
|
|
|
path.add(*font_size);
|
|
|
|
|
path.add("\"");
|
|
|
|
|
}
|
|
|
|
|
path.add(">");
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
std::vector<std::string> labels{ desc };
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
|
2023-06-09 11:34:50 +02:00
|
|
|
auto l = *lit;
|
|
|
|
|
IfcUtil::escape_xml(l);
|
2021-04-29 16:16:41 +02:00
|
|
|
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("<tspan x=\"");
|
2021-04-02 21:59:49 +02:00
|
|
|
xcoords.push_back(path.add(v.X()));
|
2021-04-29 16:16:41 +02:00
|
|
|
path.add("\" dy=\"");
|
|
|
|
|
path.add(boost::lexical_cast<std::string>(dy));
|
|
|
|
|
path.add("em\">");
|
|
|
|
|
path.add(l);
|
|
|
|
|
path.add("</tspan>");
|
|
|
|
|
}
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
path.add("</text>");
|
2021-04-02 21:59:49 +02:00
|
|
|
|
2021-04-29 16:16:41 +02:00
|
|
|
po->second.push_back(path);
|
2021-04-02 21:59:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
void SvgSerializer::finalize() {
|
2021-04-18 15:03:28 +02:00
|
|
|
doWriteHeader();
|
|
|
|
|
|
2021-04-02 21:59:49 +02:00
|
|
|
for (auto& p : drawing_metadata) {
|
|
|
|
|
addTextAnnotations(p.first);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 22:34:19 +01:00
|
|
|
for (auto& p : storey_hlr) {
|
|
|
|
|
draw_hlr(drawing_metadata[{p.first, ""}].pln_3d, { p.first, "" });
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-31 11:31:05 +01:00
|
|
|
auto m = resize();
|
|
|
|
|
|
|
|
|
|
// Update the paper space scale matrices
|
|
|
|
|
for (auto& p : paths) {
|
|
|
|
|
drawing_metadata[p.first].matrix_3 = m;
|
|
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
|
2020-12-30 14:25:08 +01:00
|
|
|
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)));
|
2021-08-15 20:43:55 +02:00
|
|
|
deferred_section_data_->push_back(vertical_section{ pln , "Section North South", true });
|
2020-12-30 14:25:08 +01:00
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
gp_Pln pln(gp_Ax3(
|
|
|
|
|
gp_Pnt((xmin + xmax) / 2., (ymin + ymax) / -2., 0.),
|
|
|
|
|
gp_Dir(0, -1, 0),
|
2021-08-17 13:50:11 +02:00
|
|
|
gp_Dir(1, 0, 0)));
|
2020-12-30 14:25:08 +01:00
|
|
|
deferred_section_data_->push_back(vertical_section{ pln , "Section East West", true });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (auto_elevation_) {
|
|
|
|
|
{
|
|
|
|
|
gp_Pln pln(gp_Ax3(
|
2021-08-17 15:58:35 +02:00
|
|
|
gp_Pnt(0., -(ymin - 0.1), 0.),
|
2021-01-02 12:34:10 +01:00
|
|
|
gp_Dir(0, 1, 0),
|
2021-08-15 20:43:55 +02:00
|
|
|
gp_Dir(-1, 0, 0)));
|
2020-12-30 14:25:08 +01:00
|
|
|
deferred_section_data_->push_back(vertical_section{ pln , "Elevation South", true });
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
gp_Pln pln(gp_Ax3(
|
2021-08-17 15:58:35 +02:00
|
|
|
gp_Pnt(xmax + 0.1, 0., 0.),
|
2021-01-02 12:34:10 +01:00
|
|
|
gp_Dir(1, 0, 0),
|
|
|
|
|
gp_Dir(0, 1, 0)));
|
2020-12-30 14:25:08 +01:00
|
|
|
deferred_section_data_->push_back(vertical_section{ pln , "Elevation East", true });
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
gp_Pln pln(gp_Ax3(
|
2021-08-17 15:58:35 +02:00
|
|
|
gp_Pnt(0., -(ymax + 0.1), 0.),
|
2021-01-02 12:34:10 +01:00
|
|
|
gp_Dir(0, -1, 0),
|
2021-08-15 20:43:55 +02:00
|
|
|
gp_Dir(1, 0, 0)));
|
2020-12-30 14:25:08 +01:00
|
|
|
deferred_section_data_->push_back(vertical_section{ pln , "Elevation North", true });
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
gp_Pln pln(gp_Ax3(
|
2021-08-17 15:58:35 +02:00
|
|
|
gp_Pnt(xmin - 0.1, 0., 0.),
|
2021-01-02 12:34:10 +01:00
|
|
|
gp_Dir(-1, 0, 0),
|
|
|
|
|
gp_Dir(0, -1, 0)));
|
2020-12-30 14:25:08 +01:00
|
|
|
deferred_section_data_->push_back(vertical_section{ pln , "Elevation West", true });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resetScale();
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) {
|
2020-09-20 15:37:47 +02:00
|
|
|
|
|
|
|
|
// Draw door arcs only on floor plans.
|
|
|
|
|
is_floor_plan_ = false;
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
for (auto& sd : *deferred_section_data_) {
|
2021-01-27 22:34:19 +01:00
|
|
|
bool use_hlr = true;
|
2021-03-05 21:50:32 +01:00
|
|
|
const gp_Pln* pln = nullptr;
|
2020-09-20 15:08:52 +02:00
|
|
|
std::string drawing_name;
|
|
|
|
|
if (sd.which() == 2) {
|
|
|
|
|
const auto& section = boost::get<vertical_section>(sd);
|
|
|
|
|
use_hlr = section.with_projection;
|
|
|
|
|
drawing_name = section.name;
|
2021-03-05 21:50:32 +01:00
|
|
|
pln = §ion.plane;
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-29 13:08:22 +02:00
|
|
|
// @todo do we have always have pln here?
|
|
|
|
|
if (use_hlr && pln) {
|
2023-08-30 10:57:33 +02:00
|
|
|
hlr = new hlr_t(use_prefiltering_, use_hlr_poly_, segment_projection_, *pln);
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-30 14:25:08 +01:00
|
|
|
section_data_ = std::vector<section_data>{ sd };
|
2020-09-20 15:08:52 +02:00
|
|
|
for (auto& e : element_buffer_) {
|
|
|
|
|
write(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (use_hlr) {
|
|
|
|
|
const auto& section = boost::get<vertical_section>(sd);
|
2021-01-27 22:34:19 +01:00
|
|
|
const auto& ax = section.plane.Position();
|
2021-01-02 12:34:10 +01:00
|
|
|
|
2021-01-27 22:34:19 +01:00
|
|
|
draw_hlr(ax, { nullptr, drawing_name });
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-24 14:18:53 +02:00
|
|
|
addTextAnnotations({ nullptr, drawing_name });
|
|
|
|
|
|
2021-08-17 13:49:49 +02:00
|
|
|
if (file && storey_height_display_ != SH_NONE && pln && std::abs(pln->Position().Direction().Z()) < 1.e-5) {
|
|
|
|
|
auto storeys = file->instances_by_type("IfcBuildingStorey");
|
2021-03-05 21:50:32 +01:00
|
|
|
if (storeys) {
|
|
|
|
|
const double lu = file->getUnit("LENGTHUNIT").second;
|
|
|
|
|
for (auto& s : *storeys) {
|
|
|
|
|
auto storey = (IfcUtil::IfcBaseEntity*) s;
|
|
|
|
|
auto a = storey->get("Elevation");
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!a.isNull()) {
|
|
|
|
|
double elev = a;
|
2021-03-05 21:50:32 +01:00
|
|
|
elev *= lu;
|
|
|
|
|
auto svg_name = nameElement(storey);
|
|
|
|
|
|
2021-03-07 12:07:40 +01:00
|
|
|
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());
|
2021-03-05 21:50:32 +01:00
|
|
|
|
|
|
|
|
double x0, y0, z0, x1, y1, z1;
|
|
|
|
|
bnd_.Get(x0, y0, z0, x1, y1, z1);
|
|
|
|
|
|
2021-08-17 15:58:35 +02:00
|
|
|
// @todo this is a hack in order to get the auto elevations (which are 0.1 offset from
|
|
|
|
|
// the global bounding box) to include the storey height symbols.
|
|
|
|
|
x0 -= 0.2;
|
|
|
|
|
y0 -= 0.2;
|
|
|
|
|
z0 -= 0.2;
|
|
|
|
|
|
|
|
|
|
x1 += 0.2;
|
|
|
|
|
y1 += 0.2;
|
|
|
|
|
z1 += 0.2;
|
|
|
|
|
|
2021-03-28 21:38:29 +02:00
|
|
|
const double shll = storey_height_line_length_.get_value_or(2.);
|
|
|
|
|
|
|
|
|
|
BRepBuilderAPI_MakeFace mf(elev_pln, x0 - shll, x1 + shll, y0 - shll, y1 + shll);
|
2021-03-05 21:50:32 +01:00
|
|
|
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");
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!a2.isNull()) {
|
|
|
|
|
name = (std::string) a2;
|
2021-03-05 21:50:32 +01:00
|
|
|
}
|
|
|
|
|
write(geometry_data{
|
2021-03-28 16:55:52 +02:00
|
|
|
C,{boost::none},trsf,storey,storey,elev,name,nameElement(storey)
|
2021-03-09 09:52:47 +01:00
|
|
|
});
|
2021-03-05 21:50:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-31 11:31:05 +01:00
|
|
|
auto m3 = resize();
|
|
|
|
|
|
|
|
|
|
auto k = std::make_pair(nullptr, drawing_name);
|
|
|
|
|
drawing_metadata[k].matrix_3 = m3;
|
|
|
|
|
|
2020-12-30 14:25:08 +01:00
|
|
|
resetScale();
|
2020-09-20 15:08:52 +02:00
|
|
|
|
2023-06-29 13:08:22 +02:00
|
|
|
delete hlr;
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::multimap<drawing_key, path_object, storey_sorter>::const_iterator it;
|
|
|
|
|
|
|
|
|
|
boost::optional<drawing_key> previous;
|
2015-07-26 14:26:13 +00:00
|
|
|
for (it = paths.begin(); it != paths.end(); ++it) {
|
2020-09-20 15:08:52 +02:00
|
|
|
if (!previous || it->first != *previous) {
|
|
|
|
|
if (previous) {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << " </g>\n";
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
std::ostringstream oss;
|
2020-09-20 15:08:52 +02:00
|
|
|
if (it->first.first) {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << " <g " << nameElement(it->first.first) << " " << writeMetadata(drawing_metadata[it->first]) << ">\n";
|
2020-09-20 15:08:52 +02:00
|
|
|
} else {
|
2020-12-30 14:25:08 +01:00
|
|
|
auto n = it->first.second;
|
|
|
|
|
IfcUtil::escape_xml(n);
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << " <g " << namespace_prefix_ << "name=\"" << n << "\" class=\"section\" " << writeMetadata(drawing_metadata[it->first]) << ">\n";
|
2020-09-20 15:08:52 +02:00
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
2021-08-08 20:20:23 +02:00
|
|
|
|
2021-08-08 21:16:30 +02:00
|
|
|
previous = it->first;
|
|
|
|
|
|
2021-08-08 20:20:23 +02:00
|
|
|
if (it->second.second.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << " <g " << it->second.first << ">\n";
|
2015-07-26 14:26:13 +00:00
|
|
|
std::vector<util::string_buffer>::const_iterator jt;
|
|
|
|
|
for (jt = it->second.second.begin(); jt != it->second.second.end(); ++jt) {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << jt->str();
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << " </g>\n";
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-20 15:08:52 +02:00
|
|
|
if (previous) {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << " </g>\n";
|
2017-10-23 13:58:07 +02:00
|
|
|
}
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << "</svg>" << std::endl;
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SvgSerializer::writeHeader() {
|
2021-04-18 15:03:28 +02:00
|
|
|
// 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() {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
|
2020-12-30 14:44:43 +01:00
|
|
|
if (use_namespace_) {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << " xmlns:ifc=\"http://www.ifcopenshell.org/ns\"";
|
2020-12-30 14:44:43 +01:00
|
|
|
}
|
2021-03-28 12:48:20 +02:00
|
|
|
if (scale_ && size_) {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream <<
|
2021-03-28 12:48:20 +02:00
|
|
|
" width=\"" << size_->first << "mm\""
|
|
|
|
|
" height=\"" << size_->second << "mm\"" <<
|
|
|
|
|
" viewBox=\"0 0 " << size_->first << " " << size_->second << "\"";
|
2020-08-25 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream << ">\n"
|
2020-08-16 11:41:40 +02:00
|
|
|
" <defs>\n"
|
|
|
|
|
" <marker id=\"arrowend\" markerWidth=\"10\" markerHeight=\"7\" refX=\"10\" refY=\"3.5\" orient=\"auto\">\n"
|
|
|
|
|
" <polygon points=\"0 0, 10 3.5, 0 7\" />\n"
|
|
|
|
|
" </marker>\n"
|
|
|
|
|
" <marker id=\"arrowstart\" markerWidth=\"10\" markerHeight=\"7\" refX=\"0\" refY=\"3.5\" orient=\"auto\">\n"
|
|
|
|
|
" <polygon points=\"10 0, 0 3.5, 10 7\" />\n"
|
|
|
|
|
" </marker>\n"
|
2021-08-08 18:10:19 +02:00
|
|
|
" </defs>\n";
|
2020-09-27 14:28:07 +02:00
|
|
|
|
2021-08-08 18:10:19 +02:00
|
|
|
if (!no_css_) {
|
2021-07-11 15:01:31 +02:00
|
|
|
svg_file.stream <<
|
2021-08-08 18:10:19 +02:00
|
|
|
" <style type=\"text/css\" >\n"
|
|
|
|
|
" <![CDATA[\n"
|
2022-02-27 12:55:11 +01:00
|
|
|
" .cut path {\n"
|
2021-08-08 18:10:19 +02:00
|
|
|
" stroke: #222222;\n"
|
|
|
|
|
" fill: #444444;\n"
|
2021-11-05 11:21:19 +01:00
|
|
|
" fill-rule: evenodd;\n"
|
2021-08-08 18:10:19 +02:00
|
|
|
" }\n"
|
2022-02-27 12:55:11 +01:00
|
|
|
" .projection path {\n"
|
|
|
|
|
" stroke: #222222;\n"
|
|
|
|
|
" fill: none;\n"
|
|
|
|
|
" stroke-opacity: 0.6;\n"
|
|
|
|
|
" }\n"
|
2021-08-08 18:10:19 +02:00
|
|
|
" .IfcDoor path,\n"
|
|
|
|
|
" .Symbol path {\n"
|
|
|
|
|
" fill: none;\n"
|
|
|
|
|
" }\n"
|
|
|
|
|
" .Symbol path {\n"
|
|
|
|
|
" stroke-width: 0.5px;\n"
|
|
|
|
|
" }\n"
|
|
|
|
|
" .IfcSpace path {\n"
|
|
|
|
|
" fill-opacity: .2;\n"
|
|
|
|
|
" }\n"
|
|
|
|
|
" .Dimension path {\n"
|
|
|
|
|
" marker-end: url(#arrowend);\n"
|
|
|
|
|
" marker-start: url(#arrowstart);\n"
|
|
|
|
|
" }\n";
|
|
|
|
|
|
|
|
|
|
if (scale_) {
|
|
|
|
|
// previously:
|
|
|
|
|
// (pt) (px) (in) (mm)
|
|
|
|
|
// approx 12 / 0.75 / 96 * 25.4
|
|
|
|
|
|
2021-08-10 14:30:07 +02:00
|
|
|
svg_file.stream <<
|
2021-08-08 18:10:19 +02:00
|
|
|
" text {\n"
|
|
|
|
|
" font-size: 2;\n" // (reduced to two).
|
|
|
|
|
" }\n"
|
|
|
|
|
" path {\n"
|
|
|
|
|
" stroke-width: 0.3;\n"
|
|
|
|
|
" }\n";
|
|
|
|
|
}
|
2020-08-25 15:01:07 +02:00
|
|
|
|
2021-08-10 14:30:07 +02:00
|
|
|
svg_file.stream <<
|
2021-08-08 18:10:19 +02:00
|
|
|
" ]]>\n"
|
|
|
|
|
" </style>\n";
|
|
|
|
|
}
|
2015-07-26 14:26:13 +00:00
|
|
|
}
|
2015-09-06 14:50:07 +02:00
|
|
|
|
2020-04-01 12:19:56 +02:00
|
|
|
namespace {
|
|
|
|
|
std::string nameElement_(const std::vector<std::pair<std::string, std::string> >& attrs) {
|
|
|
|
|
std::ostringstream oss;
|
2020-09-20 15:08:52 +02:00
|
|
|
for (auto& a : attrs) {
|
|
|
|
|
// @todo while we're at it might as well implement escaping
|
|
|
|
|
oss << a.first << "=\"" << a.second << "\" ";
|
|
|
|
|
}
|
|
|
|
|
return oss.str();
|
2020-04-01 12:19:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-11 15:00:59 +02:00
|
|
|
std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element* elem) {
|
2020-12-30 14:25:08 +01:00
|
|
|
auto n = elem->name();
|
|
|
|
|
IfcUtil::escape_xml(n);
|
|
|
|
|
|
2020-07-23 11:27:38 +02:00
|
|
|
return nameElement_({
|
|
|
|
|
{"id", with_section_heights_from_storey_ ? object_id(storey, elem) : GeometrySerializer::object_id(elem)},
|
|
|
|
|
{"class", elem->type()},
|
2020-12-30 14:44:43 +01:00
|
|
|
{namespace_prefix_ + "name", n},
|
|
|
|
|
{namespace_prefix_ + "guid", elem->guid()}
|
2020-09-20 15:08:52 +02:00
|
|
|
});
|
2020-07-23 11:27:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string SvgSerializer::idElement(const IfcUtil::IfcBaseEntity* elem) {
|
|
|
|
|
const std::string type = elem->declaration().is("IfcBuildingStorey") ? "storey" : "product";
|
|
|
|
|
const std::string name =
|
2023-11-15 10:32:20 +01:00
|
|
|
(settings().get<ifcopenshell::geometry::settings::UseElementGuids>().get()
|
2024-08-23 20:29:07 +02:00
|
|
|
? static_cast<std::string>(elem->get("GlobalId"))
|
|
|
|
|
: ((settings().get<ifcopenshell::geometry::settings::UseElementNames>().get() && !elem->get("Name").isNull()))
|
|
|
|
|
? static_cast<std::string>(elem->get("Name"))
|
2023-11-15 10:32:20 +01:00
|
|
|
: (settings().get<ifcopenshell::geometry::settings::UseElementStepIds>().get())
|
2024-08-23 20:29:07 +02:00
|
|
|
? ("id-" + boost::lexical_cast<std::string>(elem->id()))
|
|
|
|
|
: IfcParse::IfcGlobalId(elem->get("GlobalId")).formatted());
|
2020-07-23 11:27:38 +02:00
|
|
|
return type + "-" + name;
|
2015-09-06 14:50:07 +02:00
|
|
|
}
|
|
|
|
|
|
2017-12-20 12:40:15 +01:00
|
|
|
std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* elem) {
|
2017-10-23 14:17:54 +02:00
|
|
|
if (elem == 0) { return ""; }
|
2020-04-01 12:19:56 +02:00
|
|
|
|
|
|
|
|
const std::string& entity = elem->declaration().name();
|
2020-05-10 11:48:39 +02:00
|
|
|
std::string ifc_name;
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!elem->get("Name").isNull()) {
|
|
|
|
|
ifc_name = (std::string) elem->get("Name");
|
2020-12-30 14:25:08 +01:00
|
|
|
IfcUtil::escape_xml(ifc_name);
|
2020-05-10 11:48:39 +02:00
|
|
|
}
|
2017-12-20 12:40:15 +01:00
|
|
|
|
2020-07-23 11:27:38 +02:00
|
|
|
return nameElement_({
|
2020-09-20 15:08:52 +02:00
|
|
|
{"id", idElement(elem)},
|
|
|
|
|
{"class", entity},
|
2020-12-30 14:44:43 +01:00
|
|
|
{namespace_prefix_ + "name", ifc_name},
|
2024-08-23 20:29:07 +02:00
|
|
|
{namespace_prefix_ + "guid", elem->get("GlobalId")}
|
2020-09-20 15:08:52 +02:00
|
|
|
});
|
2016-03-10 11:35:32 +02:00
|
|
|
}
|
2017-10-23 14:17:54 +02:00
|
|
|
|
|
|
|
|
void SvgSerializer::setFile(IfcParse::IfcFile* f) {
|
|
|
|
|
file = f;
|
2018-12-28 16:02:03 +01:00
|
|
|
|
|
|
|
|
auto storeys = f->instances_by_type("IfcBuildingStorey");
|
2017-10-23 14:17:54 +02:00
|
|
|
if (!storeys || storeys->size() == 0) {
|
2023-11-15 10:32:20 +01:00
|
|
|
auto mapping = ifcopenshell::geometry::impl::mapping_implementations().construct(file, geometry_settings_);
|
2020-09-20 15:08:52 +02:00
|
|
|
|
2018-12-28 16:02:03 +01:00
|
|
|
std::vector<const IfcParse::declaration*> to_derive_from;
|
|
|
|
|
to_derive_from.push_back(f->schema()->declaration_by_name("IfcBuilding"));
|
|
|
|
|
to_derive_from.push_back(f->schema()->declaration_by_name("IfcSite"));
|
|
|
|
|
for (auto it = to_derive_from.begin(); it != to_derive_from.end(); ++it) {
|
2021-07-29 14:54:51 +02:00
|
|
|
aggregate_of_instance::ptr insts = f->instances_by_type(*it);
|
2018-12-28 16:02:03 +01:00
|
|
|
if (insts) {
|
|
|
|
|
for (auto jt = insts->begin(); jt != insts->end(); ++jt) {
|
|
|
|
|
IfcUtil::IfcBaseEntity* product = (IfcUtil::IfcBaseEntity*) *jt;
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!product->get("ObjectPlacement").isNull()) {
|
|
|
|
|
auto item = mapping->map(product->get("ObjectPlacement"));
|
2023-07-27 16:42:06 +08:00
|
|
|
auto matrix = ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::matrix4>(item);
|
2017-10-23 14:17:54 +02:00
|
|
|
gp_Trsf trsf;
|
2023-03-21 20:15:01 +01:00
|
|
|
if (matrix) {
|
|
|
|
|
// @todo shouldn't this take into account configurable section height?
|
2024-08-23 20:43:27 +02:00
|
|
|
setSectionHeight(matrix->translation_part()(2) + 1.);
|
2023-07-27 16:42:06 +08:00
|
|
|
#ifdef TAXONOMY_USE_NAKED_PTR
|
2023-03-21 20:15:01 +01:00
|
|
|
delete matrix;
|
2023-07-27 16:42:06 +08:00
|
|
|
#endif
|
2017-12-11 15:46:17 +01:00
|
|
|
Logger::Warning("No building storeys encountered, used for reference:", product);
|
2017-10-23 14:17:54 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
delete mapping;
|
|
|
|
|
|
2018-12-29 12:34:32 +01:00
|
|
|
Logger::Warning("No building storeys encountered, output might be invalid or missing");
|
2017-11-08 16:37:57 +01:00
|
|
|
}
|
|
|
|
|
}
|
2020-07-11 13:39:09 +02:00
|
|
|
|
2023-03-21 20:15:01 +01:00
|
|
|
void SvgSerializer::setSectionHeight(double h, const IfcUtil::IfcBaseEntity* storey) {
|
2020-09-20 15:08:52 +02:00
|
|
|
section_data_.emplace();
|
|
|
|
|
section_data_->push_back(horizontal_plan{ storey, h, 0., std::numeric_limits<double>::infinity() });
|
2020-07-11 13:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SvgSerializer::setSectionHeightsFromStoreys(double offset) {
|
2021-08-17 13:49:49 +02:00
|
|
|
if (!file) {
|
|
|
|
|
Logger::Error("No file specified");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-07-23 11:27:38 +02:00
|
|
|
with_section_heights_from_storey_ = true;
|
2020-09-20 15:08:52 +02:00
|
|
|
section_data_.emplace();
|
2020-07-11 13:39:09 +02:00
|
|
|
auto storeys = file->instances_by_type("IfcBuildingStorey");
|
|
|
|
|
const double lu = file->getUnit("LENGTHUNIT").second;
|
|
|
|
|
if (storeys && storeys->size() > 0) {
|
|
|
|
|
for (auto& s : *storeys) {
|
|
|
|
|
auto attr_value = ((IfcUtil::IfcBaseEntity*)s)->get("Elevation");
|
2024-08-23 20:29:07 +02:00
|
|
|
if (!attr_value.isNull()) {
|
2020-07-11 13:39:09 +02:00
|
|
|
double elev;
|
|
|
|
|
try {
|
2024-08-23 20:29:07 +02:00
|
|
|
elev = attr_value;
|
2020-07-11 13:39:09 +02:00
|
|
|
} catch (std::exception& e) {
|
|
|
|
|
Logger::Error(e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-20 15:08:52 +02:00
|
|
|
if (!section_data_->empty()) {
|
|
|
|
|
boost::get<horizontal_plan>(section_data_->back()).next_elevation = elev * lu;
|
|
|
|
|
}
|
|
|
|
|
section_data_->push_back(horizontal_plan{ (IfcUtil::IfcBaseEntity*)s, elev * lu, offset, std::numeric_limits<double>::infinity() });
|
2020-07-11 13:39:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-09-20 15:08:52 +02:00
|
|
|
section_data_->push_back(horizontal_plan_at_element{});
|
2020-07-11 13:39:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-31 11:31:05 +01:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
std::string array_to_string(double v) {
|
|
|
|
|
return std::to_string(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
std::string array_to_string(const T& v) {
|
|
|
|
|
return "[" + std::accumulate(
|
2021-01-02 13:08:01 +01:00
|
|
|
v.begin() + 1, v.end(),
|
2020-12-31 11:31:05 +01:00
|
|
|
array_to_string(v.front()),
|
2021-01-02 13:08:01 +01:00
|
|
|
[](const std::string& accum, decltype(*v.cbegin())& item) {
|
2020-12-31 11:31:05 +01:00
|
|
|
return accum + "," + array_to_string(item);
|
|
|
|
|
}) + "]";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string SvgSerializer::writeMetadata(const drawing_meta& m) {
|
|
|
|
|
gp_Trsf trsf;
|
|
|
|
|
trsf.SetTransformation(m.pln_3d.Position(), gp::XOY());
|
2025-04-17 15:25:35 +02:00
|
|
|
NCollection_Mat4<double> mat4;
|
|
|
|
|
trsf.GetMat4(mat4);
|
|
|
|
|
const auto* d = mat4.GetData();
|
|
|
|
|
std::array<std::array<double, 4>, 4> m4 = { {
|
|
|
|
|
{{ d[0], d[4], d[8], d[12] }},
|
|
|
|
|
{{ d[1], d[5], d[9], d[13] }},
|
|
|
|
|
{{ d[2], d[6], d[10], d[14] }},
|
|
|
|
|
{{ d[3], d[7], d[11], d[15] }}
|
|
|
|
|
} };
|
2020-12-31 11:31:05 +01:00
|
|
|
return namespace_prefix_ + "plane=\""+ array_to_string(m4) +"\" " +
|
|
|
|
|
namespace_prefix_ + "matrix3=\"" + array_to_string(m.matrix_3) + "\"";
|
2023-03-22 11:17:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|