diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 03e4ac1863..601e73a7c8 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -336,6 +336,7 @@ int main(int argc, char** argv) { ("bounds", po::value(&bounds), "Specifies the bounding rectangle, for example 512x512, to which the " "output will be scaled. Only used when converting to SVG.") + ("door-arcs", "Draw door openings arcs for IfcDoor elements") ("section-height", po::value(§ion_height), "Specifies the cut section height for SVG 2D geometry.") ("section-height-from-storeys", "Derives section height from storey elevation. Use --section-height to override default offset of 1") @@ -645,7 +646,7 @@ int main(int argc, char** argv) { SerializerSettings settings; /// @todo Make APPLY_DEFAULT_MATERIALS configurable? Quickly tested setting this to false and using obj exporter caused the program to crash and burn. settings.set(IfcGeom::IteratorSettings::APPLY_DEFAULT_MATERIALS, true); - settings.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, use_world_coords || output_extension == SVG || output_extension == OBJ); + settings.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, use_world_coords || output_extension == OBJ); settings.set(IfcGeom::IteratorSettings::WELD_VERTICES, weld_vertices); settings.set(IfcGeom::IteratorSettings::SEW_SHELLS, orient_shells); settings.set(IfcGeom::IteratorSettings::CONVERT_BACK_UNITS, convert_back_units); @@ -834,6 +835,9 @@ int main(int argc, char** argv) { if (bounding_width.is_initialized() && bounding_height.is_initialized()) { static_cast(serializer.get())->setBoundingRectangle(bounding_width.get(), bounding_height.get()); } + if (vmap.count("door-arcs")) { + static_cast(serializer.get())->setDrawDoorArcs(true); + } } if (convert_back_units) { diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 573e0c23cd..d7fbc241d2 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -59,6 +59,11 @@ #include #include +#include +#include +#include +#include + #include "../ifcparse/IfcGlobalId.h" #include "SvgSerializer.h" @@ -290,9 +295,6 @@ SvgSerializer::path_object& SvgSerializer::start_path(IfcUtil::IfcBaseEntity* st return p; } -#include -#include - void SvgSerializer::write(const IfcGeom::BRepElement* o) { std::vector> section_heights_storage; @@ -321,25 +323,119 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) } } + TopoDS_Shape compound_local = o->geometry().as_compound(); + const gp_Trsf& trsf = o->transformation().data(); + 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 = 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, trsf_mirror, true); + make_transform_mirror.Build(); + // (When determinant < 0, copy is implied and the input is not mutated.) + compound = make_transform_mirror.Shape(); + + TopoDS_Wire annotation; + + if (draw_door_arcs_ && o->product()->declaration().is("IfcDoor")) { + + boost::optional operation_type; + + try { + IfcEntityList::ptr rels; + if (o->product()->declaration().schema()->name() == "IFC2X3") { + rels = o->product()->get_inverse("IsDefinedBy"); + } else { + // Damn you, IFC + rels = o->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(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(trsf); + p1.Transform(trsf); + p2.Transform(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()); + } + } + } + } + for (auto& pair : *section_heights_used) { auto cut_z = pair.first; auto storey = pair.second; - // SVG has a coordinate system with the origin in the *upper*-left corner - // therefore we mirror the shape along the XZ-plane. - TopoDS_Shape compound = o->geometry().as_compound(); - gp_Trsf trsf; - trsf.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); - BRepBuilderAPI_Transform make_transform(compound, trsf); - make_transform.Build(); - // When determinant < 0, copy is implied and the input is not mutated. - compound = make_transform.Shape(); - TopoDS_Iterator it(compound); TopoDS_Face largest_closed_wire_face; double largest_closed_wire_area = 0.; - path_object* po; + 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()) { @@ -502,6 +598,10 @@ void SvgSerializer::write(const IfcGeom::BRepElement* o) po->second.push_back(path); } } + + if (po && !annotation.IsNull()) { + write(*po, annotation); + } } } diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index d27ec073ff..b4deecb61c 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -65,7 +65,7 @@ protected: std::ofstream svg_file; double xmin, ymin, xmax, ymax, width, height; boost::optional>> section_heights; - bool rescale, print_space_names_, print_space_areas_; + bool rescale, print_space_names_, print_space_areas_, draw_door_arcs_; std::multimap paths; std::vector< boost::shared_ptr > xcoords; std::vector< boost::shared_ptr > ycoords; @@ -103,6 +103,7 @@ public: void setSectionHeightsFromStoreys(double offset=1.); void setPrintSpaceNames(bool b) { print_space_names_ = b; } void setPrintSpaceAreas(bool b) { print_space_areas_ = b; } + void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; } std::string nameElement(const IfcGeom::Element* elem); std::string nameElement(const IfcUtil::IfcBaseEntity* elem); };