/******************************************************************************** * * * Copyright 2015 IfcOpenShell and ROOT B.V. * * * * This file is part of IfcOpenShell. * * * * IfcOpenShell is free software: you can redistribute it and/or modify * * it under the terms of the Lesser GNU General Public License as published by * * the Free Software Foundation, either version 3.0 of the License, or * * (at your option) any later version. * * * * IfcOpenShell is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * Lesser GNU General Public License for more details. * * * * You should have received a copy of the Lesser GNU General Public License * * along with this program. If not, see . * * * ********************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../ifcparse/IfcGlobalId.h" #include "SvgSerializer.h" const double PI2 = M_PI * 2.; bool SvgSerializer::ready() { return true; } void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) { /* ShapeFix_Wire fix; Handle(ShapeExtend_WireData) data = new ShapeExtend_WireData; for (TopExp_Explorer edges(result, TopAbs_EDGE); edges.More(); edges.Next()) { data->Add(edges.Current()); } fix.Load(data); fix.FixReorder(); fix.FixConnected(); const TopoDS_Wire fixed_wire = fix.Wire(); */ bool first = true; util::string_buffer path; for (TopExp_Explorer edges(wire, TopAbs_EDGE); edges.More(); edges.Next()) { const TopoDS_Edge& edge = TopoDS::Edge(edges.Current()); double u1, u2; Handle(Geom_Curve) curve = BRep_Tool::Curve(edge, u1, u2); Handle(Geom2d_Curve) curve2d; if (curve.IsNull()) { TopLoc_Location loc; Handle_Geom_Surface surf; BRep_Tool::CurveOnSurface(edge, curve2d, surf, loc, u1, u2); if (curve2d.IsNull()) { Logger::Error("Failed to obtain 2d and 3d curve from edge"); continue; } Handle(Standard_Type) sty = surf->DynamicType(); if (sty != STANDARD_TYPE(Geom_Plane)) { Logger::Error("Non-planar p-curves are not supported by this serializer"); continue; } gp_Pln pln = Handle(Geom_Plane)::DownCast(surf)->Pln(); curve = GeomAPI::To3d(curve2d, pln); } Handle(Standard_Type) ty = curve->DynamicType(); bool conical = (ty == STANDARD_TYPE(Geom_Circle) || ty == STANDARD_TYPE(Geom_Ellipse)); // TODO: ALMOST_THE_SAME utilities in separate header bool closed = fabs((u1 + PI2) - u2) < 1.e-9; if (conical && closed) { if (first) { if (ty == STANDARD_TYPE(Geom_Circle)) { Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(curve); double r = circle->Radius(); gp_Circ c = circle->Circ(); gp_Pnt center = c.Location(); path.add(" Elips(); gp_Pnt center = e.Location(); // Write the ellipse with major radius along X axis: path.add(" (id " << p.first << ")"; Logger::Warning(ss.str()); } } const bool reversed = edge.Orientation() == TopAbs_REVERSED; gp_Pnt p1, p2; curve->D0(u1, p1); curve->D0(u2, p2); if (reversed) { std::swap(p1, p2); } if (first) { path.add(" Position().Axis().Direction().Z() < 0; double r1, r2; bool larger_arc_segment = (fmod(u2 - u1 + PI2, PI2) > M_PI); bool positive_direction = (u2 > u1); if (mirrored != reversed) { // In case the local coordinate system is mirrored // the direction is reversed. positive_direction = !positive_direction; } gp_Pnt center; if (ty == STANDARD_TYPE(Geom_Circle)) { Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(curve); r1 = r2 = circle->Radius(); center = circle->Location(); } else { Handle(Geom_Ellipse) ellipse = Handle(Geom_Ellipse)::DownCast(curve); r1 = ellipse->MajorRadius(); r2 = ellipse->MinorRadius(); center = ellipse->Location(); } // Make sure the arc segment is entirely inside bounding box: growBoundingBox(center.X() - r1, center.Y() - r1); growBoundingBox(center.X() + r1, center.Y() + r1); // Calculate the angle between 2d vecs to have signed result const gp_Dir& d = conic->Position().XDirection(); const gp_Dir2d d2(d.X(), d.Y()); const double ang = d2.Angle(gp::DX2d()); // Write radii path.add(" A"); addSizeComponent(path.add(r1)); path.add(","); addSizeComponent(path.add(r2)); // Write X-axis rotation { std::stringstream ss; ss << " " << ang << " "; path.add(ss.str()); } // Write large-arc-flag and sweep-flag path.add(std::string(1, '0'+static_cast(larger_arc_segment))); path.add(","); path.add(std::string(1, '0'+static_cast(positive_direction))); path.add(" "); // Write arc end point xcoords.push_back(path.add(p2.X())); path.add(","); ycoords.push_back(path.add(p2.Y())); } else if (ty != STANDARD_TYPE(Geom_Line)) { BRepAdaptor_Curve crv(edge); GCPnts_QuasiUniformDeflection tessellater(crv, settings().deflection_tolerance()); // NB: Start at 2: 1-based and skip the first point, assume it coincides with p1. for (int i = 2; i <= tessellater.NbPoints(); ++i) { gp_Pnt pi = tessellater.Value(i); path.add(" L"); xcoords.push_back(path.add(pi.X())); path.add(","); ycoords.push_back(path.add(pi.Y())); growBoundingBox(pi.X(), pi.Y()); } } else { // Either a Geom_Line or something unimplemented, // drawn as a straight line segment. path.add(" L"); xcoords.push_back(path.add(p2.X())); path.add(","); ycoords.push_back(path.add(p2.Y())); } first = false; } path.add("\"/>\n"); p.second.push_back(path); } SvgSerializer::path_object& SvgSerializer::start_path(IfcUtil::IfcBaseEntity* storey, const std::string& id) { auto key = std::make_pair(std::make_pair(storey, ""), path_object()); SvgSerializer::path_object& p = paths.insert(key)->second; p.first = id; return p; } SvgSerializer::path_object& SvgSerializer::start_path(const std::string& drawing_name, const std::string& id) { auto key = std::make_pair(std::make_pair(nullptr, drawing_name), path_object()); SvgSerializer::path_object& p = paths.insert(key)->second; p.first = id; return p; } namespace { boost::optional> storey_elevation_from_element(const IfcGeom::BRepElement* o) { for (const auto& p : o->parents()) { if (p->type() == "IfcBuildingStorey") { try { const IfcGeom::ElementSettings& settings = o->geometry().settings(); double e = *p->product()->get("Elevation"); double storey_elevation = e * settings.unit_magnitude(); return std::make_pair(p->product(), storey_elevation); } catch (...) { continue; } break; } } return boost::none; } boost::optional edge_from_compound(TopoDS_Shape& compound) { TopoDS_Iterator it(compound); if (it.More()) { TopoDS_Shape wire = it.Value(); it.Next(); if (!it.More() && wire.ShapeType() == TopAbs_WIRE) { TopoDS_Iterator jt(wire); if (jt.More()) { TopoDS_Shape edge = jt.Value(); jt.Next(); if (!jt.More() && edge.ShapeType() == TopAbs_EDGE) { return TopoDS::Edge(edge); } } } } return boost::none; } } void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { boost::optional object_type; if (!brep_obj->product()->get("ObjectType")->isNull()) { object_type = static_cast(*brep_obj->product()->get("ObjectType")); } TopoDS_Shape compound_local = brep_obj->geometry().as_compound(); const gp_Trsf& trsf = brep_obj->transformation().data(); const bool is_section = (section_ref_ && object_type && *section_ref_ == *object_type); const bool is_elevation = (elevation_ref_ && object_type && *elevation_ref_ == *object_type); if (is_section || is_elevation) { auto e = edge_from_compound(compound_local); if (e) { TopoDS_Edge global_edge = TopoDS::Edge(e->Moved(trsf)); double u0, u1; auto crv = BRep_Tool::Curve(global_edge, u0, u1); if (crv->DynamicType() == STANDARD_TYPE(Geom_Line)) { gp_Pnt P; gp_Vec V; crv->D1((u0 + u1) / 2., P, V); auto N = gp::DZ().Crossed(V); gp_Pln pln(gp_Ax3(P, N, V)); if (!deferred_section_data_) { deferred_section_data_.emplace(); } std::string name = brep_obj->name(); if (name.empty()) { name = boost::lexical_cast(brep_obj->id()); } if (is_section) { deferred_section_data_->push_back(vertical_section{ pln , "Section " + name, false }); } if (is_elevation) { deferred_section_data_->push_back(vertical_section{ pln , "Elevation " + name, true }); } } } return; } auto p = storey_elevation_from_element(brep_obj); IfcUtil::IfcBaseEntity* storey = p ? p->first : nullptr; double elev = p ? p->second : std::numeric_limits::quiet_NaN(); geometry_data data{ compound_local, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) }; if (buffer_elements_) { element_buffer_.push_back(data); } write(data); } void SvgSerializer::write(const geometry_data& data) { std::vector section_heights_storage; const std::vector* section_heights_used = §ion_heights_storage; if (section_data_) { section_heights_used = section_data_.get_ptr(); } else { if (data.storey) { section_heights_storage.push_back(horizontal_plan{ data.storey, data.storey_elevation, +1. }); } else { Logger::Warning("No global section height and unable to determine building storey for:", data.product); return; } } BRepBuilderAPI_Transform make_transform_global(data.compound_local, data.trsf, true); make_transform_global.Build(); // (When determinant < 0, copy is implied and the input is not mutated.) auto compound_unmirrored = make_transform_global.Shape(); // SVG has a coordinate system with the origin in the *upper*-left corner // therefore we mirror the shape along the XZ-plane. gp_Trsf trsf_mirror; trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); BRepBuilderAPI_Transform make_transform_mirror(compound_unmirrored, trsf_mirror, true); make_transform_mirror.Build(); // (When determinant < 0, copy is implied and the input is not mutated.) auto compound = make_transform_mirror.Shape(); TopoDS_Wire annotation; if (is_floor_plan_ && draw_door_arcs_ && data.product->declaration().is("IfcDoor")) { boost::optional operation_type; try { IfcEntityList::ptr rels; if (data.product->declaration().schema()->name() == "IFC2X3") { rels = data.product->get_inverse("IsDefinedBy"); } else { // Damn you, IFC rels = data.product->get_inverse("IsTypedBy"); } for (auto& rel : *rels) { if (rel->declaration().name() == "IfcRelDefinesByType") { IfcUtil::IfcBaseClass* ty = *((IfcUtil::IfcBaseEntity*)rel)->get("RelatingType"); const std::string& ty_entity_name = ty->declaration().name(); // Damn you, IFC if (ty_entity_name == "IfcDoorStyle" || ty_entity_name == "IfcDoorType") { operation_type = *((IfcUtil::IfcBaseEntity*)ty)->get("OperationType"); } } } } catch (std::exception& e) { Logger::Error(e); } if (operation_type && (*operation_type == "SINGLE_SWING_LEFT") || (*operation_type == "SINGLE_SWING_RIGHT")) { const bool is_left = *operation_type == "SINGLE_SWING_LEFT"; Bnd_Box bb; BRepBndLib::Add(data.compound_local, bb); if (bb.IsVoid()) { return; } double x1, y1, z1, x2, y2, z2; bb.Get(x1, y1, z1, x2, y2, z2); double width = x2 - x1; double y12 = (y1 + y2) / 2.; gp_Pnt center(is_left ? x1 : x2, y12, 0); gp_Pnt p1(is_left ? x2 : x1, y12, 0); gp_Pnt p2(is_left ? x1 : x2, y12 + width, 0); if (!is_left) { // circles are counter clockwise, so for swing right // we need to reverse the points in order to get the // shorter part of the circle arc. std::swap(p1, p2); } BRepBuilderAPI_MakeEdge me(gp_Circ(gp_Ax2(center, gp::DZ()), width), p1, p2); if (me.IsDone()) { BRep_Builder B; B.MakeWire(annotation); auto edge = me.Edge(); make_transform_global.Perform(edge, true); auto edge_global = make_transform_global.Shape(); make_transform_mirror.Perform(edge_global, true); auto edge_global_mirrored = make_transform_mirror.Shape(); center.Transform(data.trsf); p1.Transform(data.trsf); p2.Transform(data.trsf); center.Transform(trsf_mirror); p1.Transform(trsf_mirror); p2.Transform(trsf_mirror); if (!is_left) { // For the purpose of the SVG serializer we do not a topologically // connected wire. So adding disconnected edges is fine. B.Add(annotation, BRepBuilderAPI_MakeEdge(center, p1).Edge()); } B.Add(annotation, edge_global_mirrored); if (is_left) { B.Add(annotation, BRepBuilderAPI_MakeEdge(p2, center).Edge()); } } } } bool emitted = false; for (auto sit = section_heights_used->begin(); sit != section_heights_used->end(); ++sit) { const auto& variant = *sit; // Elev + offset double cut_z = std::numeric_limits::infinity(); // Elev .. Elev(next) std::pair range; gp_Vec projection_direction; IfcUtil::IfcBaseEntity* storey = nullptr; std::string drawing_name; bool use_hlr = false; // @todo use visitor // horizontal_plan, horizontal_plan_at_element, vertical_section if (variant.which() == 0) { const auto& plan = boost::get(variant); storey = plan.storey; cut_z = plan.elevation + plan.offset; range = { plan.elevation, plan.next_elevation }; if (sit == section_heights_used->begin()) { range.first = -std::numeric_limits::infinity(); } projection_direction = gp::DZ(); } else if (variant.which() == 1) { projection_direction = gp::DZ(); } else if (variant.which() == 2) { const auto& section = boost::get(variant); projection_direction = section.plane.Axis().Direction(); drawing_name = section.name; use_hlr = section.with_projection; } if (use_hlr && hlr) { hlr->Add(compound_unmirrored); } TopoDS_Iterator it(compound); TopoDS_Face largest_closed_wire_face; double largest_closed_wire_area = 0.; path_object* po = nullptr; // Iterate over components of compound to have better chance of matching section edges to closed wires for (; it.More(); it.Next()) { const TopoDS_Shape& subshape = it.Value(); Bnd_Box bb; try { BRepBndLib::Add(it.Value(), bb); } catch (const Standard_Failure&) {} // Empty geometry if (bb.IsVoid()) { continue; } double x1, y1, zmin, x2, y2, zmax; bb.Get(x1, y1, zmin, x2, y2, zmax); // Determine slicing plane z coordinate, priority: // 1) explicitly set global section height // 2) containing building storey elevation + 1m // 3) zmin (from geometry bounding box) + 1m if (variant.which() == 1) { cut_z = zmin + 1.; } gp_Vec bbmin(x1, y1, zmin); gp_Vec bbmax(x2, y2, zmax); auto bbdif = bbmax - bbmin; auto proj = projection_direction ^ bbdif ^ projection_direction; if (data.product->declaration().is("IfcAnnotation") && (proj.Magnitude() > 1.e-5) && zmin >= range.first && zmin <= range.second) { if (po == nullptr) { if (storey) { po = &start_path(storey, data.svg_name); } else { po = &start_path(drawing_name, data.svg_name); } } TopExp_Explorer exp(subshape, TopAbs_EDGE, TopAbs_FACE); for (; exp.More(); exp.Next()) { const auto& e = TopoDS::Edge(exp.Current()); TopoDS_Vertex v0, v1; TopExp::Vertices(e, v0, v1); gp_Pnt p0 = BRep_Tool::Pnt(v0); gp_Pnt p1 = BRep_Tool::Pnt(v1); // @todo should we take the average parameter value instead? gp_XYZ center = (p0.XYZ() + p1.XYZ()) / 2.; BRep_Builder B; TopoDS_Wire W; B.MakeWire(W); B.Add(W, e); write(*po, W); util::string_buffer path; // dominant-baseline="central" is not well supported in IE. // so we add a 0.35 offset to the dy of the tspans path.add(" "); std::vector labels{}; GProp_GProps prop; BRepGProp::LinearProperties(e, prop); const double area = prop.Mass(); std::stringstream ss; ss << std::setprecision(2) << std::fixed << std::showpoint << area; labels.push_back(ss.str() + "m"); for (auto lit = labels.begin(); lit != labels.end(); ++lit) { const auto& l = *lit; double dy = labels.begin() == lit ? 0.35 - (labels.size() - 1.) / 2. : 1.0; // <- dy is relative to the previous text element, so // always 1 for successive spans. path.add("(dy)); path.add("em\">"); path.add(l); path.add(""); } path.add(""); po->second.push_back(path); } continue; } if (subshape.ShapeType() > TopAbs_FACE) { // Except for annotations we only emit solids and surfaces to SVG. emitted = true; continue; } // No intersection with bounding box, fail early if (variant.which() < 2) { if (zmin > cut_z || zmax < cut_z) continue; } emitted = true; if (po == nullptr) { if (storey) { po = &start_path(storey, data.svg_name); } else { po = &start_path(drawing_name, data.svg_name); } } // Create a horizontal cross section 1 meter above the bottom point of the shape gp_Pln pln; if (variant.which() < 2) { pln = gp_Pln(gp_Pnt(0, 0, cut_z), gp::DZ()); } else { const auto& section = boost::get(variant); pln = section.plane; } TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln); if (variant.which() == 2) { gp_Trsf trsf; trsf.SetTransformation(gp::XOY(), pln.Position()); result.Move(trsf); } Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape(); Handle(TopTools_HSequenceOfShape) wires = new TopTools_HSequenceOfShape(); { TopExp_Explorer exp(result, TopAbs_EDGE); for (; exp.More(); exp.Next()) { edges->Append(exp.Current()); } } ShapeAnalysis_FreeBounds::ConnectEdgesToWires(edges, 1e-5, false, wires); gp_Pnt prev; for (int i = 1; i <= wires->Length(); ++i) { const TopoDS_Wire& wire = TopoDS::Wire(wires->Value(i)); if (wire.Closed() && (print_space_names_ || print_space_areas_) && data.product->declaration().is("IfcSpace")) { // we explicitly specify the surface here, to later on // simplify the projection from {x,y,z} to {u, v} because // we know we can simply discard z. BRepBuilderAPI_MakeFace mf(pln, wire); if (mf.IsDone()) { TopoDS_Face f = mf.Face(); GProp_GProps prop; BRepGProp::SurfaceProperties(f, prop); const double area = prop.Mass(); if (area > largest_closed_wire_area) { largest_closed_wire_face = f; largest_closed_wire_area = area; } } } write(*po, wire); } } if (!largest_closed_wire_face.IsNull()) { std::vector points; TopExp_Explorer exp(largest_closed_wire_face, TopAbs_VERTEX); for (; exp.More(); exp.Next()) { if (exp.Current().Orientation() == TopAbs_FORWARD) { const TopoDS_Vertex& v = TopoDS::Vertex(exp.Current()); points.push_back(BRep_Tool::Pnt(v)); } } // we brute force the largest distance between pairs of points where // the center is contained in the face. std::pair furthest_points = { nullptr, nullptr }; double furthest_points_distance = 0.; boost::optional center_point; BRepTopAdaptor_FClass2d fcls(largest_closed_wire_face, BRep_Tool::Tolerance(largest_closed_wire_face)); for (size_t i = 0; i < points.size(); ++i) { for (size_t j = 0; j < i; ++j) { const gp_Pnt& pa = points[i]; const gp_Pnt& pb = points[j]; // Since the text is always displayed horizontally, // the distance is not simply euclidian, but we // favour the x-component; const double d = std::sqrt( 10 * ((pa.X() - pb.X()) * (pa.X() - pb.X())) + 1 * ((pa.Y() - pb.Y()) * (pa.Y() - pb.Y())) ); if (d > furthest_points_distance) { gp_Pnt p3d((pa.XYZ() + pb.XYZ()) / 2.); gp_Pnt2d p2d(p3d.X(), p3d.Y()); if (fcls.Perform(p2d) == TopAbs_IN) { furthest_points = { &pa, &pb }; furthest_points_distance = d; center_point = p3d; } } } } if (center_point) { std::vector labels; if (print_space_names_) { labels.push_back(data.ifc_name); } if (print_space_names_ && data.product->declaration().is("IfcSpace")) { auto attr = data.product->get("LongName"); if (!attr->isNull()) { std::string long_name = *attr; if (!long_name.empty()) { labels.insert(labels.begin(), long_name); } } } if (print_space_areas_) { GProp_GProps prop; BRepGProp::SurfaceProperties(largest_closed_wire_face, prop); const double area = prop.Mass(); std::stringstream ss; ss << std::setprecision(2) << std::fixed << std::showpoint << area; labels.push_back(ss.str() + "m²"); } util::string_buffer path; // dominant-baseline="central" is not well supported in IE. // so we add a 0.35 offset to the dy of the tspans path.add(" X())); path.add("\" y=\""); ycoords.push_back(path.add(center_point->Y())); path.add("\">"); for (auto lit = labels.begin(); lit != labels.end(); ++lit) { const auto& l = *lit; double dy = labels.begin() == lit ? 0.35 - (labels.size() - 1.) / 2. : 1.0; // <- dy is relative to the previous text element, so // always 1 for successive spans. path.add("X())); path.add("\" dy=\""); path.add(boost::lexical_cast(dy)); path.add("em\">"); path.add(l); path.add(""); } path.add(""); po->second.push_back(path); } } if (po && !annotation.IsNull()) { write(*po, annotation); } } if (!emitted) { Logger::Warning("Element not written to SVG due to section heights", data.product); } } void SvgSerializer::setBoundingRectangle(double width, double height) { this->width = width; this->height = height; this->rescale = true; } void SvgSerializer::resize() { if (rescale) { // Scale the resulting image to a bounding rectangle specified by command line arguments const double dx = xmax - xmin; const double dy = ymax - ymin; double sc, cx, cy; if (scale_) { sc = (*scale_) * 1000; cx = (xmax + xmin) / 2. * sc - width / 2.; cy = (ymax + ymin) / 2. * sc - height / 2.; } else { if (calculated_scale_) { sc = *calculated_scale_; } else { if (dx / width > dy / height) { sc = width / dx; } else { sc = height / dy; } calculated_scale_ = sc; } cx = xmin * sc; cy = ymin * sc; } float_item_list::const_iterator it; for (it = xcoords.begin() + xcoords_begin; it != xcoords.end(); ++it, ++xcoords_begin) { double& v = (*it)->value(); v = v * sc - cx; } for (it = ycoords.begin() + ycoords_begin; it != ycoords.end(); ++it, ++ycoords_begin) { double& v = (*it)->value(); v = v * sc - cy; } for (it = radii.begin() + radii_begin; it != radii.end(); ++it, ++radii_begin) { (*it)->value() *= sc; } } // reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale. xmin = +std::numeric_limits::infinity(); ymin = +std::numeric_limits::infinity(); xmax = -std::numeric_limits::infinity(); ymax = -std::numeric_limits::infinity(); } void SvgSerializer::finalize() { resize(); if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) { // Draw door arcs only on floor plans. is_floor_plan_ = false; for (auto& sd : *deferred_section_data_) { bool use_hlr = false; std::string drawing_name; if (sd.which() == 2) { const auto& section = boost::get(sd); use_hlr = section.with_projection; drawing_name = section.name; } if (use_hlr) { hlr = new HLRBRep_Algo; } *section_data_ = { sd }; for (auto& e : element_buffer_) { write(e); } if (use_hlr) { const auto& section = boost::get(sd); gp_Ax2 transform = section.plane.Position().Ax2(); HLRAlgo_Projector projector(transform); hlr->Projector(projector); hlr->Update(); hlr->Hide(); HLRBRep_HLRToShape hlr_shapes(hlr); auto compound = hlr_shapes.VCompound(); TopExp_Explorer exp(compound, TopAbs_EDGE); BRep_Builder B; auto& po = start_path(drawing_name, "class=\"projection\""); for (; exp.More(); exp.Next()) { TopoDS_Wire w; B.MakeWire(w); B.Add(w, exp.Current()); write(po, w); } } resize(); if (use_hlr) { hlr.Nullify(); } } } std::multimap::const_iterator it; boost::optional previous; for (it = paths.begin(); it != paths.end(); ++it) { if (!previous || it->first != *previous) { if (previous) { svg_file << " \n"; } std::ostringstream oss; if (it->first.first) { svg_file << " first.first) << ">\n"; } else { svg_file << " first.second << "\" class=\"section\">\n"; } } svg_file << " second.first << ">\n"; std::vector::const_iterator jt; for (jt = it->second.second.begin(); jt != it->second.second.end(); ++jt) { svg_file << jt->str(); } svg_file << " \n"; previous = it->first; } if (previous) { svg_file << " \n"; } svg_file << "" << std::endl; } void SvgSerializer::writeHeader() { svg_file << "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n"; } namespace { std::string nameElement_(const std::vector >& attrs) { std::ostringstream oss; for (auto& a : attrs) { // @todo while we're at it might as well implement escaping oss << a.first << "=\"" << a.second << "\" "; } return oss.str(); } } std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* storey, const IfcGeom::Element* elem) { return nameElement_({ {"id", with_section_heights_from_storey_ ? object_id(storey, elem) : GeometrySerializer::object_id(elem)}, {"class", elem->type()}, {"data-name", elem->name()}, {"data-guid", elem->guid()} }); } std::string SvgSerializer::idElement(const IfcUtil::IfcBaseEntity* elem) { const std::string type = elem->declaration().is("IfcBuildingStorey") ? "storey" : "product"; const std::string name = (settings().get(SerializerSettings::USE_ELEMENT_GUIDS) ? static_cast(*elem->get("GlobalId")) : ((settings().get(SerializerSettings::USE_ELEMENT_NAMES) && !elem->get("Name")->isNull())) ? static_cast(*elem->get("Name")) : (settings().get(SerializerSettings::USE_ELEMENT_STEPIDS)) ? ("id-" + boost::lexical_cast(elem->data().id())) : IfcParse::IfcGlobalId(*elem->get("GlobalId")).formatted()); return type + "-" + name; } std::string SvgSerializer::nameElement(const IfcUtil::IfcBaseEntity* elem) { if (elem == 0) { return ""; } const std::string& entity = elem->declaration().name(); std::string ifc_name; if (!elem->get("Name")->isNull()) { ifc_name = (std::string) *elem->get("Name"); } return nameElement_({ {"id", idElement(elem)}, {"class", entity}, {"data-name", ifc_name}, {"data-guid", *elem->get("GlobalId")} }); } void SvgSerializer::setFile(IfcParse::IfcFile* f) { file = f; auto storeys = f->instances_by_type("IfcBuildingStorey"); if (!storeys || storeys->size() == 0) { IfcGeom::Kernel kernel(f); std::vector 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) { IfcEntityList::ptr insts = f->instances_by_type(*it); if (insts) { for (auto jt = insts->begin(); jt != insts->end(); ++jt) { IfcUtil::IfcBaseEntity* product = (IfcUtil::IfcBaseEntity*) *jt; if (!product->get("ObjectPlacement")->isNull()) { gp_Trsf trsf; if (kernel.convert_placement(*product->get("ObjectPlacement"), trsf)) { setSectionHeight(trsf.TranslationPart().Z() + 1.); Logger::Warning("No building storeys encountered, used for reference:", product); return; } } } } } Logger::Warning("No building storeys encountered, output might be invalid or missing"); } } void SvgSerializer::setSectionHeight(double h, IfcUtil::IfcBaseEntity* storey) { section_data_.emplace(); section_data_->push_back(horizontal_plan{ storey, h, 0., std::numeric_limits::infinity() }); } void SvgSerializer::setSectionHeightsFromStoreys(double offset) { with_section_heights_from_storey_ = true; section_data_.emplace(); 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"); if (!attr_value->isNull()) { double elev; try { elev = *attr_value; } catch (std::exception& e) { Logger::Error(e); continue; } if (!section_data_->empty()) { boost::get(section_data_->back()).next_elevation = elev * lu; } section_data_->push_back(horizontal_plan{ (IfcUtil::IfcBaseEntity*)s, elev * lu, offset, std::numeric_limits::infinity() }); } } } else { section_data_->push_back(horizontal_plan_at_element{}); } }