File renames, build script and cmake updates

This commit is contained in:
Thomas Krijnen
2022-11-15 13:19:24 +01:00
parent 8f33c2ca9d
commit ccde605029
133 changed files with 63 additions and 218 deletions
@@ -0,0 +1,508 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
#ifndef IFCGEOMTREE_H
#define IFCGEOMTREE_H
#include "../ifcparse/IfcFile.h"
#include "../ifcgeom_schema_agnostic/IfcGeomElement.h"
#include "../ifcgeom_schema_agnostic/IfcGeomIterator.h"
#include "../ifcgeom_schema_agnostic/IfcGeomMaterial.h"
#include "../ifcgeom_schema_agnostic/Kernel.h"
#include "../ifcgeom_schema_agnostic/base_utils.h"
#include <NCollection_UBTree.hxx>
#include <BRepBndLib.hxx>
#include <Bnd_Box.hxx>
#include <BRep_Builder.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <BRepClass3d_SolidClassifier.hxx>
#include <TopTools_DataMapOfShapeInteger.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepExtrema_ExtPF.hxx>
namespace IfcGeom {
struct ray_intersection_result {
double distance;
int style_index;
IfcUtil::IfcBaseEntity* instance;
std::array<double, 3> position;
std::array<double, 3> normal;
double ray_distance;
double dot_product;
};
namespace {
// Approximates the distance `other` protrudes into `volume` by finding the
// max face-vertex distance for every face, and taking the minimal value of
// those. Note that this uses the internal `BRepExtrema_ExtPF` which only
// returns solutions whose when the vertex projected onto the face is contained
// within the face boundaries. In case of concave `volume` this is desirable.
double max_distance_inside(const TopoDS_Shape& volume, const TopoDS_Shape& other) {
TopExp_Explorer exp_v(volume.Reversed(), TopAbs_FACE);
double min_face_vertex_distance = std::numeric_limits<double>::infinity();
for (; exp_v.More(); exp_v.Next()) {
const TopoDS_Face& f = TopoDS::Face(exp_v.Current());
BRepExtrema_ExtPF epf;
epf.Initialize(f, Extrema_ExtFlag_MIN);
double face_vertex_distance = 0.;
TopExp_Explorer exp_o(other, TopAbs_VERTEX);
for (; exp_o.More(); exp_o.Next()) {
const TopoDS_Vertex& v = TopoDS::Vertex(exp_o.Current());
epf.Perform(v, f);
if (epf.IsDone() && epf.NbExt() == 1) {
double d = epf.SquareDistance(1);
if (d > face_vertex_distance) {
face_vertex_distance = d;
}
}
}
if (face_vertex_distance < min_face_vertex_distance) {
min_face_vertex_distance = face_vertex_distance;
}
}
if (min_face_vertex_distance == std::numeric_limits<double>::infinity()) {
return -1.;
} else {
return std::sqrt(min_face_vertex_distance);
}
}
}
namespace impl {
template <typename T>
class tree {
bool test(const TopoDS_Shape& A, const TopoDS_Shape& B, bool completely_within, double extend) const {
if (extend > 0.) {
BRepExtrema_DistShapeShape dss(A, B);
if (dss.Perform() && dss.NbSolution() >= 1) {
if (dss.Value() <= extend) {
distances_.push_back(dss.Value());
protrusion_distances_.push_back(max_distance_inside(B, A));
}
return dss.Value() <= extend;
}
} else {
if (util::count(A, TopAbs_SHELL) == 0 ||
util::count(B, TopAbs_SHELL) == 0)
{
return false;
}
if (completely_within) {
BRepAlgoAPI_Cut cut(B, A);
if (cut.IsDone()) {
if (util::count(cut.Shape(), TopAbs_SHELL) == 0) {
return true;
}
}
} else {
BRepAlgoAPI_Common common(A, B);
if (common.IsDone()) {
if (util::count(common.Shape(), TopAbs_SHELL) > 0) {
return true;
}
}
}
}
return false;
}
protected:
// @todo this is ugly, embed this in the return type
mutable std::vector<double> distances_;
mutable std::vector<double> protrusion_distances_;
public:
void add(const T& t, const Bnd_Box& b) {
tree_.Add(t, b);
}
void add(const T& t, const TopoDS_Shape& s) {
Bnd_Box b;
BRepBndLib::AddClose(s, b);
add(t, b);
shapes_[t] = s;
}
std::vector<T> select_box(const T& t, bool completely_within = false, double extend=-1.e-5) const {
typename map_t::const_iterator it = shapes_.find(t);
if (it == shapes_.end()) {
return std::vector<T>();
}
Bnd_Box b;
BRepBndLib::AddClose(it->second, b);
// Gap is assumed to be positive throughout the codebase,
// but at least for IsOut() in the selector a negative
// Gap should work as well.
b.SetGap(b.GetGap() + extend);
return select_box(b, completely_within);
}
std::vector<T> select_box(const gp_Pnt& p, double extend=0.0) const {
Bnd_Box b;
b.Add(p);
b.SetGap(b.GetGap() + extend);
return select_box(b);
}
std::vector<T> select_box(const Bnd_Box& b, bool completely_within = false) const {
selector s(b);
tree_.Select(s);
if (completely_within) {
std::vector<T> ts = s.results();
std::vector<T> ts_filtered;
ts_filtered.reserve(ts.size());
typename std::vector<T>::const_iterator it = ts.begin();
for (; it != ts.end(); ++it) {
const TopoDS_Shape& shp = shapes_.find(*it)->second;
Bnd_Box B;
BRepBndLib::AddClose(shp, B);
// BndBox::CornerMin() /-Max() introduced in OCCT 6.8
double x1, y1, z1, x2, y2, z2;
b.Get(x1, y1, z1, x2, y2, z2);
double gap = B.GetGap();
gp_Pnt p1(x1 - gap, y1 - gap, z1 - gap);
gp_Pnt p2(x2 + gap, y2 + gap, z2 + gap);
if (!b.IsOut(p1) && !b.IsOut(p2)) {
ts_filtered.push_back(*it);
}
}
return ts_filtered;
} else {
return s.results();
}
}
std::vector<T> select(const T& t, bool completely_within = false, double extend = 0.0) const {
distances_.clear();
protrusion_distances_.clear();
std::vector<T> ts = select_box(t, completely_within, extend);
if (ts.empty()) {
return ts;
}
const TopoDS_Shape& A = shapes_.find(t)->second;
std::vector<T> ts_filtered;
ts_filtered.reserve(ts.size());
typename std::vector<T>::const_iterator it = ts.begin();
for (it = ts.begin(); it != ts.end(); ++it) {
const TopoDS_Shape& B = shapes_.find(*it)->second;
if (test(A, B, completely_within, extend)) {
ts_filtered.push_back(*it);
}
}
return ts_filtered;
}
std::vector<T> select(const TopoDS_Shape& s, bool completely_within = false, double extend = -1.e-5) const {
distances_.clear();
protrusion_distances_.clear();
Bnd_Box bb;
BRepBndLib::AddClose(s, bb);
bb.SetGap(bb.GetGap() + extend);
std::vector<T> ts = select_box(bb, completely_within);
if (ts.empty()) {
return ts;
}
std::vector<T> ts_filtered;
ts_filtered.reserve(ts.size());
typename std::vector<T>::const_iterator it = ts.begin();
for (it = ts.begin(); it != ts.end(); ++it) {
const TopoDS_Shape& B = shapes_.find(*it)->second;
if (test(s, B, completely_within, extend)) {
ts_filtered.push_back(*it);
}
}
return ts_filtered;
}
std::vector<T> select(const IfcGeom::BRepElement* elem, bool completely_within = false, double extend = -1.e-5) const {
auto compound = elem->geometry().as_compound();
compound.Move(elem->transformation().data());
return select(compound, completely_within, extend);
}
std::vector<T> select(const gp_Pnt& p, double extend=0.0) const {
distances_.clear();
protrusion_distances_.clear();
std::vector<T> ts = select_box(p, extend);
if (ts.empty()) {
return ts;
}
std::vector<T> ts_filtered;
ts_filtered.reserve(ts.size());
TopoDS_Vertex v;
if (extend > 0.) {
BRep_Builder B;
B.MakeVertex(v, p, Precision::Confusion());
}
typename std::vector<T>::const_iterator it = ts.begin();
for (it = ts.begin(); it != ts.end(); ++it) {
const TopoDS_Shape& B = shapes_.find(*it)->second;
if (extend > 0.0) {
BRepExtrema_DistShapeShape dss(v, B);
if (dss.Perform() && dss.NbSolution() >= 1 && dss.Value() <= extend) {
distances_.push_back(dss.Value());
protrusion_distances_.push_back(max_distance_inside(B, v));
ts_filtered.push_back(*it);
}
} else {
TopExp_Explorer exp(B, TopAbs_SOLID);
for (; exp.More(); exp.Next()) {
BRepClass3d_SolidClassifier cls(exp.Current(), p, 1e-5);
if (cls.State() != TopAbs_OUT) {
ts_filtered.push_back(*it);
break;
}
}
}
}
return ts_filtered;
}
protected:
typedef NCollection_UBTree<T, Bnd_Box> tree_t;
typedef std::map<T, TopoDS_Shape> map_t;
tree_t tree_;
map_t shapes_;
bool enable_face_styles_ = false;
class selector : public tree_t::Selector
{
public:
selector(const Bnd_Box& b)
: tree_t::Selector()
, bounds_(b)
{}
Standard_Boolean Reject(const Bnd_Box& b) const {
return bounds_.IsOut(b);
}
Standard_Boolean Accept(const T& o) {
results_.push_back(o);
return Standard_True;
}
const std::vector<T>& results() const {
return results_;
}
private:
std::vector<T> results_;
const Bnd_Box& bounds_;
};
};
}
class tree : public impl::tree<IfcUtil::IfcBaseEntity*> {
public:
tree() {};
tree(IfcParse::IfcFile& f) {
add_file(f, IfcGeom::IteratorSettings());
}
tree(IfcParse::IfcFile& f, const IfcGeom::IteratorSettings& settings) {
add_file(f, settings);
}
tree(IfcGeom::Iterator& it) {
add_file(it);
}
void add_file(IfcParse::IfcFile& f, const IfcGeom::IteratorSettings& settings) {
IfcGeom::IteratorSettings settings_ = settings;
settings_.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
settings_.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, true);
settings_.set(IfcGeom::IteratorSettings::SEW_SHELLS, true);
IfcGeom::Iterator it(settings_, &f);
add_file(it);
}
void add_file(IfcGeom::Iterator& it) {
if (it.initialize()) {
do {
add_element(dynamic_cast<IfcGeom::BRepElement*>(it.get()));
} while (it.next());
}
}
void add_element(IfcGeom::BRepElement* elem) {
if (!elem) {
return;
}
auto compound = elem->geometry().as_compound();
compound.Move(elem->transformation().data());
add(elem->product(), compound);
auto git = elem->geometry().begin();
if (enable_face_styles_) {
TopoDS_Iterator it(compound);
for (; it.More(); it.Next(), ++git) {
std::unique_ptr<IfcGeom::Material> adaptor;
if (git->hasStyle()) {
adaptor.reset(new Material(git->StylePtr()));
} else {
adaptor.reset(new Material(IfcGeom::get_default_style(elem->type())));
}
// Assumption is that the number of styles is small, so the linear lookup time is not significant.
auto sit = std::find(styles_.begin(), styles_.end(), *adaptor);
size_t index;
if (sit == styles_.end()) {
index = styles_.size();
styles_.push_back(*adaptor);
} else {
index = std::distance(styles_.begin(), sit);
}
TopExp_Explorer exp(it.Value(), TopAbs_FACE);
for (; exp.More(); exp.Next()) {
face_styles_.Bind(exp.Current(), (int) index);
}
}
}
}
const std::vector<double>& distances() const {
return distances_;
}
const std::vector<double>& protrusion_distances() const {
return protrusion_distances_;
}
std::vector<IfcGeom::ray_intersection_result> select_ray(const gp_Pnt& p0, const gp_Dir& d, double length = 1000.) const {
gp_Pnt p1 = p0.XYZ() + d.XYZ() * length;
auto E = BRepBuilderAPI_MakeEdge(p0, p1).Edge();
Bnd_Box bb;
bb.Add(p0);
bb.Add(p1);
auto candidates = select_box(bb);
std::multimap<double, ray_intersection_result> ordered;
for (auto& c : candidates) {
BRepExtrema_DistShapeShape dss(E, shapes_.find(c)->second);
for (int i = 1; i <= dss.NbSolution(); ++i) {
if (dss.SupportTypeShape1(i) != BRepExtrema_IsOnEdge) {
// @todo set to 0, is it on the first verteX?
continue;
}
if (dss.SupportTypeShape2(i) != BRepExtrema_IsInFace) {
continue;
}
double u, v, w;
dss.ParOnEdgeS1(i, u);
auto face = TopoDS::Face(dss.SupportOnShape2(i));
int sidx = -1;
if (enable_face_styles_) {
sidx = face_styles_.Find(face);
}
dss.ParOnFaceS2(i, v, w);
BRepGProp_Face prop(face);
gp_Pnt P;
gp_Vec V;
prop.Normal(v, w, P, V);
ordered.insert({ u, { u, sidx, c,
{P.X(), P.Y(), P.Z()},
{V.X(), V.Y(), V.Z()},
d.XYZ().Dot(p0.XYZ() - P.XYZ()),
V.Dot(d)
} });
}
}
std::vector<ray_intersection_result> result;
for (auto& p : ordered) {
result.push_back(p.second);
}
return result;
}
bool enable_face_styles() const {
return enable_face_styles_;
}
void enable_face_styles(bool b) {
enable_face_styles_ = b;
}
const std::vector<IfcGeom::Material>& styles() const {
return styles_;
}
protected:
typedef TopTools_DataMapOfShapeInteger face_style_map_t;
face_style_map_t face_styles_;
std::vector<IfcGeom::Material> styles_;
};
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,429 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
#ifndef IFCGEOM_H
#define IFCGEOM_H
#include <cmath>
#include <array>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <gp_Mat.hxx>
#include <gp_Mat2d.hxx>
#include <gp_GTrsf.hxx>
#include <gp_GTrsf2d.hxx>
#include <gp_Trsf.hxx>
#include <gp_Trsf2d.hxx>
#include <gp_Quaternion.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Face.hxx>
#include <Geom_Curve.hxx>
#include <gp_Pln.hxx>
#include <TColgp_SequenceOfPnt.hxx>
#include <TopTools_ListOfShape.hxx>
#include <BOPAlgo_Operation.hxx>
#include <BRep_Builder.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include "../ifcparse/macros.h"
#include "../ifcparse/IfcParse.h"
#include "../ifcparse/IfcBaseClass.h"
#include "../ifcgeom_schema_agnostic/IfcGeomElement.h"
#include "../ifcgeom_schema_agnostic/IfcGeomRepresentation.h"
#include "../ifcgeom_schema_agnostic/IfcRepresentationShapeItem.h"
#include "../ifcgeom_schema_agnostic/IfcGeomShapeType.h"
#include "../ifcgeom_schema_agnostic/Kernel.h"
#include "../ifcgeom_schema_agnostic/ifc_geom_api.h"
// Define this in case you want to conserve memory usage at all cost. This has been
// benchmarked extensively: https://github.com/IfcOpenShell/IfcOpenShell/pull/47
// #define NO_CACHE
#ifdef NO_CACHE
#define IN_CACHE(T,E,t,e)
#define CACHE(T,E,e)
#else
#define IN_CACHE(T,E,t,e) std::map<int,t>::const_iterator it = cache.T.find(E->data().id());\
if ( it != cache.T.end() ) { e = it->second; return true; }
#define CACHE(T,E,e) cache.T[E->data().id()] = e;
#endif
#define INCLUDE_PARENT_DIR(x) STRINGIFY(../ifcparse/x.h)
#include INCLUDE_PARENT_DIR(IfcSchema)
#undef INCLUDE_PARENT_DIR
#define INCLUDE_PARENT_DIR(x) STRINGIFY(../ifcparse/x-definitions.h)
#include INCLUDE_PARENT_DIR(IfcSchema)
namespace IfcGeom {
class IFC_GEOM_API MAKE_TYPE_NAME(Cache) {
public:
#include "mapping_cache.i"
std::map<int, TopoDS_Shape> Shape;
};
namespace util {
template <typename T>
typename std::enable_if<std::is_pointer<T>::value, T&>::type conditional_address_of(T& t) {
return t;
}
template <typename T>
typename std::enable_if<!std::is_pointer<T>::value, T*>::type conditional_address_of(T& t) {
return &t;
}
}
class IFC_GEOM_API MAKE_TYPE_NAME(Kernel) : public IfcGeom::Kernel {
private:
/*
faceset_helper traverses the forward instance references of IfcConnectedFaceSet and then provides a mapping
M of (IfcCartesianPoint, IfcCartesianPoint) -> TopoDS_Edge, where M(a, b) is a partner of M(b, a), ie share
the same underlying edge but with orientation reversed. This then later speeds op the process of creating a
manifold Shell / Solid from this set of faces. Only IfcPolyLoop instances are used. Points within the tolerance
threshiold are merged, so consider points a, b, c, distance(a, b) < eps then M(a, b) = Null, M(a, b) = M(a, c).
*/
template <typename CP=const IfcSchema::IfcCartesianPoint*, typename LP=const IfcSchema::IfcPolyLoop*>
class faceset_helper {
private:
MAKE_TYPE_NAME(Kernel)* kernel_;
std::set<typename std::conditional<std::is_pointer<LP>::value, LP, const LP*>::type> duplicates_;
std::map<const void*, int> vertex_mapping_;
std::map<std::pair<int, int>, TopoDS_Edge> edges_;
// not always in use
const std::vector<std::vector<double>>* points_ = nullptr;
double eps_;
bool non_manifold_;
void loop_(const LP& lp, const std::function<void(int, int, bool)>& callback);
bool construct(const IfcSchema::IfcCartesianPoint* cp, gp_Pnt* l);
bool construct(const std::vector<double>& cp, gp_Pnt* l);
const void* get_idx(const IfcSchema::IfcCartesianPoint* cp) {
return cp;
}
const void* get_idx(const std::vector<double>& cp) {
return &cp;
}
std::vector<const void*> get_idxs(const IfcSchema::IfcPolyLoop* lp);
std::vector<const void*> get_idxs(const std::vector<int>& it);
public:
faceset_helper(
MAKE_TYPE_NAME(Kernel)* kernel,
const std::vector<CP>& points,
const std::vector<LP>& indices,
bool should_by_closed);
~faceset_helper();
bool non_manifold() const { return non_manifold_; }
bool& non_manifold() { return non_manifold_; }
double epsilon() const { return eps_; }
bool edge(int A, int B, TopoDS_Edge& e);
bool wire(const LP& loop, TopoDS_Wire& wire);
bool wires(const LP& loop, TopTools_ListOfShape& wires);
};
double deflection_tolerance;
double max_faces_to_orient;
double ifc_length_unit;
double ifc_planeangle_unit;
double modelling_precision;
double dimensionality;
double layerset_first;
double no_wire_intersection_check;
double no_wire_intersection_tolerance;
double precision_factor;
double boolean_debug_setting;
double boolean_attempt_2d;
// For stopping PlacementRelTo recursion in convert(const IfcSchema::IfcObjectPlacement* l, gp_Trsf& trsf)
const IfcParse::declaration* placement_rel_to_type_;
const IfcUtil::IfcBaseEntity* placement_rel_to_instance_;
faceset_helper<>* faceset_helper_;
double disable_boolean_result;
gp_Vec offset = gp_Vec{0.0, 0.0, 0.0};
gp_Quaternion rotation = gp_Quaternion{};
gp_Trsf offset_and_rotation = gp_Trsf();
#ifndef NO_CACHE
MAKE_TYPE_NAME(Cache) cache;
#endif
std::map<int, std::shared_ptr<const SurfaceStyle>> style_cache;
std::shared_ptr<const SurfaceStyle> internalize_surface_style(const std::pair<IfcUtil::IfcBaseClass*, IfcUtil::IfcBaseClass*>& shading_style);
public:
MAKE_TYPE_NAME(Kernel)()
: IfcGeom::Kernel()
, deflection_tolerance(0.001)
, max_faces_to_orient(-1.0)
, ifc_length_unit(1.0)
, ifc_planeangle_unit(-1.0)
, modelling_precision(0.00001)
, dimensionality(1.)
, layerset_first(-1.)
, no_wire_intersection_check(-1)
, no_wire_intersection_tolerance(-1)
, precision_factor(10.)
, boolean_debug_setting(false)
, boolean_attempt_2d(true)
, placement_rel_to_type_(nullptr)
, placement_rel_to_instance_(nullptr)
, faceset_helper_(nullptr)
, disable_boolean_result(-1.)
{}
MAKE_TYPE_NAME(Kernel)(const MAKE_TYPE_NAME(Kernel)& other)
: IfcGeom::Kernel()
, deflection_tolerance(other.deflection_tolerance)
, max_faces_to_orient(other.max_faces_to_orient)
, ifc_length_unit(other.ifc_length_unit)
, ifc_planeangle_unit(other.ifc_planeangle_unit)
, modelling_precision(other.modelling_precision)
, dimensionality(other.dimensionality)
, layerset_first(other.layerset_first)
, no_wire_intersection_check(other.no_wire_intersection_check)
, no_wire_intersection_tolerance(other.no_wire_intersection_tolerance)
, precision_factor(other.precision_factor)
, boolean_debug_setting(other.boolean_debug_setting)
, boolean_attempt_2d(other.boolean_attempt_2d)
, placement_rel_to_type_(other.placement_rel_to_type_)
, placement_rel_to_instance_(other.placement_rel_to_instance_)
// @nb faceset_helper_ always initialized to 0
, faceset_helper_(nullptr)
, disable_boolean_result(other.disable_boolean_result)
, offset(other.offset)
, rotation(other.rotation)
, offset_and_rotation(other.offset_and_rotation)
{
}
MAKE_TYPE_NAME(Kernel)& operator=(const MAKE_TYPE_NAME(Kernel)& other) {
deflection_tolerance = other.deflection_tolerance;
max_faces_to_orient = other.max_faces_to_orient;
ifc_length_unit = other.ifc_length_unit;
ifc_planeangle_unit = other.ifc_planeangle_unit;
modelling_precision = other.modelling_precision;
dimensionality = other.dimensionality;
layerset_first = other.layerset_first;
no_wire_intersection_check = other.no_wire_intersection_check;
no_wire_intersection_tolerance = other.no_wire_intersection_tolerance;
precision_factor = other.precision_factor;
boolean_debug_setting = other.boolean_debug_setting;
boolean_attempt_2d = other.boolean_attempt_2d;
placement_rel_to_type_ = other.placement_rel_to_type_;
placement_rel_to_instance_ = other.placement_rel_to_instance_;
disable_boolean_result = other.disable_boolean_result;
offset = other.offset;
rotation = other.rotation;
offset_and_rotation = other.offset_and_rotation;
return *this;
}
void set_offset(const std::array<double, 3>& offset);
void set_rotation(const std::array<double, 4>& rotation);
double get_wire_intersection_tolerance(const TopoDS_Wire&) const;
bool convert_shapes(const IfcUtil::IfcBaseInterface* L, IfcRepresentationShapeItems& result);
IfcGeom::ShapeType shape_type(const IfcUtil::IfcBaseInterface* L);
bool convert_shape(const IfcUtil::IfcBaseInterface* L, TopoDS_Shape& result);
bool convert_wire(const IfcUtil::IfcBaseInterface* L, TopoDS_Wire& result);
bool convert_curve(const IfcUtil::IfcBaseInterface* L, Handle(Geom_Curve)& result);
bool convert_face(const IfcUtil::IfcBaseInterface* L, TopoDS_Shape& result);
bool convert_openings(const IfcSchema::IfcProduct* entity, const IfcSchema::IfcRelVoidsElement::list::ptr& openings, const IfcRepresentationShapeItems& entity_shapes, const gp_Trsf& entity_trsf, IfcRepresentationShapeItems& cut_shapes);
bool convert_layerset(const IfcSchema::IfcProduct*, std::vector<Handle_Geom_Surface>&, std::vector<std::shared_ptr<const SurfaceStyle>>&, std::vector<double>&);
bool fold_layers(const IfcSchema::IfcWall*, const IfcRepresentationShapeItems&, const std::vector<Handle_Geom_Surface>&, const std::vector<double>&, std::vector< std::vector<Handle_Geom_Surface> >&);
bool find_wall_end_points(const IfcSchema::IfcWall*, gp_Pnt& start, gp_Pnt& end);
IfcSchema::IfcSurfaceStyleShading* get_surface_style(IfcSchema::IfcRepresentationItem* item);
const IfcSchema::IfcRepresentationItem* find_item_carrying_style(const IfcSchema::IfcRepresentationItem* item);
bool is_identity_transform(IfcUtil::IfcBaseInterface*);
IfcSchema::IfcRelVoidsElement::list::ptr find_openings(IfcSchema::IfcProduct* product);
IfcSchema::IfcRepresentation* find_representation(const IfcSchema::IfcProduct*, const std::string&);
std::pair<std::string, double> initializeUnits(IfcSchema::IfcUnitAssignment*);
IfcGeom::BRepElement* create_brep_for_representation_and_product(
const IteratorSettings&, IfcSchema::IfcRepresentation*, IfcSchema::IfcProduct*);
IfcGeom::BRepElement* create_brep_for_processed_representation(
const IteratorSettings&, IfcSchema::IfcRepresentation*, IfcSchema::IfcProduct*, IfcGeom::BRepElement*);
const IfcSchema::IfcMaterial* get_single_material_association(const IfcSchema::IfcProduct*);
IfcSchema::IfcRepresentation* representation_mapped_to(const IfcSchema::IfcRepresentation* representation);
IfcSchema::IfcProduct::list::ptr products_represented_by(const IfcSchema::IfcRepresentation*);
std::shared_ptr<const SurfaceStyle> get_style(const IfcSchema::IfcRepresentationItem*);
std::shared_ptr<const SurfaceStyle> get_style(const IfcSchema::IfcMaterial*);
template <typename T> std::pair<IfcSchema::IfcSurfaceStyle*, T*> _get_surface_style(const IfcSchema::IfcStyledItem* si) {
std::vector<IfcSchema::IfcPresentationStyle*> prs_styles;
#ifdef SCHEMA_HAS_IfcStyleAssignmentSelect
aggregate_of_instance::ptr style_assignments = si->Styles();
for (aggregate_of_instance::it kt = style_assignments->begin(); kt != style_assignments->end(); ++kt) {
// Using IfcPresentationStyleAssignment is deprecated, use the direct assignment of a subtype of IfcPresentationStyle instead.
auto style_k = (*kt)->as<IfcSchema::IfcPresentationStyle>();
if (style_k) {
prs_styles.push_back(style_k);
continue;
}
if (!(*kt)->declaration().is(IfcSchema::IfcPresentationStyleAssignment::Class())) {
continue;
}
IfcSchema::IfcPresentationStyleAssignment* style_assignment = (IfcSchema::IfcPresentationStyleAssignment*) *kt;
Logger::Warning("Deprecated usage of", style_assignment);
// Only in case of 2x3 or old style IfcPresentationStyleAssignment
auto styles = style_assignment->Styles();
#elif defined SCHEMA_HAS_IfcPresentationStyleAssignment
IfcSchema::IfcPresentationStyleAssignment::list::ptr style_assignments = si->Styles();
for (IfcSchema::IfcPresentationStyleAssignment::list::it kt = style_assignments->begin(); kt != style_assignments->end(); ++kt) {
IfcSchema::IfcPresentationStyleAssignment* style_assignment = *kt;
// Only in case of 2x3 or old style IfcPresentationStyleAssignment
auto styles = style_assignment->Styles();
#else
auto styles = si->Styles();
#endif
for (auto lt = styles->begin(); lt != styles->end(); ++lt) {
auto style_l = (*lt)->as<IfcSchema::IfcPresentationStyle>();
if (style_l) {
prs_styles.push_back(style_l);
}
}
#if defined(SCHEMA_HAS_IfcStyleAssignmentSelect) || defined(SCHEMA_HAS_IfcPresentationStyleAssignment)
}
#endif
for (auto& style : prs_styles) {
if (style->declaration().is(IfcSchema::IfcSurfaceStyle::Class())) {
IfcSchema::IfcSurfaceStyle* surface_style = (IfcSchema::IfcSurfaceStyle*) style;
if (surface_style->Side() != IfcSchema::IfcSurfaceSide::IfcSurfaceSide_NEGATIVE) {
aggregate_of_instance::ptr styles_elements = surface_style->Styles();
for (aggregate_of_instance::it mt = styles_elements->begin(); mt != styles_elements->end(); ++mt) {
if ((*mt)->declaration().is(T::Class())) {
return std::make_pair(surface_style, (T*) *mt);
}
}
}
}
}
return std::make_pair<IfcSchema::IfcSurfaceStyle*, T*>(0,0);
}
template <typename T> std::pair<IfcSchema::IfcSurfaceStyle*, T*> get_surface_style(const IfcSchema::IfcRepresentationItem* representation_item) {
// For certain representation items, most notably boolean operands,
// a style definition might reside on one of its operands.
representation_item = find_item_carrying_style(representation_item);
if (representation_item->as<IfcSchema::IfcStyledItem>()) {
return _get_surface_style<T>(representation_item->as<IfcSchema::IfcStyledItem>());
}
IfcSchema::IfcStyledItem::list::ptr styled_items = representation_item->StyledByItem();
if (styled_items->size()) {
// StyledByItem is a SET [0:1] OF IfcStyledItem, so we return after the first IfcStyledItem:
return _get_surface_style<T>(*styled_items->begin());
}
return std::make_pair<IfcSchema::IfcSurfaceStyle*, T*>(0,0);
}
void purge_cache() {
// Rather hack-ish, but a stopgap solution to keep memory under control
// for large files. SurfaceStyles need to be kept at all costs, as they
// are read later on when serializing Collada files.
#ifndef NO_CACHE
cache = MAKE_TYPE_NAME(Cache)();
#endif
}
void set_conversion_placement_rel_to_type(const IfcParse::declaration* type);
void set_conversion_placement_rel_to_instance(const IfcUtil::IfcBaseEntity* instance);
#include "mapping_kernel_header.i"
virtual void setValue(GeomValue var, double value);
virtual double getValue(GeomValue var) const;
virtual IfcGeom::BRepElement* convert(
const IteratorSettings& settings, IfcUtil::IfcBaseClass* representation,
IfcUtil::IfcBaseClass* product)
{
return create_brep_for_representation_and_product(settings, representation->as<IfcSchema::IfcRepresentation>(), product->as<IfcSchema::IfcProduct>());
}
virtual IfcRepresentationShapeItems convert(IfcUtil::IfcBaseClass* item) {
IfcRepresentationShapeItems items;
bool success = convert_shapes(item, items);
if (!success) {
throw IfcParse::IfcException("Failed to process representation item");
}
return items;
}
virtual bool convert_placement(IfcUtil::IfcBaseClass* item, gp_Trsf& trsf) {
if (item->as<IfcSchema::IfcObjectPlacement>()) {
try {
return convert(item->as<IfcSchema::IfcObjectPlacement>(), trsf);
} catch (std::exception& e) {
Logger::Error(e, item);
} catch (...) {
Logger::Error("Failed processing placement", item);
}
}
return false;
}
};
IfcUtil::IfcBaseClass* MAKE_TYPE_NAME(tesselate_)(const TopoDS_Shape& shape, double deflection);
IfcUtil::IfcBaseClass* MAKE_TYPE_NAME(serialise_)(const TopoDS_Shape& shape, bool advanced);
}
#endif
@@ -0,0 +1,741 @@
#include "base_utils.h"
#include "../ifcparse/IfcLogger.h"
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Vertex.hxx>
#include <gp_GTrsf.hxx>
#include <gp_GTrsf2d.hxx>
#include <Geom_Plane.hxx>
#include <Geom_OffsetSurface.hxx>
#include <ShapeAnalysis_Curve.hxx>
#include <ShapeAnalysis_Surface.hxx>
#include <BRep_Tool.hxx>
#include <BRepBndLib.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepBuilderAPI_GTransform.hxx>
#include <BRepBuilderAPI_MakePolygon.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepPrimAPI_MakeHalfSpace.hxx>
#include <BRepOffsetAPI_Sewing.hxx>
#include <GeomAPI_IntSS.hxx>
#include <GeomAPI_IntCS.hxx>
#include <BRepGProp.hxx>
#include <BRepGProp_Face.hxx>
#include <GProp_GProps.hxx>
#include <ShapeFix_Shell.hxx>
#include <ShapeFix_Solid.hxx>
#include <ShapeFix_Shape.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepClass3d_SolidClassifier.hxx>
#include <TopTools_IndexedMapOfShape.hxx>
// For axis placements detect equality early in order for the
// relatively computionaly expensive gp_Trsf calculation to be skipped
bool IfcGeom::util::axis_equal(const gp_Ax3 & a, const gp_Ax3 & b, double tolerance) {
if (!a.Location().IsEqual(b.Location(), tolerance)) return false;
// Note that the tolerance below is angular, above is linear. Since architectural
// objects are about 1m'ish in scale, it should be somewhat equivalent. Besides,
// this is mostly a filter for NULL or default values in the placements.
if (!a.Direction().IsEqual(b.Direction(), tolerance)) return false;
if (!a.XDirection().IsEqual(b.XDirection(), tolerance)) return false;
if (!a.YDirection().IsEqual(b.YDirection(), tolerance)) return false;
return true;
}
bool IfcGeom::util::axis_equal(const gp_Ax2d & a, const gp_Ax2d & b, double tolerance) {
if (!a.Location().IsEqual(b.Location(), tolerance)) return false;
if (!a.Direction().IsEqual(b.Direction(), tolerance)) return false;
return true;
}
int IfcGeom::util::count(const TopoDS_Shape& s, TopAbs_ShapeEnum t, bool unique) {
if (unique) {
TopTools_IndexedMapOfShape map;
TopExp::MapShapes(s, t, map);
return map.Extent();
} else {
int i = 0;
TopExp_Explorer exp(s, t);
for (; exp.More(); exp.Next()) {
++i;
}
return i;
}
}
int IfcGeom::util::surface_genus(const TopoDS_Shape& s) {
int nv = count(s, TopAbs_VERTEX, true);
int ne = count(s, TopAbs_EDGE, true);
int nf = count(s, TopAbs_FACE, true);
const int euler = nv - ne + nf;
const int genus = (2 - euler) / 2;
return genus;
}
bool IfcGeom::util::is_manifold(const TopoDS_Shape& a) {
if (a.ShapeType() == TopAbs_COMPOUND || a.ShapeType() == TopAbs_SOLID) {
TopoDS_Iterator it(a);
for (; it.More(); it.Next()) {
if (!is_manifold(it.Value())) {
return false;
}
}
return true;
} else {
TopTools_IndexedDataMapOfShapeListOfShape map;
TopExp::MapShapesAndAncestors(a, TopAbs_EDGE, TopAbs_FACE, map);
for (int i = 1; i <= map.Extent(); ++i) {
const TopoDS_Edge& e = TopoDS::Edge(map.FindKey(i));
TopoDS_Vertex v0, v1;
TopExp::Vertices(e, v0, v1);
const bool degenerate = !v0.IsNull() && !v1.IsNull() && v0.IsSame(v1);
if (degenerate) {
continue;
}
if (map.FindFromIndex(i).Extent() != 2) {
return false;
}
}
return true;
}
}
bool IfcGeom::util::is_nested_compound_of_solid(const TopoDS_Shape& s, int depth) {
if (s.ShapeType() == TopAbs_COMPOUND) {
TopoDS_Iterator it(s);
for (; it.More(); it.Next()) {
if (!is_nested_compound_of_solid(it.Value(), depth + 1)) {
return false;
}
}
return true;
} else if (s.ShapeType() == TopAbs_SOLID) {
return depth > 0;
} else {
return false;
}
}
namespace {
template <typename T> struct dimension_count {};
template <> struct dimension_count <gp_Trsf2d > { static const int n = 2; };
template <> struct dimension_count <gp_GTrsf2d> { static const int n = 2; };
template <> struct dimension_count < gp_Trsf > { static const int n = 3; };
template <> struct dimension_count < gp_GTrsf > { static const int n = 3; };
template <typename T>
bool is_identity_helper(const T& t, double tolerance) {
// Note the {1, n+1} range due to Open Cascade's 1-based indexing
// Note the {1, n+2} range due to the translation part of the matrix
for (int i = 1; i < dimension_count<T>::n + 2; ++i) {
for (int j = 1; j < dimension_count<T>::n + 1; ++j) {
const double iden_value = i == j ? 1. : 0.;
const double trsf_value = t.Value(j, i);
if (fabs(trsf_value - iden_value) > tolerance) {
return false;
}
}
}
return true;
}
}
bool IfcGeom::util::is_identity(const gp_Trsf2d& t, double tolerance) {
return is_identity_helper(t, tolerance);
}
bool IfcGeom::util::is_identity(const gp_GTrsf2d& t, double tolerance) {
return is_identity_helper(t, tolerance);
}
bool IfcGeom::util::is_identity(const gp_Trsf& t, double tolerance) {
return is_identity_helper(t, tolerance);
}
bool IfcGeom::util::is_identity(const gp_GTrsf& t, double tolerance) {
return is_identity_helper(t, tolerance);
}
gp_Trsf IfcGeom::util::combine_offset_and_rotation(const gp_Vec & offset, const gp_Quaternion & rotation) {
auto offset_transform = gp_Trsf{};
offset_transform.SetTranslation(offset);
auto rotation_transform = gp_Trsf{};
rotation_transform.SetRotation(rotation);
return rotation_transform * offset_transform;
}
bool IfcGeom::util::project(const Handle_Geom_Surface& srf, const TopoDS_Shape& shp, double& u1, double& v1, double& u2, double& v2, double widen) {
// @todo std::unique_ptr for C++11
ShapeAnalysis_Surface* sas = 0;
Handle(Geom_Plane) pln;
if (srf->DynamicType() == STANDARD_TYPE(Geom_Plane)) {
// Optimize projection for specific cases
pln = Handle(Geom_Plane)::DownCast(srf);
} else if (srf->DynamicType() == STANDARD_TYPE(Geom_OffsetSurface) && Handle(Geom_OffsetSurface)::DownCast(srf)->BasisSurface()->DynamicType() == STANDARD_TYPE(Geom_Plane)) {
// For an offset planar surface the projected UV coords are the same as the basis surface
pln = Handle(Geom_Plane)::DownCast(Handle(Geom_OffsetSurface)::DownCast(srf)->BasisSurface());
} else {
sas = new ShapeAnalysis_Surface(srf);
}
u1 = v1 = +std::numeric_limits<double>::infinity();
u2 = v2 = -std::numeric_limits<double>::infinity();
gp_Pnt median;
int vertex_count = 0;
for (TopExp_Explorer exp(shp, TopAbs_VERTEX); exp.More(); exp.Next(), ++vertex_count) {
gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current()));
median.ChangeCoord() += p.XYZ();
gp_Pnt2d uv;
if (sas) {
uv = sas->ValueOfUV(p, 1e-3);
} else {
gp_Vec d = p.XYZ() - pln->Position().Location().XYZ();
uv.SetX(d.Dot(pln->Position().XDirection()));
uv.SetY(d.Dot(pln->Position().YDirection()));
}
if (uv.X() < u1) u1 = uv.X();
if (uv.Y() < v1) v1 = uv.Y();
if (uv.X() > u2) u2 = uv.X();
if (uv.Y() > v2) v2 = uv.Y();
}
if (vertex_count > 0) {
// Add a little bit of resolution so that the median is shifted towards the mass
// of the curve. This helps to find the parameter ordering for conic surfaces.
for (TopExp_Explorer exp(shp, TopAbs_EDGE); exp.More(); exp.Next(), ++vertex_count) {
const TopoDS_Edge& e = TopoDS::Edge(exp.Current());
double a, b;
Handle_Geom_Curve crv = BRep_Tool::Curve(e, a, b);
gp_Pnt p;
crv->D0((a + b) / 2., p);
median.ChangeCoord() += p.XYZ();
}
median.ChangeCoord().Divide(vertex_count);
gp_Pnt2d uv;
if (sas) {
uv = sas->ValueOfUV(median, 1e-3);
} else {
gp_Vec d = median.XYZ() - pln->Position().Location().XYZ();
uv.SetX(d.Dot(pln->Position().XDirection()));
uv.SetY(d.Dot(pln->Position().YDirection()));
}
if (uv.X() < u1 || uv.X() > u2) {
std::swap(u1, u2);
}
u1 -= widen;
u2 += widen;
v1 -= widen;
v2 += widen;
}
delete sas;
return vertex_count > 0;
}
TopoDS_Shape IfcGeom::util::apply_transformation(const TopoDS_Shape& s, const gp_Trsf& t) {
if (t.Form() == gp_Identity) {
return s;
} else {
/// @todo set to 1. and exactly 1. or use epsilon?
if (t.ScaleFactor() != 1.) {
return BRepBuilderAPI_Transform(s, t, true);
} else {
return s.Moved(t);
}
}
}
TopoDS_Shape IfcGeom::util::apply_transformation(const TopoDS_Shape& s, const gp_GTrsf& t) {
if (t.Form() == gp_Other) {
return BRepBuilderAPI_GTransform(s, t, true);
} else {
return apply_transformation(s, t.Trsf());
}
}
bool IfcGeom::util::fit_halfspace(const TopoDS_Shape& a, const TopoDS_Shape& b, TopoDS_Shape& box, double& height, double tol) {
TopExp_Explorer exp(b, TopAbs_FACE);
if (!exp.More()) {
return false;
}
TopoDS_Face face = TopoDS::Face(exp.Current());
exp.Next();
if (exp.More()) {
return false;
}
Handle(Geom_Surface) surf = BRep_Tool::Surface(face);
// const gp_XYZ xyz = a.Location().Transformation().TranslationPart();
// std::cout << "dz " << xyz.Z() << std::endl;
if (surf->DynamicType() != STANDARD_TYPE(Geom_Plane)) {
return false;
}
Bnd_Box bb;
BRepBndLib::Add(a, bb);
if (bb.IsVoid()) {
return false;
}
double xs[2], ys[2], zs[2];
bb.Get(xs[0], ys[0], zs[0], xs[1], ys[1], zs[1]);
gp_Pln pln = Handle(Geom_Plane)::DownCast(surf)->Pln();
gp_Pnt P = pln.Position().Location();
gp_Vec z = pln.Position().Direction();
gp_Vec x = pln.Position().XDirection();
gp_Vec y = pln.Position().YDirection();
if (face.Orientation() != TopAbs_REVERSED) {
z.Reverse();
}
double D, Umin, Umax, Vmin, Vmax;
D = 0.;
Umin = Vmin = +std::numeric_limits<double>::infinity();
Umax = Vmax = -std::numeric_limits<double>::infinity();
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
for (int k = 0; k < 2; ++k) {
gp_Pnt p(xs[i], ys[j], zs[k]);
gp_Vec d = p.XYZ() - P.XYZ();
const double u = d.Dot(x);
const double v = d.Dot(y);
const double w = d.Dot(z);
if (w > D) {
D = w;
}
if (u < Umin) {
Umin = u;
}
if (u > Umax) {
Umax = u;
}
if (v < Vmin) {
Vmin = v;
}
if (v > Vmax) {
Vmax = v;
}
}
}
}
const double eps = tol * 1000.;
BRepBuilderAPI_MakePolygon poly;
poly.Add(P.XYZ() + x.XYZ() * (Umin - eps) + y.XYZ() * (Vmin - eps));
poly.Add(P.XYZ() + x.XYZ() * (Umax + eps) + y.XYZ() * (Vmin - eps));
poly.Add(P.XYZ() + x.XYZ() * (Umax + eps) + y.XYZ() * (Vmax + eps));
poly.Add(P.XYZ() + x.XYZ() * (Umin - eps) + y.XYZ() * (Vmax + eps));
poly.Close();
BRepBuilderAPI_MakeFace mf(surf, poly.Wire(), true);
gp_Vec vec = gp_Vec(z.XYZ() * (D + eps));
BRepPrimAPI_MakePrism mp(mf.Face(), vec);
box = mp.Shape();
height = D;
return true;
}
const Handle_Geom_Curve IfcGeom::util::intersect(const Handle_Geom_Surface& a, const Handle_Geom_Surface& b) {
GeomAPI_IntSS x(a, b, 1.e-7);
if (x.IsDone() && x.NbLines() == 1) {
return x.Line(1);
} else {
return Handle_Geom_Curve();
}
}
const Handle_Geom_Curve IfcGeom::util::intersect(const Handle_Geom_Surface& a, const TopoDS_Face& b) {
return intersect(a, BRep_Tool::Surface(b));
}
const Handle_Geom_Curve IfcGeom::util::intersect(const TopoDS_Face& a, const Handle_Geom_Surface& b) {
return intersect(BRep_Tool::Surface(a), b);
}
bool IfcGeom::util::intersect(const Handle_Geom_Curve& a, const Handle_Geom_Surface& b, gp_Pnt& p) {
GeomAPI_IntCS x(a, b);
if (x.IsDone() && x.NbPoints() == 1) {
p = x.Point(1);
return true;
} else {
return false;
}
}
bool IfcGeom::util::intersect(const Handle_Geom_Curve& a, const TopoDS_Face& b, gp_Pnt &c) {
return intersect(a, BRep_Tool::Surface(b), c);
}
bool IfcGeom::util::intersect(const Handle_Geom_Curve& a, const TopoDS_Shape& b, std::vector<gp_Pnt>& out) {
TopExp_Explorer exp(b, TopAbs_FACE);
gp_Pnt p;
for (; exp.More(); exp.Next()) {
if (intersect(a, TopoDS::Face(exp.Current()), p)) {
out.push_back(p);
}
}
return !out.empty();
}
bool IfcGeom::util::intersect(const Handle_Geom_Surface& a, const TopoDS_Shape& b, std::vector< std::pair<Handle_Geom_Surface, Handle_Geom_Curve> >& out) {
TopExp_Explorer exp(b, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
const TopoDS_Face& f = TopoDS::Face(exp.Current());
const Handle_Geom_Surface& s = BRep_Tool::Surface(f);
Handle_Geom_Curve crv = intersect(a, s);
if (!crv.IsNull()) {
out.push_back(std::make_pair(s, crv));
}
}
return !out.empty();
}
bool IfcGeom::util::closest(const gp_Pnt& a, const std::vector<gp_Pnt>& b, gp_Pnt& c) {
double minimal_distance = std::numeric_limits<double>::infinity();
for (std::vector<gp_Pnt>::const_iterator it = b.begin(); it != b.end(); ++it) {
const double d = a.Distance(*it);
if (d < minimal_distance) {
minimal_distance = d;
c = *it;
}
}
return minimal_distance != std::numeric_limits<double>::infinity();
}
bool IfcGeom::util::project(const Handle_Geom_Curve& crv, const gp_Pnt& pt, gp_Pnt& p, double& u, double& d) {
ShapeAnalysis_Curve sac;
sac.Project(crv, pt, 1e-3, p, u, false);
d = pt.Distance(p);
return true;
}
double IfcGeom::util::shape_volume(const TopoDS_Shape& s) {
GProp_GProps prop;
BRepGProp::VolumeProperties(s, prop);
return prop.Mass();
}
double IfcGeom::util::face_area(const TopoDS_Face& f) {
GProp_GProps prop;
BRepGProp::SurfaceProperties(f, prop);
return prop.Mass();
}
bool IfcGeom::util::is_convex(const TopoDS_Wire& wire, double tol) {
for (TopExp_Explorer exp1(wire, TopAbs_VERTEX); exp1.More(); exp1.Next()) {
TopoDS_Vertex V1 = TopoDS::Vertex(exp1.Current());
gp_Pnt P1 = BRep_Tool::Pnt(V1);
// Store the neighboring points
std::vector<gp_Pnt> neighbors;
for (TopExp_Explorer exp3(wire, TopAbs_EDGE); exp3.More(); exp3.Next()) {
TopoDS_Edge edge = TopoDS::Edge(exp3.Current());
std::vector<gp_Pnt> edge_points;
for (TopExp_Explorer exp2(edge, TopAbs_VERTEX); exp2.More(); exp2.Next()) {
TopoDS_Vertex V2 = TopoDS::Vertex(exp2.Current());
gp_Pnt P2 = BRep_Tool::Pnt(V2);
edge_points.push_back(P2);
}
if (edge_points.size() != 2) continue;
if (edge_points[0].IsEqual(P1, tol)) neighbors.push_back(edge_points[1]);
else if (edge_points[1].IsEqual(P1, tol)) neighbors.push_back(edge_points[0]);
}
// There should be two of these
if (neighbors.size() != 2) return false;
// Now find the non neighboring points
std::vector<gp_Pnt> non_neighbors;
for (TopExp_Explorer exp2(wire, TopAbs_VERTEX); exp2.More(); exp2.Next()) {
TopoDS_Vertex V2 = TopoDS::Vertex(exp2.Current());
gp_Pnt P2 = BRep_Tool::Pnt(V2);
if (P1.IsEqual(P2, tol)) continue;
bool found = false;
for (std::vector<gp_Pnt>::const_iterator it = neighbors.begin(); it != neighbors.end(); ++it) {
if ((*it).IsEqual(P2, tol)) { found = true; break; }
}
if (!found) non_neighbors.push_back(P2);
}
// Calculate the angle between the two edges of the vertex
gp_Dir dir1(neighbors[0].XYZ() - P1.XYZ());
gp_Dir dir2(neighbors[1].XYZ() - P1.XYZ());
const double angle = acos(dir1.Dot(dir2)) + 0.0001;
// Now for the non-neighbors see whether a greater angle can be found with one of the edges
for (std::vector<gp_Pnt>::const_iterator it = non_neighbors.begin(); it != non_neighbors.end(); ++it) {
gp_Dir dir3((*it).XYZ() - P1.XYZ());
const double angle2 = acos(dir3.Dot(dir1));
const double angle3 = acos(dir3.Dot(dir2));
if (angle2 > angle || angle3 > angle) return false;
}
}
return true;
}
TopoDS_Shape IfcGeom::util::halfspace_from_plane(const gp_Pln& pln, const gp_Pnt& cent) {
TopoDS_Face face = BRepBuilderAPI_MakeFace(pln).Face();
return BRepPrimAPI_MakeHalfSpace(face, cent).Solid();
}
gp_Pln IfcGeom::util::plane_from_face(const TopoDS_Face& face) {
BRepGProp_Face prop(face);
Standard_Real u1, u2, v1, v2;
prop.Bounds(u1, u2, v1, v2);
Standard_Real u = (u1 + u2) / 2.0;
Standard_Real v = (v1 + v2) / 2.0;
gp_Pnt p;
gp_Vec n;
prop.Normal(u, v, p, n);
return gp_Pln(p, n);
}
gp_Pnt IfcGeom::util::point_above_plane(const gp_Pln& pln, bool agree) {
if (agree) {
return pln.Location().Translated(pln.Axis().Direction());
} else {
return pln.Location().Translated(-pln.Axis().Direction());
}
}
bool IfcGeom::util::is_compound(const TopoDS_Shape& shape) {
bool has_solids = TopExp_Explorer(shape, TopAbs_SOLID).More() != 0;
bool has_shells = TopExp_Explorer(shape, TopAbs_SHELL).More() != 0;
bool has_compounds = TopExp_Explorer(shape, TopAbs_COMPOUND).More() != 0;
bool has_faces = TopExp_Explorer(shape, TopAbs_FACE).More() != 0;
return has_compounds && has_faces && !has_solids && !has_shells;
}
bool IfcGeom::util::shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li) {
TopExp_Explorer exp(s, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
TopoDS_Face face = TopoDS::Face(exp.Current());
li.Append(face);
}
return true;
}
bool IfcGeom::util::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape, double tol) {
TopTools_ListOfShape face_list;
shape_to_face_list(compound, face_list);
if (face_list.Extent() == 0) {
return false;
}
return create_solid_from_faces(face_list, shape, tol);
}
bool IfcGeom::util::create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& shape, double tol, bool force_sewing) {
bool valid_shell = false;
if (face_list.Extent() == 1) {
shape = face_list.First();
// A bit dubious what to return here.
return true;
} else if (face_list.Extent() == 0) {
return false;
}
TopTools_ListIteratorOfListOfShape face_iterator;
bool has_shared_edges = false;
TopTools_MapOfShape edge_set;
// In case there are wire interesections or failures in non-planar wire triangulations
// the idea is to let occt do an exhaustive search of edge partners. But we have not
// found a case where this actually improves boolean ops later on.
// if (!faceset_helper_ || !faceset_helper_->non_manifold()) {
for (face_iterator.Initialize(face_list); !force_sewing && face_iterator.More(); face_iterator.Next()) {
// As soon as is detected one of the edges is shared, the assumption is made no
// additional sewing is necessary.
if (!has_shared_edges) {
TopExp_Explorer exp(face_iterator.Value(), TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
if (edge_set.Contains(exp.Current())) {
has_shared_edges = true;
break;
}
edge_set.Add(exp.Current());
}
}
}
BRepOffsetAPI_Sewing sewing_builder;
sewing_builder.SetTolerance(tol);
sewing_builder.SetMaxTolerance(tol);
sewing_builder.SetMinTolerance(tol);
BRep_Builder builder;
TopoDS_Shell shell;
builder.MakeShell(shell);
for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) {
if (has_shared_edges) {
builder.Add(shell, face_iterator.Value());
} else {
sewing_builder.Add(face_iterator.Value());
}
}
try {
if (has_shared_edges) {
ShapeFix_Shell fix;
fix.FixFaceOrientation(shell);
shape = fix.Shape();
} else {
sewing_builder.Perform();
shape = sewing_builder.SewedShape();
}
BRepCheck_Analyzer ana(shape);
valid_shell = ana.IsValid();
if (!valid_shell) {
ShapeFix_Shape sfs(shape);
sfs.Perform();
shape = sfs.Shape();
BRepCheck_Analyzer reana(shape);
valid_shell = reana.IsValid();
}
valid_shell &= util::count(shape, TopAbs_SHELL) > 0;
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error sewing shell");
}
} catch (...) {
Logger::Error("Unknown error sewing shell");
}
if (valid_shell) {
TopoDS_Shape complete_shape;
TopExp_Explorer exp(shape, TopAbs_SHELL);
for (; exp.More(); exp.Next()) {
TopoDS_Shape result_shape = exp.Current();
try {
ShapeFix_Solid solid;
solid.SetMaxTolerance(tol);
TopoDS_Solid solid_shape = solid.SolidFromShell(TopoDS::Shell(exp.Current()));
// @todo: BRepClass3d_SolidClassifier::PerformInfinitePoint() is done by SolidFromShell
// and this is done again, to be able to catch errors during this process.
// This is double work that should be avoided.
if (!solid_shape.IsNull()) {
try {
BRepClass3d_SolidClassifier classifier(solid_shape);
result_shape = solid_shape;
classifier.PerformInfinitePoint(tol);
if (classifier.State() == TopAbs_IN) {
shape.Reverse();
}
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error classifying solid");
}
} catch (...) {
Logger::Error("Unknown error classifying solid");
}
}
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error creating solid");
}
} catch (...) {
Logger::Error("Unknown error creating solid");
}
if (complete_shape.IsNull()) {
complete_shape = result_shape;
} else {
BRep_Builder B;
if (complete_shape.ShapeType() != TopAbs_COMPOUND) {
TopoDS_Compound C;
B.MakeCompound(C);
B.Add(C, complete_shape);
complete_shape = C;
Logger::Warning("Multiple components in IfcConnectedFaceSet");
}
B.Add(complete_shape, result_shape);
}
}
TopExp_Explorer loose_faces(shape, TopAbs_FACE, TopAbs_SHELL);
for (; loose_faces.More(); loose_faces.Next()) {
BRep_Builder B;
if (complete_shape.ShapeType() != TopAbs_COMPOUND) {
TopoDS_Compound C;
B.MakeCompound(C);
B.Add(C, complete_shape);
complete_shape = C;
Logger::Warning("Loose faces in IfcConnectedFaceSet");
}
B.Add(complete_shape, loose_faces.Current());
}
shape = complete_shape;
} else {
Logger::Error("Failed to sew faceset");
}
return valid_shell;
}
@@ -0,0 +1,71 @@
#ifndef BASE_UTILS_H
#define BASE_UTILS_H
#include <gp_Ax3.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <TopTools_ListOfShape.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <Geom_Curve.hxx>
#include <Geom_Surface.hxx>
#include <vector>
namespace IfcGeom {
namespace util {
int count(const TopoDS_Shape&, TopAbs_ShapeEnum, bool unique = false);
int surface_genus(const TopoDS_Shape&);
bool is_manifold(const TopoDS_Shape& a);
// For axis placements detect equality early in order for the
// relatively computionaly expensive gp_Trsf calculation to be skipped
bool axis_equal(const gp_Ax3& a, const gp_Ax3& b, double tolerance);
bool axis_equal(const gp_Ax2d& a, const gp_Ax2d& b, double tolerance);
bool is_identity(const gp_Trsf2d& t, double tolerance);
bool is_identity(const gp_GTrsf2d& t, double tolerance);
bool is_identity(const gp_Trsf& t, double tolerance);
bool is_identity(const gp_GTrsf& t, double tolerance);
gp_Trsf combine_offset_and_rotation(const gp_Vec &offset, const gp_Quaternion& rotation);
bool is_nested_compound_of_solid(const TopoDS_Shape& s, int depth = 0);
bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid, double tol);
bool shape_to_face_list(const TopoDS_Shape& s, TopTools_ListOfShape& li);
bool create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& solid, double tol, bool force_sewing = false);
bool is_compound(const TopoDS_Shape& shape);
bool is_convex(const TopoDS_Wire& wire, double tol);
TopoDS_Shape halfspace_from_plane(const gp_Pln& pln, const gp_Pnt& cent);
gp_Pln plane_from_face(const TopoDS_Face& face);
gp_Pnt point_above_plane(const gp_Pln& pln, bool agree = true);
bool fit_halfspace(const TopoDS_Shape& a, const TopoDS_Shape& b, TopoDS_Shape& box, double& height, double tol);
const Handle_Geom_Curve intersect(const Handle_Geom_Surface&, const Handle_Geom_Surface&);
const Handle_Geom_Curve intersect(const Handle_Geom_Surface&, const TopoDS_Face&);
const Handle_Geom_Curve intersect(const TopoDS_Face&, const Handle_Geom_Surface&);
bool intersect(const Handle_Geom_Curve&, const Handle_Geom_Surface&, gp_Pnt&);
bool intersect(const Handle_Geom_Curve&, const TopoDS_Face&, gp_Pnt&);
bool intersect(const Handle_Geom_Curve&, const TopoDS_Shape&, std::vector<gp_Pnt>&);
bool intersect(const Handle_Geom_Surface&, const TopoDS_Shape&, std::vector< std::pair<Handle_Geom_Surface, Handle_Geom_Curve> >&);
bool closest(const gp_Pnt&, const std::vector<gp_Pnt>&, gp_Pnt&);
bool project(const Handle_Geom_Curve&, const gp_Pnt&, gp_Pnt& p, double& u, double& d);
bool project(const Handle_Geom_Surface&, const TopoDS_Shape&, double& u1, double& v1, double& u2, double& v2, double widen = 0.1);
double shape_volume(const TopoDS_Shape& s);
double face_area(const TopoDS_Face& f);
TopoDS_Shape apply_transformation(const TopoDS_Shape&, const gp_Trsf&);
TopoDS_Shape apply_transformation(const TopoDS_Shape&, const gp_GTrsf&);
}
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,101 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
#ifndef BOOLEAN_UTILS_H
#define BOOLEAN_UTILS_H
#include <TopoDS_Shape.hxx>
#include <TopTools_ListOfShape.hxx>
#include <Geom_Surface.hxx>
#include <TopoDS_Face.hxx>
#include <BRepTopAdaptor_FClass2d.hxx>
#include <BRep_Tool.hxx>
#include <BRepTools.hxx>
#include <TopTools_IndexedMapOfShape.hxx>
#include <BOPAlgo_Operation.hxx>
namespace IfcGeom {
namespace util {
void copy_operand(const TopTools_ListOfShape& l, TopTools_ListOfShape& r);
TopoDS_Shape copy_operand(const TopoDS_Shape& s);
double min_edge_length(const TopoDS_Shape& a);
double min_vertex_edge_distance(const TopoDS_Shape& a, double min_search, double max_search);
class points_on_planar_face_generator {
private:
const TopoDS_Face& f_;
Handle(Geom_Surface) plane_;
BRepTopAdaptor_FClass2d cls_;
double u0, u1, v0, v1;
int i, j;
bool inset_;
static const int N = 10;
public:
points_on_planar_face_generator(const TopoDS_Face& f, bool inset = false)
: f_(f)
, plane_(BRep_Tool::Surface(f_))
, cls_(f_, BRep_Tool::Tolerance(f_))
, i((int)inset), j((int)inset)
, inset_(inset)
{
BRepTools::UVBounds(f_, u0, u1, v0, v1);
}
void reset();
bool operator()(gp_Pnt& p);
};
bool faces_overlap(const TopoDS_Face& f, const TopoDS_Face& g);
double min_face_face_distance(const TopoDS_Shape& a, double max_search);
int bounding_box_overlap(double p, const TopoDS_Shape& a, const TopTools_ListOfShape& b, TopTools_ListOfShape& c);
bool get_edge_axis(const TopoDS_Edge& e, gp_Ax1& ax);
bool is_subset(const TopTools_IndexedMapOfShape& lhs, const TopTools_IndexedMapOfShape& rhs);
bool is_extrusion(const gp_Vec& v, const TopoDS_Shape& s, TopoDS_Face& base, std::pair<double, double>& interval);
int eliminate_touching_operands(double prec, const TopoDS_Shape& a, const TopTools_ListOfShape& bs, TopTools_ListOfShape& c);
TopoDS_Shape unify(const TopoDS_Shape& s, double tolerance);
bool boolean_subtraction_2d_using_builder(const TopoDS_Shape& a_input, const TopTools_ListOfShape& b_input, TopoDS_Shape& result, double eps);
struct boolean_settings {
bool debug, attempt_2d;
double precision;
};
bool boolean_operation(const boolean_settings& settings, const TopoDS_Shape&, const TopTools_ListOfShape&, BOPAlgo_Operation, TopoDS_Shape&, double fuzziness = -1.);
bool boolean_operation(const boolean_settings& settings, const TopoDS_Shape&, const TopoDS_Shape&, BOPAlgo_Operation, TopoDS_Shape&, double fuzziness = -1.);
const TopoDS_Shape& ensure_fit_for_subtraction(const TopoDS_Shape& shape, TopoDS_Shape& solid, double tol);
}
}
#endif
@@ -0,0 +1,22 @@
#include "face_definition.h"
#include <TopoDS.hxx>
#include <Geom_Line.hxx>
#include <BRep_Tool.hxx>
#include <TopoDS_Iterator.hxx>
/* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/
bool IfcGeom::util::is_polyhedron(const TopoDS_Wire & wire) {
double a, b;
TopLoc_Location l;
TopoDS_Iterator it(wire, false, false);
for (; it.More(); it.Next()) {
auto crv = BRep_Tool::Curve(TopoDS::Edge(it.Value()), l, a, b);
if (!crv || crv->DynamicType() != STANDARD_TYPE(Geom_Line)) {
return false;
}
}
return true;
}
@@ -0,0 +1,78 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
#ifndef FACE_DEFINITION_H
#define FACE_DEFINITION_H
#include <TopoDS_Wire.hxx>
#include <Geom_Surface.hxx>
#include <map>
#include <vector>
namespace IfcGeom {
namespace util {
/* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/
bool is_polyhedron(const TopoDS_Wire& wire);
/* A temporary structure to store the intermediate data for the face conversion */
class face_definition {
private:
Handle(Geom_Surface) surface_;
std::vector<TopoDS_Wire> wires_;
bool all_outer_;
public:
face_definition() : surface_(), all_outer_(false) {}
typedef std::vector<TopoDS_Wire>::const_iterator wire_it;
bool& all_outer() {
return all_outer_;
}
bool all_outer() const {
return all_outer_;
}
Handle(Geom_Surface)& surface() {
return surface_;
}
const Handle(Geom_Surface)& surface() const {
return surface_;
}
std::vector<TopoDS_Wire>& wires() {
return wires_;
}
const TopoDS_Wire& outer_wire() const {
return wires_.front();
}
std::pair<wire_it, wire_it> inner_wires() const {
return { wires_.begin() + 1, wires_.end() };
}
};
}
}
#endif
@@ -0,0 +1,461 @@
#include "layerset.h"
#include "base_utils.h"
#include "boolean_utils.h"
#include "../ifcparse/IfcLogger.h"
#include <BRep_Tool.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopExp_Explorer.hxx>
#include <TopTools_ListOfShape.hxx>
#include <Bnd_Box.hxx>
#include <BRepBuilderAPI_MakeShell.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
#include <BRepAlgoAPI_Splitter.hxx>
#include <BRepPrimAPI_MakeHalfSpace.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepOffsetAPI_Sewing.hxx>
#include <BOPAlgo_PaveFiller.hxx>
#include <Standard_Version.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <ShapeFix_Shape.hxx>
#include <NCollection_IncAllocator.hxx>
namespace {
void subshapes(const TopoDS_Shape& in, std::list<TopoDS_Shape>& out) {
TopoDS_Iterator sit(in);
for (; sit.More(); sit.Next()) {
out.push_back(sit.Value());
}
}
#if OCC_VERSION_HEX >= 0x70200
bool split(const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double eps, std::vector<TopoDS_Shape>& slices) {
if (operands.Extent() < 2) {
// Needs to have at least two cutting surfaces for the ordering based on surface containment to work.
return false;
}
BRepAlgoAPI_Splitter split;
TopTools_ListOfShape input_list;
input_list.Append(input);
split.SetArguments(input_list);
split.SetTools(operands);
split.SetNonDestructive(true);
split.SetFuzzyValue(eps);
split.Build();
if (!split.IsDone()) {
return false;
} else {
std::map<Geom_Surface*, int> surfaces;
// NB 1, since first surface has been excluded
int i = 1;
for (TopTools_ListIteratorOfListOfShape it(operands); it.More(); it.Next(), ++i) {
TopExp_Explorer exp(it.Value(), TopAbs_FACE);
for (; exp.More(); exp.Next()) {
surfaces.insert(std::make_pair(BRep_Tool::Surface(TopoDS::Face(exp.Current())).get(), i));
}
}
auto result_shape = split.Shape();
std::list<TopoDS_Shape> subs;
subshapes(result_shape, subs);
// Sometimes there is more nesting of compounds, so when we find a single compound we again try to explode it into a list.
if (subs.size() == 1 && (subs.front().ShapeType() == TopAbs_COMPSOLID || subs.front().ShapeType() == TopAbs_COMPOUND)) {
auto s = subs.front();
subs.clear();
subshapes(s, subs);
}
// Initialize storage
slices.resize(subs.size());
for (auto& s : subs) {
// Iterate over the faces of solid to find correspondence to original
// splitting surfaces. For the outmost slices, there will be a single
// corresponding surface, because the outmost surfaces that align with
// the body geometry have not been added as operands. For intermediate
// slices, two surface indices should be find that should be next to
// each other in the array of input surfaces.
TopExp_Explorer exp(s, TopAbs_FACE);
int min = std::numeric_limits<int>::max();
int max = std::numeric_limits<int>::min();
for (; exp.More(); exp.Next()) {
auto ssrf = BRep_Tool::Surface(TopoDS::Face(exp.Current()));
auto it = surfaces.find(ssrf.get());
if (it != surfaces.end()) {
if (it->second < min) {
min = it->second;
}
if (it->second > max) {
max = it->second;
}
}
}
int idx = std::numeric_limits<int>::max();
if (min != std::numeric_limits<int>::max()) {
if (min == 1 && max == 1) {
idx = 0;
} else if (min + 1 == max || min == max) {
idx = min;
}
}
if (idx < (int)slices.size()) {
if (slices[idx].IsNull()) {
slices[idx] = s;
continue;
}
}
Logger::Error("Unable to map layer geometry to material index");
return false;
}
}
return true;
}
#else
bool split(const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double, std::vector<TopoDS_Shape>& slices) {
TopTools_ListIteratorOfListOfShape it(operands);
TopoDS_Shape i = input;
for (; it.More(); it.Next()) {
const TopoDS_Shape& s = it.Value();
TopoDS_Shape a, b;
Handle(Geom_Surface) surf;
if (s.ShapeType() == TopAbs_FACE) {
surf = BRep_Tool::Surface(TopoDS::Face(s));
}
if ((s.ShapeType() == TopAbs_FACE && IfcGeom::util::split_solid_by_surface(i, surf, a, b)) ||
(s.ShapeType() == TopAbs_SHELL && IfcGeom::util::split_solid_by_shell(i, s, a, b))) {
slices.push_back(b);
i = a;
} else {
return false;
}
}
slices.push_back(i);
return true;
}
#endif
}
bool IfcGeom::util::apply_folded_layerset(const IfcRepresentationShapeItems& items, const std::vector< std::vector<Handle_Geom_Surface> >& surfaces, const std::vector<std::shared_ptr<const SurfaceStyle>>& styles, IfcRepresentationShapeItems& result, double tol) {
Bnd_Box bb;
TopoDS_Shape input;
flatten_shape_list(items, input, false, tol);
typedef std::vector< std::vector<Handle_Geom_Surface> > folded_surfaces_t;
typedef std::vector< std::pair< TopoDS_Face, std::pair<gp_Pnt, gp_Pnt> > > faces_with_mass_t;
TopTools_ListOfShape shells;
for (folded_surfaces_t::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) {
if (it->empty()) {
continue;
} else if (it->size() == 1) {
const Handle_Geom_Surface& surface = (*it)[0];
double u1, v1, u2, v2;
if (!project(surface, input, u1, v1, u2, v2)) {
continue;
}
shells.Append(BRepBuilderAPI_MakeShell(surface, u1, v1, u2, v2).Shell());
} else {
faces_with_mass_t solids;
for (folded_surfaces_t::value_type::const_iterator jt = it->begin(); jt != it->end(); ++jt) {
const Handle_Geom_Surface& surface = *jt;
double u1, v1, u2, v2;
if (!project(surface, input, u1, v1, u2, v2)) {
continue;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(surface, u1, u2, v1, v2, 1.e-7).Face();
gp_Pnt p, p1, p2; gp_Vec vu, vv, n;
surface->D1((u1 + u2) / 2., (v1 + v2) / 2., p, vu, vv);
n = vu ^ vv;
p1 = p.Translated(n);
p2 = p.Translated(-n);
solids.push_back(std::make_pair(face, std::make_pair(p1, p2)));
}
if (solids.empty()) {
continue;
}
faces_with_mass_t::iterator jt = solids.begin();
TopoDS_Face& A = jt->first;
TopoDS_Shape An = BRepPrimAPI_MakeHalfSpace(A, jt->second.second).Solid();
for (++jt; jt != solids.end(); ++jt) {
TopoDS_Face& B = jt->first;
TopoDS_Shape Bn = BRepPrimAPI_MakeHalfSpace(B, jt->second.second).Solid();
TopoDS_Shape a = BRepAlgoAPI_Cut(A, Bn);
if (util::count(a, TopAbs_FACE) == 1) {
A = TopoDS::Face(TopExp_Explorer(a, TopAbs_FACE).Current());
}
TopoDS_Shape b = BRepAlgoAPI_Cut(B, An);
if (util::count(b, TopAbs_FACE) == 1) {
B = TopoDS::Face(TopExp_Explorer(b, TopAbs_FACE).Current());
}
}
BRepOffsetAPI_Sewing builder;
for (faces_with_mass_t::const_iterator kt = solids.begin(); kt != solids.end(); ++kt) {
builder.Add(kt->first);
}
builder.Perform();
TopoDS_Shape s = builder.SewedShape();
if (s.ShapeType() == TopAbs_SHELL) {
shells.Append(TopoDS::Shell(s));
} else {
Logger::Error("Expected shell type in layerset processing");
return false;
}
}
}
if (shells.Extent() == 0) {
return false;
} else if (shells.Extent() == 1) {
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
TopoDS_Shape a, b;
if (split_solid_by_shell(it->Shape(), shells.First(), a, b, tol)) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), b, !!styles[0] ? styles[0] : it->StylePtr()));
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), a, !!styles[1] ? styles[1] : it->StylePtr()));
} else {
continue;
}
}
return true;
} else {
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
const TopoDS_Shape& s = it->Shape();
TopoDS_Solid sld;
ensure_fit_for_subtraction(s, sld, tol);
std::vector<TopoDS_Shape> slices;
if (split(it->Shape(), shells, tol, slices) && slices.size() == styles.size()) {
for (size_t i = 0; i < slices.size(); ++i) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), slices[i], !!styles[i] ? styles[i] : it->StylePtr()));
}
} else {
return false;
}
}
return true;
}
}
bool IfcGeom::util::apply_layerset(const IfcRepresentationShapeItems& items, const std::vector<Handle_Geom_Surface>& surfaces, const std::vector<std::shared_ptr<const SurfaceStyle>>& styles, IfcRepresentationShapeItems& result, double tol) {
if (surfaces.size() < 3) {
return false;
} else if (surfaces.size() == 3) {
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
TopoDS_Shape a, b;
if (split_solid_by_surface(it->Shape(), surfaces[1], a, b, tol)) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), b, !!styles[0] ? styles[0] : it->StylePtr()));
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), a, !!styles[1] ? styles[1] : it->StylePtr()));
} else {
continue;
}
}
return true;
} else {
/*
// Determine whether sequence of surfaces is consistent with surface normal, so that
// layer operations are applied in the correct order. This seems to be always the case.
Bnd_Box bb;
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
BRepBndLib::Add(it->Shape(), bb);
}
double x1, y1, z1, x2, y2, z2;
bb.Get(x1, y1, z1, x2, y2, z2);
gp_Pnt p1(x1, y1, z1);
gp_Pnt p2(x2, y2, z2);
gp_Pnt avg = (p1.XYZ() + p2.XYZ()) / 2.;
ShapeAnalysis_Surface sas1(surfaces[0]);
ShapeAnalysis_Surface sas2(surfaces[1]);
const gp_Pnt2d uv = sas1.ValueOfUV(avg, 1e-3);
gp_Pnt ps1, ps2, mass;
gp_Vec du1, dv1, du2, dv2;
surfaces[0]->D1(uv.X(), uv.Y(), ps1, du1, dv1);
const gp_Vec n1 = dv1.XYZ() ^ du1.XYZ();
const bool reversed = gp_Dir(ps2.XYZ() - ps1.XYZ()).Dot(n1) < 0.;
surfaces[surfaces.size() - 1]->D0(uv.X(), uv.Y(), mass);
mass.ChangeCoord() += n1.XYZ();
*/
for (IfcRepresentationShapeItems::const_iterator it = items.begin(); it != items.end(); ++it) {
const TopoDS_Shape& s = it->Shape();
TopoDS_Solid sld;
ensure_fit_for_subtraction(s, sld, tol);
TopTools_ListOfShape operands;
for (unsigned i = 1; i < surfaces.size() - 1; ++i) {
double u1, v1, u2, v2;
if (!project(surfaces[i], sld, u1, v1, u2, v2)) {
return false;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(surfaces[i], u1, u2, v1, v2, 1.e-7).Face();
operands.Append(face);
}
/*
// enable this is you want to see how IfcOpenShell has placed the layer surfaces
for (auto& x : operands) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), x, nullptr));
}
*/
std::vector<TopoDS_Shape> slices;
if (split(it->Shape(), operands, tol, slices) && slices.size() == styles.size()) {
for (size_t i = 0; i < slices.size(); ++i) {
result.push_back(IfcRepresentationShapeItem(it->ItemId(), it->Placement(), slices[i], !!styles[i] ? styles[i] : it->StylePtr()));
}
} else {
return false;
}
}
return true;
}
}
bool IfcGeom::util::split_solid_by_surface(const TopoDS_Shape& input, const Handle_Geom_Surface& surface, TopoDS_Shape& front, TopoDS_Shape& back, double tol) {
// Use an unbounded surface, that isolate part of the input shape,
// to split this shape into two parts. Make sure that the addition
// of the two result volumes matches that of the input.
double u1, v1, u2, v2;
if (!project(surface, input, u1, v1, u2, v2)) {
return false;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(surface, u1, u2, v1, v2, 1.e-7).Face();
gp_Pnt p, p1, p2; gp_Vec vu, vv, n;
surface->D1((u1 + u2) / 2., (v1 + v2) / 2., p, vu, vv);
n = vu ^ vv;
p1 = p.Translated(-n);
TopoDS_Solid solid = BRepPrimAPI_MakeHalfSpace(face, p1).Solid();
const bool b = split_solid_by_shell(input, solid, front, back, tol);
return b;
}
bool IfcGeom::util::split_solid_by_shell(const TopoDS_Shape& input, const TopoDS_Shape& shell, TopoDS_Shape& front, TopoDS_Shape& back, double tol) {
// Use a shell, typically one or more connected faces, that isolate part
// of the input shape, to split this shape into two parts. Make sure that
// the addition of the two result volumes matches that of the input.
TopoDS_Solid solid;
if (shell.ShapeType() == TopAbs_SHELL) {
solid = BRepBuilderAPI_MakeSolid(TopoDS::Shell(shell)).Solid();
} else if (shell.ShapeType() == TopAbs_SOLID) {
solid = TopoDS::Solid(shell);
} else {
return false;
}
#if OCC_VERSION_HEX >= 0x70300
TopTools_ListOfShape shapes;
#else
BOPCol_ListOfShape shapes;
#endif
shapes.Append(input);
shapes.Append(solid);
BOPAlgo_PaveFiller filler(new NCollection_IncAllocator); // TODO: Does this need to be freed?
filler.SetArguments(shapes);
filler.Perform();
front = BRepAlgoAPI_Cut(input, solid, filler);
back = BRepAlgoAPI_Common(input, solid, filler);
bool is_null[2];
for (int i = 0; i < 2; ++i) {
TopoDS_Shape& shape = i == 0 ? front : back;
const bool result_is_null = is_null[i] = shape.IsNull() != 0;
if (result_is_null) {
continue;
}
try {
ShapeFix_Shape fix(shape);
if (fix.Perform()) {
shape = fix.Shape();
}
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error performing fixes");
}
} catch (...) {
Logger::Error("Unknown error performing fixes");
}
BRepCheck_Analyzer analyser(shape);
bool is_valid = analyser.IsValid() != 0;
if (!is_valid) {
return false;
}
}
if (is_null[0] || is_null[1]) {
Logger::Message(Logger::LOG_ERROR, "Null result obtained from layerset slicing");
if (is_null[0] && is_null[1]) {
return false;
}
}
const double ab = shape_volume(input);
const double a = shape_volume(front);
const double b = shape_volume(back);
return std::fabs(ab - (a + b)) < tol;
}
@@ -0,0 +1,23 @@
#ifndef LAYERSET_H
#define LAYERSET_H
#include "IfcRepresentationShapeItem.h"
#include <Geom_Surface.hxx>
#include <list>
#include <vector>
namespace IfcGeom {
namespace util {
bool apply_layerset(const IfcRepresentationShapeItems&, const std::vector<Handle_Geom_Surface>&, const std::vector<std::shared_ptr<const SurfaceStyle>>&, IfcRepresentationShapeItems&, double tol);
bool apply_folded_layerset(const IfcRepresentationShapeItems&, const std::vector< std::vector<Handle_Geom_Surface> >&, const std::vector<std::shared_ptr<const SurfaceStyle>>&, IfcRepresentationShapeItems&, double tol);
bool split_solid_by_surface(const TopoDS_Shape&, const Handle_Geom_Surface&, TopoDS_Shape&, TopoDS_Shape&, double tol);
bool split_solid_by_shell(const TopoDS_Shape&, const TopoDS_Shape& s, TopoDS_Shape&, TopoDS_Shape&, double tol);
}
}
#endif
@@ -0,0 +1,357 @@
#include "sweep_utils.h"
#include <gp_Ax2.hxx>
#include <Geom_Line.hxx>
#include <Geom_Circle.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Compound.hxx>
#include <BRep_Tool.hxx>
#include <BRep_Builder.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepPrimAPI_MakeRevol.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepOffsetAPI_MakePipeShell.hxx>
#include "../ifcparse/IfcLogger.h"
#include "../ifcgeom_schema_agnostic/Kernel.h"
#include "../ifcgeom_schema_agnostic/base_utils.h"
bool IfcGeom::util::wire_is_c1_continuous(const TopoDS_Wire & w, double tol) {
// NB Note that c0 continuity is NOT checked!
TopTools_IndexedDataMapOfShapeListOfShape map;
TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, map);
for (int i = 1; i <= map.Extent(); ++i) {
const auto& li = map.FindFromIndex(i);
if (li.Extent() == 2) {
const TopoDS_Vertex& v = TopoDS::Vertex(map.FindKey(i));
const TopoDS_Edge& e0 = TopoDS::Edge(li.First());
const TopoDS_Edge& e1 = TopoDS::Edge(li.Last());
double u0 = BRep_Tool::Parameter(v, e0);
double u1 = BRep_Tool::Parameter(v, e1);
double _, __;
Handle(Geom_Curve) c0 = BRep_Tool::Curve(e0, _, __);
Handle(Geom_Curve) c1 = BRep_Tool::Curve(e1, _, __);
gp_Pnt p;
gp_Vec v0, v1;
c0->D1(u0, p, v0);
c1->D1(u1, p, v1);
if (1. - std::abs(v0.Normalized().Dot(v1.Normalized())) > tol) {
return false;
}
}
}
return true;
}
bool IfcGeom::util::wire_to_ax(const TopoDS_Wire & wire, gp_Ax2 & directrix) {
gp_Pnt directrix_origin;
gp_Vec directrix_tangent;
TopoDS_Edge edge;
// Find first edge
TopoDS_Vertex v0, v1;
TopExp::Vertices(wire, v0, v1);
TopTools_IndexedDataMapOfShapeListOfShape map;
TopExp::MapShapesAndAncestors(wire, TopAbs_VERTEX, TopAbs_EDGE, map);
if (v0.IsSame(v1) && map.Contains(v0) && map.FindFromKey(v0).Extent() == 2) {
// Closed wire, with more than 1 edges
auto es = map.FindFromKey(v0);
auto e1 = TopoDS::Edge(es.First());
auto e2 = TopoDS::Edge(es.Last());
double u0, u1;
gp_Vec accum;
Handle(Geom_Curve) crv = BRep_Tool::Curve(e1, u0, u1);
crv->D1(TopExp::FirstVertex(e1).IsSame(v0) ? u0 : u1, directrix_origin, directrix_tangent);
accum += directrix_tangent;
crv = BRep_Tool::Curve(e2, u0, u1);
crv->D1(TopExp::FirstVertex(e2).IsSame(v0) ? u0 : u1, directrix_origin, directrix_tangent);
accum += directrix_tangent;
directrix_tangent = accum;
} else if (map.Contains(v0) && map.FindFromKey(v0).Extent() == 1) {
edge = TopoDS::Edge(map.FindFromKey(v0).First());
double u0, u1;
Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u0, u1);
crv->D1(u0, directrix_origin, directrix_tangent);
} else {
Logger::Error("Unable to locate first edge");
return false;
}
directrix = gp_Ax2(directrix_origin, directrix_tangent);
return true;
}
bool IfcGeom::util::is_single_linear_edge(const TopoDS_Wire & wire) {
TopExp_Explorer exp(wire, TopAbs_EDGE);
if (!exp.More()) {
return false;
}
TopoDS_Edge e = TopoDS::Edge(exp.Current());
exp.Next();
if (exp.More()) {
return false;
}
double u, v;
Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v);
return crv->DynamicType() == STANDARD_TYPE(Geom_Line);
}
bool IfcGeom::util::is_single_circular_edge(const TopoDS_Wire & wire) {
TopExp_Explorer exp(wire, TopAbs_EDGE);
if (!exp.More()) {
return false;
}
TopoDS_Edge e = TopoDS::Edge(exp.Current());
exp.Next();
if (exp.More()) {
return false;
}
double u, v;
Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v);
return crv->DynamicType() == STANDARD_TYPE(Geom_Circle);
}
void IfcGeom::util::process_sweep_as_extrusion(const TopoDS_Wire & wire, const TopoDS_Wire & section, TopoDS_Shape & result) {
TopExp_Explorer exp(wire, TopAbs_EDGE);
TopoDS_Edge e = TopoDS::Edge(exp.Current());
double u, v;
Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v);
const auto& dir = Handle(Geom_Line)::DownCast(crv)->Position().Direction();
// OCCT line is normalized so diff in parametric coords equals length
const double depth = std::abs(u - v);
// @todo we could be extruding the wire only when we know this is an intermediate edge.
TopoDS_Face face = BRepBuilderAPI_MakeFace(section).Face();
result = BRepPrimAPI_MakePrism(face, depth*dir).Shape();
}
void IfcGeom::util::process_sweep_as_revolution(const TopoDS_Wire & wire, const TopoDS_Wire & section, TopoDS_Shape & result) {
TopExp_Explorer exp(wire, TopAbs_EDGE);
TopoDS_Edge e = TopoDS::Edge(exp.Current());
double u, v;
Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v);
auto circ = Handle(Geom_Circle)::DownCast(crv);
// @todo we could be extruding the wire only when we know this is an intermediate edge.
const double depth = std::abs(u - v);
TopoDS_Face face = BRepBuilderAPI_MakeFace(section).Face();
result = BRepPrimAPI_MakeRevol(face, circ->Axis(), depth).Shape();
}
void IfcGeom::util::process_sweep_as_pipe(const TopoDS_Wire & wire, const TopoDS_Wire & section, TopoDS_Shape & result, bool force_transformed) {
// This tolerance is fairly high due to the linear edge substitution for small (or large radii) conical curves.
const bool is_continuous = wire_is_c1_continuous(wire, 1.e-2);
BRepOffsetAPI_MakePipeShell builder(wire);
builder.Add(section);
builder.SetTransitionMode(is_continuous || force_transformed ? BRepBuilderAPI_Transformed : BRepBuilderAPI_RightCorner);
try {
builder.Build();
} catch (Standard_Failure& e) {
// We fallback to BRepBuilderAPI_Transformed, but likely with visual artefacts.
if (!(is_continuous || force_transformed)) {
return process_sweep_as_pipe(wire, section, result, true);
} else {
throw e;
}
}
builder.MakeSolid();
result = builder.Shape();
}
void IfcGeom::util::sort_edges(const TopoDS_Wire & wire, std::vector<TopoDS_Edge>& sorted_edges) {
TopTools_IndexedDataMapOfShapeListOfShape map;
TopExp::MapShapesAndAncestors(wire, TopAbs_VERTEX, TopAbs_EDGE, map);
for (int i = 1; i <= map.Extent(); ++i) {
if (map.FindFromIndex(i).Extent() > 2) {
Logger::Warning("Self-intersecting Directrix");
}
}
std::set<TopoDS_TShape*> seen;
auto num_edges = count(wire, TopAbs_EDGE);
TopoDS_Vertex v0, v1;
// @todo this creates the ancestor map twice
TopExp::Vertices(wire, v0, v1);
bool ignore_first_equality_because_closed = v0.IsSame(v1);
// @todo this probably still does not work on a closed wire consisting of one (circular) edge.
while ((int)sorted_edges.size() < num_edges &&
(!v0.IsSame(v1) || ignore_first_equality_because_closed)) {
ignore_first_equality_because_closed = false;
if (!map.Contains(v0)) {
throw std::runtime_error("Disconnected vertex");
}
const TopTools_ListOfShape& es = map.FindFromKey(v0);
TopoDS_Vertex ve0, ve1;
TopTools_ListIteratorOfListOfShape it(es);
bool added = false;
for (; it.More(); it.Next()) {
const TopoDS_Edge& e = TopoDS::Edge(it.Value());
TopExp::Vertices(e, ve0, ve1, true);
if (ve0.IsSame(v0) && seen.find(&*e.TShape()) == seen.end()) {
sorted_edges.push_back(e);
v0 = ve1;
added = true;
seen.insert(&*e.TShape());
break;
}
}
if (!added) {
throw std::runtime_error("Disconnected edge");
}
}
}
// #939: a closed loop causes failed triangulation in 7.3 and artefacts
// in 7.4 so we break up a closed wire into two equal parts.
void IfcGeom::util::break_closed(const TopoDS_Wire & wire, std::vector<TopoDS_Wire>& wires) {
std::vector<TopoDS_Edge> sorted_edges;
sort_edges(wire, sorted_edges);
if (sorted_edges.size() == 1) {
wires.push_back(wire);
return;
}
BRep_Builder B;
wires.emplace_back();
B.MakeWire(wires.back());
for (size_t i = 0; i < sorted_edges.size(); ++i) {
if (i == sorted_edges.size() / 2) {
wires.emplace_back();
B.MakeWire(wires.back());
}
const auto& e = sorted_edges[i];
B.Add(wires.back(), e);
}
}
void IfcGeom::util::segment_adjacent_non_linear(const TopoDS_Wire & wire, std::vector<TopoDS_Wire>& wires) {
std::vector<TopoDS_Edge> sorted_edges;
sort_edges(wire, sorted_edges);
BRep_Builder B;
double u, v;
wires.emplace_back();
B.MakeWire(wires.back());
for (int i = 0; i < (int)sorted_edges.size() - 1; ++i) {
const auto& e = sorted_edges[i];
Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v);
const bool is_linear = crv->DynamicType() == STANDARD_TYPE(Geom_Line);
const auto& f = sorted_edges[i + 1];
crv = BRep_Tool::Curve(f, u, v);
const bool next_is_linear = crv->DynamicType() == STANDARD_TYPE(Geom_Line);
B.Add(wires.back(), e);
if (!is_linear && !next_is_linear) {
wires.emplace_back();
B.MakeWire(wires.back());
}
}
if (!sorted_edges.empty()) {
B.Add(wires.back(), sorted_edges.back());
}
}
// @todo make this generic for other sweeps not just swept disk
void IfcGeom::util::process_sweep(const TopoDS_Wire & wire, double radius, TopoDS_Shape & result) {
std::vector<TopoDS_Wire> wires, wires_tmp;
segment_adjacent_non_linear(wire, wires_tmp);
for (auto& w : wires_tmp) {
break_closed(w, wires);
}
TopoDS_Compound C;
BRep_Builder B;
if (wires.size() > 1) {
B.MakeCompound(C);
}
for (auto& w : wires) {
TopoDS_Shape part;
gp_Ax2 directrix;
if (!wire_to_ax(w, directrix)) {
continue;
}
Handle(Geom_Circle) circle = new Geom_Circle(directrix, radius);
TopoDS_Wire section = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(circle));
if (is_single_circular_edge(w)) {
process_sweep_as_revolution(w, section, part);
} else if (is_single_linear_edge(w)) {
process_sweep_as_extrusion(w, section, part);
} else {
process_sweep_as_pipe(w, section, part);
}
if (wires.size() > 1) {
B.Add(C, part);
} else {
result = part;
}
}
if (wires.size() > 1) {
result = C;
}
/*
// Eliminate Swept Surfaces?
result = ShapeCustom::SweptToElementary(result);
// Eliminate Trimmed Surfaces?
ShapeBuild_ReShape sbrs;
BRep_Builder b;
TopExp_Explorer exp(result, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
const TopoDS_Face& f = TopoDS::Face(exp.Current());
auto S = BRep_Tool::Surface(f);
if (S->IsKind(STANDARD_TYPE(Geom_RectangularTrimmedSurface))) {
auto RTS = Handle(Geom_RectangularTrimmedSurface)::DownCast(S);
auto B = RTS->BasisSurface();
TopoDS_Shape newf = f.EmptyCopied();
// @todo Is it ok to assume no location?
b.MakeFace(TopoDS::Face(newf), B, BRep_Tool::Tolerance(f));
sbrs.Replace(f, newf);
}
}
result = sbrs.Apply(result);
*/
}
@@ -0,0 +1,59 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
#ifndef SWEEP_UTILS_H
#define SWEEP_UTILS_H
#include <TopoDS_Wire.hxx>
#include <TopoDS_Edge.hxx>
#include <vector>
namespace IfcGeom {
namespace util {
bool wire_is_c1_continuous(const TopoDS_Wire& w, double tol);
bool wire_to_ax(const TopoDS_Wire& wire, gp_Ax2& directrix);
bool is_single_linear_edge(const TopoDS_Wire& wire);
bool is_single_circular_edge(const TopoDS_Wire& wire);
void process_sweep_as_extrusion(const TopoDS_Wire& wire, const TopoDS_Wire& section, TopoDS_Shape& result);
void process_sweep_as_revolution(const TopoDS_Wire& wire, const TopoDS_Wire& section, TopoDS_Shape& result);
void process_sweep_as_pipe(const TopoDS_Wire& wire, const TopoDS_Wire& section, TopoDS_Shape& result, bool force_transformed = false);
void sort_edges(const TopoDS_Wire& wire, std::vector<TopoDS_Edge>& sorted_edges);
// #939: a closed loop causes failed triangulation in 7.3 and artefacts
// in 7.4 so we break up a closed wire into two equal parts.
void break_closed(const TopoDS_Wire& wire, std::vector<TopoDS_Wire>& wires);
void segment_adjacent_non_linear(const TopoDS_Wire& wire, std::vector<TopoDS_Wire>& wires);
// @todo make this generic for other sweeps not just swept disk
void process_sweep(const TopoDS_Wire& wire, double radius, TopoDS_Shape& result);
}
}
#endif
@@ -0,0 +1,226 @@
#include "wire_builder.h"
#include "../ifcparse/IfcLogger.h"
#include "../ifcgeom_schema_agnostic/Kernel.h"
#include <TopExp.hxx>
#include <TopoDS.hxx>
#include <BRep_Tool.hxx>
#include <BRep_Builder.hxx>
#include <ShapeBuild_ReShape.hxx>
#include <GC_MakeCircle.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <Geom_Line.hxx>
#include <Geom_Circle.hxx>
#include <GeomAdaptor_Curve.hxx>
// Returns the first edge of a wire
TopoDS_Edge IfcGeom::util::first_edge(const TopoDS_Wire & w) {
TopoDS_Vertex v1, v2;
TopExp::Vertices(w, v1, v2);
TopTools_IndexedDataMapOfShapeListOfShape wm;
TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, wm);
return TopoDS::Edge(wm.FindFromKey(v1).First());
}
// Returns new wire with the edge replaced by a linear edge with the vertex v moved to p
TopoDS_Wire IfcGeom::util::adjust(const TopoDS_Wire & w, const TopoDS_Vertex & v, const gp_Pnt & p) {
TopTools_IndexedDataMapOfShapeListOfShape map;
TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, map);
bool all_linear = true, single_circle = false, first = true;
const TopTools_ListOfShape& edges = map.FindFromKey(v);
TopTools_ListIteratorOfListOfShape it(edges);
for (; it.More(); it.Next()) {
const TopoDS_Edge& e = TopoDS::Edge(it.Value());
double _, __;
Handle(Geom_Curve) crv = BRep_Tool::Curve(e, _, __);
const bool is_line = crv->DynamicType() == STANDARD_TYPE(Geom_Line);
const bool is_circle = crv->DynamicType() == STANDARD_TYPE(Geom_Circle);
all_linear = all_linear && is_line;
single_circle = first && is_circle;
}
if (all_linear) {
BRep_Builder b;
TopoDS_Vertex v2;
b.MakeVertex(v2, p, BRep_Tool::Tolerance(v));
ShapeBuild_ReShape reshape;
reshape.Replace(v.Oriented(TopAbs_FORWARD), v2);
return TopoDS::Wire(reshape.Apply(w));
} else if (single_circle) {
TopoDS_Vertex v1, v2;
TopExp::Vertices(w, v1, v2);
gp_Pnt p1, p2, p3;
p1 = v.IsEqual(v1) ? p : BRep_Tool::Pnt(v1);
p3 = v.IsEqual(v2) ? p : BRep_Tool::Pnt(v2);
double a, b;
Handle(Geom_Curve) crv = BRep_Tool::Curve(TopoDS::Edge(edges.First()), a, b);
crv->D0((a + b) / 2., p2);
GC_MakeCircle mc(p1, p2, p3);
if (!mc.IsDone()) {
throw IfcGeom::geometry_exception("Failed to adjust circle");
}
TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(mc.Value(), p1, p3).Edge();
BRepBuilderAPI_MakeWire builder;
builder.Add(edge);
return builder.Wire();
} else {
throw IfcGeom::geometry_exception("Unexpected wire to adjust");
}
}
double IfcGeom::util::deflection_for_approximating_circle(double radius, double param) {
return -radius * std::cos(1. / 2. * param) * std::cos(param) - radius * std::sin(1. / 2. * param) * std::sin(param) + radius;
}
bool IfcGeom::util::create_edge_over_curve_with_log_messages(const Handle_Geom_Curve & crv, const double eps, const gp_Pnt & p1, const gp_Pnt & p2, TopoDS_Edge & result) {
if (crv->IsClosed() && p1.Distance(p2) <= eps) {
BRepBuilderAPI_MakeEdge me(crv);
if (me.IsDone()) {
result = me.Edge();
return true;
} else {
return false;
}
}
BRep_Builder builder;
TopoDS_Vertex v1, v2;
/// @todo project first and emit warnings accordingly
builder.MakeVertex(v1, p1, eps);
builder.MakeVertex(v2, p2, eps);
BRepBuilderAPI_MakeEdge me(crv, v1, v2);
if (!me.IsDone()) {
const double eps2 = eps * eps;
if (me.Error() == BRepBuilderAPI_PointProjectionFailed) {
GeomAdaptor_Curve GAC(crv);
const gp_Pnt* ps[2] = { &p1, &p2 };
for (int i = 0; i < 2; ++i) {
Extrema_ExtPC extrema(*ps[i], GAC);
if (extrema.IsDone()) {
int n = extrema.NbExt();
double dmin = std::numeric_limits<double>::infinity();
for (int j = 1; j <= n; j++) {
const double d = extrema.SquareDistance(j);
if (d < dmin) {
dmin = d;
}
}
if (dmin == std::numeric_limits<double>::infinity()) {
Logger::Error("No extrema for point");
} else if (dmin > eps2) {
Logger::Error("Distance of " + boost::lexical_cast<std::string>(std::sqrt(dmin)) + " exceeds tolerance");
}
} else {
Logger::Error("Failed to calculate extrema for point");
}
}
}
return false;
}
result = me.Edge();
return true;
}
void IfcGeom::util::wire_builder::operator()(const TopoDS_Shape& a) {
const TopoDS_Wire& w = TopoDS::Wire(a);
if (override_next_) {
override_next_ = false;
TopoDS_Edge e = first_edge(w);
mw_.Add(adjust(w, TopExp::FirstVertex(e, true), next_override_));
} else {
mw_.Add(w);
}
}
void IfcGeom::util::wire_builder::operator()(const TopoDS_Shape& a, const TopoDS_Shape& b, bool last) {
TopoDS_Wire w1 = TopoDS::Wire(a);
const TopoDS_Wire& w2 = TopoDS::Wire(b);
if (override_next_) {
override_next_ = false;
TopoDS_Edge e = first_edge(w1);
w1 = adjust(w1, TopExp::FirstVertex(e, true), next_override_);
}
TopoDS_Vertex w11, w12, w21, w22;
TopExp::Vertices(w1, w11, w12);
TopExp::Vertices(w2, w21, w22);
gp_Pnt p1 = BRep_Tool::Pnt(w12);
gp_Pnt p2 = BRep_Tool::Pnt(w21);
double dist = p1.Distance(p2);
// Distance is within tolerance, this is fine
if (dist < p_) {
mw_.Add(w1);
goto check;
}
// Distance is too large for attempting to move end points, add intermediate edge
if (dist > 1000. * p_) {
mw_.Add(w1);
mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2));
Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast<std::string>(dist) + " to:", inst_);
goto check;
}
{
TopTools_IndexedDataMapOfShapeListOfShape wmap1, wmap2;
// Find edges connected to end- and begin vertex
TopExp::MapShapesAndAncestors(w1, TopAbs_VERTEX, TopAbs_EDGE, wmap1);
TopExp::MapShapesAndAncestors(w2, TopAbs_VERTEX, TopAbs_EDGE, wmap2);
const TopTools_ListOfShape& last_edges = wmap1.FindFromKey(w12);
const TopTools_ListOfShape& first_edges = wmap2.FindFromKey(w21);
double _, __;
if (last_edges.Extent() == 1 && first_edges.Extent() == 1) {
Handle(Geom_Curve) c1 = BRep_Tool::Curve(TopoDS::Edge(last_edges.First()), _, __);
Handle(Geom_Curve) c2 = BRep_Tool::Curve(TopoDS::Edge(first_edges.First()), _, __);
const bool is_line1 = c1->DynamicType() == STANDARD_TYPE(Geom_Line);
const bool is_line2 = c2->DynamicType() == STANDARD_TYPE(Geom_Line);
const bool is_circle1 = c1->DynamicType() == STANDARD_TYPE(Geom_Circle);
const bool is_circle2 = c2->DynamicType() == STANDARD_TYPE(Geom_Circle);
// Preferably adjust the segment that is linear
if (is_line1 || (is_circle1 && !is_line2)) {
mw_.Add(adjust(w1, w12, p2));
Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast<std::string>(dist) + " on:", inst_);
} else if ((is_line2 || is_circle2) && !last) {
mw_.Add(w1);
override_next_ = true;
next_override_ = p1;
Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast<std::string>(dist) + " on:", inst_);
} else {
// In all other cases an edge is added
mw_.Add(w1);
mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2));
Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast<std::string>(dist) + " to:", inst_);
}
} else {
Logger::Error("Internal error, inconsistent wire segments", inst_);
mw_.Add(w1);
}
}
check:
if (mw_.Error() == BRepBuilderAPI_NonManifoldWire) {
Logger::Error("Non-manifold curve segments:", inst_);
} else if (mw_.Error() == BRepBuilderAPI_DisconnectedWire) {
Logger::Error("Failed to join curve segments:", inst_);
}
}
@@ -0,0 +1,105 @@
/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
#ifndef WIRE_BUILDER_H
#define WIRE_BUILDER_H
#include "../ifcparse/IfcBaseClass.h"
#include <Geom_Curve.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Wire.hxx>
#include <Extrema_ExtPC.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
namespace IfcGeom {
namespace util {
// Returns the first edge of a wire
TopoDS_Edge first_edge(const TopoDS_Wire& w);
// Returns new wire with the edge replaced by a linear edge with the vertex v moved to p
TopoDS_Wire adjust(const TopoDS_Wire& w, const TopoDS_Vertex& v, const gp_Pnt& p);
// A wrapper around BRepBuilderAPI_MakeWire that makes sure segments are connected either by moving end points or by adding intermediate segments
class wire_builder {
private:
BRepBuilderAPI_MakeWire mw_;
double p_;
bool override_next_;
gp_Pnt next_override_;
const IfcUtil::IfcBaseClass* inst_;
public:
wire_builder(double p, const IfcUtil::IfcBaseClass* inst = 0) : p_(p), override_next_(false), inst_(inst) {}
void operator()(const TopoDS_Shape& a);
void operator()(const TopoDS_Shape& a, const TopoDS_Shape& b, bool last);
const TopoDS_Wire& wire() { return mw_.Wire(); }
};
template <typename Fn>
void shape_pair_enumerate(TopTools_ListIteratorOfListOfShape& it, Fn& fn, bool closed) {
bool is_first = true;
TopoDS_Shape first, previous, current;
for (; it.More(); it.Next(), is_first = false) {
current = it.Value();
if (is_first) {
first = current;
} else {
fn(previous, current, false);
}
previous = current;
}
if (closed) {
fn(current, first, true);
} else {
fn(current);
}
}
/*
Below is code to deduce the formula below in SageMath
| R, b = var('R b')
|
| Bxy = R * cos(b), R * sin(b)
| Cxy = R * cos(b/2), R * sin(b/2)
|
| def dot(v, w):
| return v[0] * w[0] + v[1] * w[1]
|
| def norm(v):
| l = sqrt(v[0]^2 + v[1]^2)
| return v[0] / l, v[1] / l
|
| (R - R*dot(norm(Cxy), norm(Bxy))).full_simplify()
*/
double deflection_for_approximating_circle(double radius, double param);
bool create_edge_over_curve_with_log_messages(const Handle_Geom_Curve& crv, const double eps, const gp_Pnt& p1, const gp_Pnt& p2, TopoDS_Edge& result);
}
}
#endif
@@ -0,0 +1,948 @@
#include "wire_utils.h"
#include "../ifcparse/IfcLogger.h"
#include "../ifcgeom_schema_agnostic/Kernel.h"
#include "../ifcgeom_schema_agnostic/base_utils.h"
#include "../ifcgeom_schema_agnostic/boolean_utils.h"
#include "../ifcgeom_schema_agnostic/IfcGeomTree.h"
#include <TopExp.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Iterator.hxx>
#include <ShapeFix_Wire.hxx>
#include <BRep_Tool.hxx>
#include <BRepTools_WireExplorer.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepAlgo_NormalProjection.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRepBuilderAPI_MakePolygon.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <TopTools_ListOfShape.hxx>
#include <ShapeExtend_WireData.hxx>
#include <Standard_Version.hxx>
#include <GeomAPI_ExtremaCurveCurve.hxx>
#include <BRepOffsetAPI_Sewing.hxx>
#include <ShapeFix_Solid.hxx>
#include <ShapeFix_ShapeTolerance.hxx>
#include <boost/range/irange.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>
#include <map>
bool IfcGeom::util::approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps_) {
// Newell's Method is used for the normal calculation
// as a simple edge cross product can give opposite results
// for a concave face boundary.
// Reference: Graphics Gems III p. 231
const double eps2 = eps_ * eps_;
double x = 0, y = 0, z = 0;
gp_Pnt current, previous, first;
gp_XYZ center;
int n = 0;
BRepTools_WireExplorer exp(wire);
for (;; exp.Next()) {
const bool has_more = exp.More() != 0;
if (has_more) {
const TopoDS_Vertex& v = exp.CurrentVertex();
current = BRep_Tool::Pnt(v);
center += current.XYZ();
} else {
current = first;
}
if (n) {
const double& xn = previous.X();
const double& yn = previous.Y();
const double& zn = previous.Z();
const double& xn1 = current.X();
const double& yn1 = current.Y();
const double& zn1 = current.Z();
x += (yn - yn1)*(zn + zn1);
y += (xn + xn1)*(zn - zn1);
z += (xn - xn1)*(yn + yn1);
} else {
first = current;
}
if (!has_more) {
break;
}
previous = current;
++n;
}
if (n < 3) {
return false;
}
gp_Vec v(x, y, z);
if (v.SquareMagnitude() < eps_ * eps_) {
Logger::Warning("Degenerate face boundary in normal estimation");
return false;
}
plane = gp_Pln(center / n, v);
exp.Init(wire);
for (; exp.More(); exp.Next()) {
const TopoDS_Vertex& vrt = exp.CurrentVertex();
current = BRep_Tool::Pnt(vrt);
if (plane.SquareDistance(current) > eps2) {
return false;
}
}
return true;
}
bool IfcGeom::util::flatten_wire(TopoDS_Wire& wire, double eps) {
gp_Pln pln;
if (!approximate_plane_through_wire(wire, pln, eps)) {
return false;
}
TopoDS_Face face = BRepBuilderAPI_MakeFace(pln).Face();
BRepAlgo_NormalProjection proj(face);
proj.Add(wire);
proj.Build();
if (!proj.IsDone()) {
return false;
}
TopTools_ListOfShape list;
proj.BuildWire(list);
if (list.Extent() != 1) {
return false;
}
wire = TopoDS::Wire(list.First());
return true;
}
IfcGeom::util::triangulate_wire_result IfcGeom::util::triangulate_wire(const std::vector<TopoDS_Wire>& wires, TopTools_ListOfShape& faces) {
// This is a bit of a precarious approach, but seems to work for the
// versions of OCCT tested for. OCCT has a Delaunay triangulation function
// BRepMesh_Delaun, but it is notoriously hard to interpret the results
// (due to the Bowyer-Watson super triangle perhaps?). Therefore
// alternatively we use the regular OCCT incremental mesher on a new face
// created from the UV coordinates of the original wire. Pray to our gods
// that the vertex coordinates are unaffected by the meshing algorithm and
// map them back to 3d coordinates when iterating over the mesh triangles.
// In addition, to maintain a manifold shell, we need to make sure that
// every edge from the input wire is used exactly once in the list of
// resulting faces. And that other internal edges are used twice.
typedef std::pair<double, double> uv_node;
gp_Pln pln;
if (!approximate_plane_through_wire(wires.front(), pln, std::numeric_limits<double>::infinity())) {
return TRIANGULATE_WIRE_FAIL;
}
const gp_XYZ& udir = pln.Position().XDirection().XYZ();
const gp_XYZ& vdir = pln.Position().YDirection().XYZ();
const gp_XYZ& pnt = pln.Position().Location().XYZ();
std::map<uv_node, TopoDS_Vertex> mapping;
std::map<std::pair<uv_node, uv_node>, TopoDS_Edge> existing_edges, new_edges;
std::unique_ptr<BRepBuilderAPI_MakeFace> mf;
for (auto it = wires.begin(); it != wires.end(); ++it) {
const TopoDS_Wire& wire = *it;
BRepTools_WireExplorer exp(wire);
BRepBuilderAPI_MakePolygon mp;
// Add UV coordinates to a newly created polygon
for (; exp.More(); exp.Next()) {
// Project onto plane
const TopoDS_Vertex& V = exp.CurrentVertex();
gp_Pnt p = BRep_Tool::Pnt(V);
double u = (p.XYZ() - pnt).Dot(udir);
double v = (p.XYZ() - pnt).Dot(vdir);
mp.Add(gp_Pnt(u, v, 0.));
mapping.insert(std::make_pair(std::make_pair(u, v), V));
// Store existing edges in a map so that triangles can
// actually reference the preexisting edges.
const TopoDS_Edge& e = exp.Current();
TopoDS_Vertex V0, V1;
TopExp::Vertices(e, V0, V1, true);
gp_Pnt p0 = BRep_Tool::Pnt(V0);
gp_Pnt p1 = BRep_Tool::Pnt(V1);
double u0 = (p0.XYZ() - pnt).Dot(udir);
double v0 = (p0.XYZ() - pnt).Dot(vdir);
double u1 = (p1.XYZ() - pnt).Dot(udir);
double v1 = (p1.XYZ() - pnt).Dot(vdir);
uv_node uv0 = std::make_pair(u0, v0);
uv_node uv1 = std::make_pair(u1, v1);
existing_edges.insert(std::make_pair(std::make_pair(uv0, uv1), e));
existing_edges.insert(std::make_pair(std::make_pair(uv1, uv0), TopoDS::Edge(e.Reversed())));
}
// Not closed by default
mp.Close();
if (mf) {
if (it - 1 == wires.begin()) {
// @todo is this necessary?
TopoDS_Face f = mf->Face();
mf->Init(f);
}
mf->Add(mp.Wire());
} else {
mf.reset(new BRepBuilderAPI_MakeFace(mp.Wire()));
}
}
const TopoDS_Face& face = mf->Face();
// Create a triangular mesh from the face
BRepMesh_IncrementalMesh(face, Precision::Confusion());
int n123[3];
TopLoc_Location loc;
Handle_Poly_Triangulation tri = BRep_Tool::Triangulation(face, loc);
if (!tri.IsNull()) {
const Poly_Array1OfTriangle& triangles = tri->Triangles();
for (int i = 1; i <= triangles.Length(); ++i) {
if (face.Orientation() == TopAbs_REVERSED)
triangles(i).Get(n123[2], n123[1], n123[0]);
else triangles(i).Get(n123[0], n123[1], n123[2]);
// Create polygons from the mesh vertices
BRepBuilderAPI_MakeWire mp2;
for (int j = 0; j < 3; ++j) {
uv_node uvnodes[2];
TopoDS_Vertex vs[2];
for (int k = 0; k < 2; ++k) {
const gp_Pnt& uv = tri->Node(n123[(j + k) % 3]);
uvnodes[k] = std::make_pair(uv.X(), uv.Y());
auto it = mapping.find(uvnodes[k]);
if (it == mapping.end()) {
Logger::Error("Internal error: unable to unproject uv-mesh");
return TRIANGULATE_WIRE_FAIL;
}
vs[k] = it->second;
}
auto it = existing_edges.find(std::make_pair(uvnodes[0], uvnodes[1]));
if (it != existing_edges.end()) {
// This is a boundary edge, reuse existing edge from wire
mp2.Add(it->second);
} else {
auto jt = new_edges.find(std::make_pair(uvnodes[0], uvnodes[1]));
if (jt != new_edges.end()) {
// We have already added the reverse as part of another
// triangle, reuse this edge.
mp2.Add(TopoDS::Edge(jt->second));
} else {
// This is a new internal edge. Register the reverse
// for reuse later. We need to be sure to reuse vertices
// for the edge construction because otherwise the wire
// builder will use geometrical proximity for vertex
// connections in which case the edge will be copied
// and no longer partner with other edges from the shell.
TopoDS_Edge ne = BRepBuilderAPI_MakeEdge(vs[0], vs[1]);
mp2.Add(ne);
// Store the reverse to be picked up later.
new_edges.insert(std::make_pair(std::make_pair(uvnodes[1], uvnodes[0]), TopoDS::Edge(ne.Reversed())));
}
}
}
BRepBuilderAPI_MakeFace mft(mp2.Wire());
if (mft.IsDone()) {
TopoDS_Face triangle_face = mft.Face();
TopoDS_Iterator jt(triangle_face, false);
for (; jt.More(); jt.Next()) {
const TopoDS_Wire& w = TopoDS::Wire(jt.Value());
if (w.Orientation() != wires.front().Orientation()) {
triangle_face.Reverse();
}
}
faces.Append(triangle_face);
} else {
Logger::Error("Internal error: missing face");
return TRIANGULATE_WIRE_FAIL;
}
}
}
TopTools_IndexedDataMapOfShapeListOfShape mape, mapn;
for (auto& wire : wires) {
TopExp::MapShapesAndAncestors(wire, TopAbs_EDGE, TopAbs_WIRE, mape);
}
TopTools_ListIteratorOfListOfShape it(faces);
for (; it.More(); it.Next()) {
TopExp::MapShapesAndAncestors(it.Value(), TopAbs_EDGE, TopAbs_WIRE, mapn);
}
// Validation
bool non_manifold = false;
for (int i = 1; i <= mape.Extent(); ++i) {
#if OCC_VERSION_HEX >= 0x70000
TopTools_ListOfShape val;
if (!mapn.FindFromKey(mape.FindKey(i), val)) {
#else
bool contains = false;
try {
TopTools_ListOfShape val = mapn.FindFromKey(mape.FindKey(i));
contains = true;
} catch (Standard_NoSuchObject&) {}
if (!contains) {
#endif
// All existing edges need to exist in the new faces
Logger::Error("Internal error, missing edge from triangulation");
non_manifold = true;
}
}
for (int i = 1; i <= mapn.Extent(); ++i) {
const TopoDS_Shape& v = mapn.FindKey(i);
int n = mapn.FindFromIndex(i).Extent();
// Existing edges are boundaries with use 1
// New edges are internal with use 2
if (n != (mape.Contains(v) ? 1 : 2)) {
Logger::Error("Internal error, non-manifold result from triangulation");
non_manifold = true;
}
}
return non_manifold ? TRIANGULATE_WIRE_NON_MANIFOLD : TRIANGULATE_WIRE_OK;
}
namespace {
/*
* A small helper utility to wrap around a numeric range
*/
class bounded_int {
private:
int i;
size_t n;
public:
bounded_int(int i, size_t n) : i(i), n(n) {}
bounded_int& operator--() {
--i;
if (i == -1) {
i = (int)n - 1;
}
return *this;
}
bounded_int& operator++() {
++i;
if (i == (int)n) {
i = 0;
}
return *this;
}
operator int() { return i; }
};
}
namespace {
double get_wire_intersection_tolerance(const IfcGeom::util::wire_tolerance_settings& settings, const TopoDS_Wire& wire) {
if (settings.use_wire_intersection_tolerance) {
// This corresponds to faceset_helper::epsilon
if (settings.vertex_clustering_epsilon > 0.) {
return settings.vertex_clustering_epsilon / 3.;
} else {
return (std::min)(IfcGeom::util::min_edge_length(wire) / 2., settings.precision * 10.);
}
} else {
return 0.;
}
}
}
bool IfcGeom::util::wire_intersections(const TopoDS_Wire& wire, TopTools_ListOfShape& wires, const wire_tolerance_settings& settings) {
double eps = get_wire_intersection_tolerance(settings, wire);
double eps_real = settings.precision;
if (!wire.Closed()) {
wires.Append(wire);
return false;
}
int n = util::count(wire, TopAbs_EDGE);
if (n < 3) {
wires.Append(wire);
return false;
}
// Note: initialize empty
Handle(ShapeExtend_WireData) wd = new ShapeExtend_WireData();
// ... to be sure to get consecutive edges
BRepTools_WireExplorer exp(wire);
IfcGeom::impl::tree<int> tree;
int edge_idx = 0;
for (; exp.More(); exp.Next()) {
wd->Add(exp.Current());
if (n > 64) {
// tfk: indices in tree are 0-based vd 1-based in wiredata
tree.add(edge_idx++, exp.Current());
}
}
if (wd->NbEdges() != n) {
// If the number of edges differs, BRepTools_WireExplorer did not
// reach every edge, probably due to loops exactly at vertex locations.
// This is not supported by this algorithm which only elimates loops
// due to edge crossings.
throw geometry_exception("Invalid loop");
}
bool intersected = false;
// tfk: Extrema on infinite curves proved to be more robust.
// TopoDS_Face face = BRepBuilderAPI_MakeFace(wire, true).Face();
// ShapeAnalysis_Wire saw(wd, face, getValue(GV_PRECISION));
// @todo: should this start from 0 in case of n > 64?
for (int i = 2; i < n; ++i) {
std::vector<int> js;
if (n > 64) {
Bnd_Box b;
BRepBndLib::Add(wd->Edge(i + 1), b);
b.Enlarge(eps);
js = tree.select_box(b, false);
} else {
boost::push_back(js, boost::irange(0, i - 1));
}
for (std::vector<int>::const_iterator it = js.begin(); it != js.end(); ++it) {
int j = *it;
if (n > 64) {
if (j > i) {
continue;
}
if ((std::max)(i, j) - (std::min)(i, j) <= 1) {
continue;
}
}
// Only check non-consecutive edges
if (i == n - 1 && j == 0) continue;
double u11, u12, u21, u22, U1, U2;
GeomAPI_ExtremaCurveCurve ecc(
BRep_Tool::Curve(wd->Edge(i + 1), u11, u12),
BRep_Tool::Curve(wd->Edge(j + 1), u21, u22)
);
// @todo: extend this to work in case of multiple extrema and curved segments.
const bool unbounded_intersects = (!ecc.Extrema().IsParallel() && ecc.NbExtrema() == 1 && ecc.Distance(1) < eps);
if (unbounded_intersects) {
ecc.Parameters(1, U1, U2);
if (u11 > u12) {
std::swap(u11, u12);
}
if (u21 > u22) {
std::swap(u21, u22);
}
/// @todo: tfk: probably need different thresholds on non-linear curves
u11 -= eps;
u12 += eps;
u21 -= eps;
u22 += eps;
// tfk: code below is for ShapeAnalysis_Wire::CheckIntersectingEdges()
// IntRes2d_SequenceOfIntersectionPoint points2d;
// TColgp_SequenceOfPnt points3d;
// TColStd_SequenceOfReal errors;
// if (saw.CheckIntersectingEdges(i + 1, j + 1, points2d, points3d, errors)) {
if (u11 < U1 && U1 < u12 && u21 < U2 && U2 < u22) {
intersected = true;
// Explore a forward and backward cycle from the intersection point
for (int fb = 0; fb <= 1; ++fb) {
const bool forward = fb == 0;
BRepBuilderAPI_MakeWire mw;
bool first = true;
for (bounded_int k(j, n);;) {
bool intersecting = k == j || k == i;
if (intersecting) {
TopoDS_Edge e = wd->Edge(k + 1);
TopoDS_Vertex v1, v2;
TopExp::Vertices(e, v1, v2, true);
const TopoDS_Vertex* v = first == forward ? &v2 : &v1;
// gp_Pnt p2 = points3d.Value(1);
gp_Pnt p1 = BRep_Tool::Pnt(*v);
gp_Pnt pp1, pp2;
ecc.Points(1, pp1, pp2);
const gp_Pnt& p2 = k == i ? pp1 : pp2;
// Substitute with a new edge from/to the intersection point
if (p1.Distance(p2) > eps_real * 2) {
double _, __;
Handle_Geom_Curve crv = BRep_Tool::Curve(e, _, __);
BRepBuilderAPI_MakeEdge me(crv, p1, p2);
TopoDS_Edge ed = me.Edge();
mw.Add(ed);
}
first = false;
} else {
// Re-use original edge
mw.Add(wd->Edge(k + 1));
}
if (k == i) {
break;
}
if (forward) {
++k;
} else {
--k;
}
}
ShapeFix_Wire sfw;
sfw.Load(mw.Wire());
sfw.Perform();
// Recursively process both cuts
// @todo this is a change in behaviour with eps precomputed from the kernel
// instead of adaptively calculated for the successive iterations.
wire_intersections(sfw.Wire(), wires, settings);
}
return true;
}
}
}
}
// No intersections found, append original wire
if (!intersected) {
wires.Append(wire);
}
return intersected;
}
void IfcGeom::util::select_largest(const TopTools_ListOfShape& shapes, TopoDS_Shape& largest) {
double mass = 0.;
TopTools_ListIteratorOfListOfShape it(shapes);
for (; it.More(); it.Next()) {
/*
// tfk: bounding box is more efficient probably
const TopoDS_Wire& w = TopoDS::Wire(it.Value());
TopoDS_Face face = BRepBuilderAPI_MakeFace(w).Face();
const double m = face_area(face);
*/
Bnd_Box bb;
BRepBndLib::AddClose(it.Value(), bb);
double xyz_min[3], xyz_max[3];
bb.Get(xyz_min[0], xyz_min[1], xyz_min[2], xyz_max[0], xyz_max[1], xyz_max[2]);
// @todo hard coded precision.
// @todo this is a really strange measure for wire size. Why not use newell's
// method to project to plane and then calculate size of the 2d bbox?
const double eps = 1.e-5;
double m = 1.;
for (int i = 0; i < 3; ++i) {
if (Precision::IsNegativeInfinite(xyz_min[i])) {
xyz_min[i] = 0.;
}
if (Precision::IsInfinite(xyz_max[i])) {
xyz_max[i] = 0.;
}
m *= (xyz_max[i] + eps) - (xyz_min[i] - eps);
}
if (m > mass) {
mass = m;
largest = it.Value();
}
}
}
bool IfcGeom::util::wire_to_sequence_of_point(const TopoDS_Wire& w, TColgp_SequenceOfPnt& p) {
TopExp_Explorer exp(w, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
double a, b;
Handle_Geom_Curve crv = BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b);
if (crv->DynamicType() != STANDARD_TYPE(Geom_Line)) {
return false;
}
}
exp.ReInit();
int i = 0;
for (; exp.More(); exp.Next(), ++i) {
TopoDS_Vertex v1, v2;
TopExp::Vertices(TopoDS::Edge(exp.Current()), v1, v2, true);
if (exp.More()) {
if (i == 0) {
p.Append(BRep_Tool::Pnt(v1));
}
p.Append(BRep_Tool::Pnt(v2));
}
}
return true;
}
void IfcGeom::util::sequence_of_point_to_wire(const TColgp_SequenceOfPnt& p, TopoDS_Wire& w, bool close) {
BRepBuilderAPI_MakePolygon builder;
for (int i = 1; i <= p.Length(); ++i) {
builder.Add(p.Value(i));
}
if (close) {
builder.Close();
}
w = builder.Wire();
}
void IfcGeom::util::remove_collinear_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol) {
const int start = closed ? 1 : 2;
const int end = polygon.Length() - (closed ? 0 : 1);
std::vector<bool> to_remove(polygon.Length(), false);
for (int i = start; i <= end; ++i) {
const gp_Pnt& a = polygon.Value(((i - 2 + polygon.Length()) % polygon.Length()) + 1);
const gp_Pnt& b = polygon.Value(i);
const gp_Pnt& c = polygon.Value((i % polygon.Length()) + 1);
const gp_Vec d1 = c.XYZ() - a.XYZ();
const gp_Vec d2 = b.XYZ() - a.XYZ();
const double dt = d2.Dot(d1) / d1.Dot(d1);
const gp_Vec d3 = d1.Scaled(dt);
const gp_Pnt b2 = a.XYZ() + d3.XYZ();
if (b.Distance(b2) < tol) {
to_remove[i - 1] = true;
}
}
for (int i = (int)to_remove.size() - 1; i >= 0; --i) {
if (to_remove[i]) {
polygon.Remove(i + 1);
}
}
}
void IfcGeom::util::remove_duplicate_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol) {
tol *= tol;
for (;;) {
bool removed = false;
int n = polygon.Length() - (closed ? 0 : 1);
for (int i = 1; i <= n; ++i) {
// wrap around to the first point in case of a closed loop
int j = (i % polygon.Length()) + 1;
double dist = polygon.Value(i).SquareDistance(polygon.Value(j));
if (dist < tol) {
// do not remove the first or last point to
// maintain connectivity with other wires
if ((closed && j == 1) || (!closed && j == n)) polygon.Remove(i);
else polygon.Remove(j);
removed = true;
break;
}
}
if (!removed) break;
}
}
namespace {
// Returns the vertex part of an TopoDS_Edge edge that is not TopoDS_Vertex vertex
TopoDS_Vertex find_other(const TopoDS_Edge& edge, const TopoDS_Vertex& vertex) {
TopExp_Explorer exp(edge, TopAbs_VERTEX);
while (exp.More()) {
if (!exp.Current().IsSame(vertex)) {
return TopoDS::Vertex(exp.Current());
}
exp.Next();
}
return TopoDS_Vertex();
}
TopoDS_Edge find_next(const TopTools_IndexedMapOfShape& edge_set, const TopTools_IndexedDataMapOfShapeListOfShape& vertex_to_edges, const TopoDS_Vertex& current, const TopoDS_Edge& previous_edge) {
const TopTools_ListOfShape& edges = vertex_to_edges.FindFromKey(current);
TopTools_ListIteratorOfListOfShape eit;
for (eit.Initialize(edges); eit.More(); eit.Next()) {
const TopoDS_Edge& edge = TopoDS::Edge(eit.Value());
if (edge.IsSame(previous_edge)) continue;
if (edge_set.Contains(edge)) {
return edge;
}
}
return TopoDS_Edge();
}
}
bool IfcGeom::util::fill_nonmanifold_wires_with_planar_faces(TopoDS_Shape& shape, double tol) {
BRepOffsetAPI_Sewing sew;
sew.Add(shape);
TopTools_IndexedDataMapOfShapeListOfShape edge_to_faces;
TopTools_IndexedDataMapOfShapeListOfShape vertex_to_edges;
std::set<int> visited;
TopTools_IndexedMapOfShape edge_set;
TopExp::MapShapesAndAncestors(shape, TopAbs_EDGE, TopAbs_FACE, edge_to_faces);
const int num_edges = edge_to_faces.Extent();
for (int i = 1; i <= num_edges; ++i) {
const TopTools_ListOfShape& faces = edge_to_faces.FindFromIndex(i);
const int count = faces.Extent();
// Find only the non-manifold edges: Edges that are only part of a
// single face and therefore part of the wire(s) we want to fill.
if (count == 1) {
const TopoDS_Shape& edge = edge_to_faces.FindKey(i);
TopExp::MapShapesAndAncestors(edge, TopAbs_VERTEX, TopAbs_EDGE, vertex_to_edges);
edge_set.Add(edge);
}
}
const int num_verts = vertex_to_edges.Extent();
TopoDS_Vertex first, current;
TopoDS_Edge previous_edge;
// Now loop over all the vertices that are part of the wire(s) to be filled
for (int i = 1; i <= num_verts; ++i) {
first = current = TopoDS::Vertex(vertex_to_edges.FindKey(i));
// We keep track of the vertices we already used
if (visited.find(vertex_to_edges.FindIndex(current)) != visited.end()) {
continue;
}
// Given these vertices, try to find closed loops and create new
// wires out of them.
BRepBuilderAPI_MakeWire w;
for (;;) {
visited.insert(vertex_to_edges.FindIndex(current));
// Find the edge that the current vertex is part of and points
// away from the previous vertex (null for the first vertex).
TopoDS_Edge edge = find_next(edge_set, vertex_to_edges, current, previous_edge);
if (edge.IsNull()) {
return false;
}
TopoDS_Vertex other = find_other(edge, current);
if (other.IsNull()) {
// Dealing with a conical edge probably, for some reason
// this works better than adding the edge directly.
double u1, u2;
Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u1, u2);
w.Add(BRepBuilderAPI_MakeEdge(crv, u1, u2));
break;
} else {
w.Add(edge);
}
// See if the starting point of this loop has been reached. Note that
// additional wires after this one potentially will be created.
if (other.IsSame(first)) {
break;
}
previous_edge = edge;
current = other;
}
sew.Add(BRepBuilderAPI_MakeFace(w));
previous_edge.Nullify();
}
sew.Perform();
shape = sew.SewedShape();
try {
ShapeFix_Solid solid;
solid.LimitTolerance(tol);
shape = solid.SolidFromShell(TopoDS::Shell(shape));
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error creating solid");
}
} catch (...) {
Logger::Error("Unknown error creating solid");
}
return true;
}
bool IfcGeom::util::convert_curve_to_wire(const Handle(Geom_Curve)& curve, TopoDS_Wire& wire) {
try {
wire = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(curve));
return true;
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error converting curve to wire");
}
} catch (...) {
Logger::Error("Unknown error converting curve to wire");
}
return false;
}
void IfcGeom::util::assert_closed_wire(TopoDS_Wire& wire, double tol) {
if (wire.Closed() == 0) {
TopoDS_Vertex v0, v1;
TopExp::Vertices(wire, v0, v1);
gp_Pnt p1 = BRep_Tool::Pnt(v0);
gp_Pnt p2 = BRep_Tool::Pnt(v1);
if (p1.Distance(p2) > tol) {
BRepBuilderAPI_MakeWire mw;
mw.Add(wire);
mw.Add(BRepBuilderAPI_MakeEdge(v0, v1).Edge());
wire = mw.Wire();
}
Logger::Warning("Wire not closed");
}
}
bool IfcGeom::util::convert_wire_to_face(const TopoDS_Wire& w, TopoDS_Face& face, const IfcGeom::util::wire_tolerance_settings& settings) {
TopoDS_Wire wire = w;
TopTools_ListOfShape results;
if (settings.use_wire_intersection_check && util::wire_intersections(wire, results, settings)) {
Logger::Warning("Self-intersections with " + boost::lexical_cast<std::string>(results.Extent()) + " cycles detected");
util::select_largest(results, wire);
}
bool is_2d = true;
TopExp_Explorer exp(wire, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
double a, b;
// @todo this does not handle fillets
Handle(Geom_Curve) crv = BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b);
if (crv->DynamicType() != STANDARD_TYPE(Geom_Line)) {
is_2d = false;
break;
}
Handle(Geom_Line) line = Handle(Geom_Line)::DownCast(crv);
if (line->Lin().Direction().Z() > ALMOST_ZERO) {
is_2d = false;
break;
}
}
if (!is_2d) {
// For 2d wires (e.g. profiles) a higher tolerance for plane fitting is never required.
ShapeFix_ShapeTolerance FTol;
FTol.SetTolerance(wire, settings.precision, TopAbs_WIRE);
}
BRepBuilderAPI_MakeFace mf(wire, false);
BRepBuilderAPI_FaceError er = mf.Error();
if (er != BRepBuilderAPI_FaceDone) {
Logger::Error("Failed to create face.");
return false;
}
face = mf.Face();
return true;
}
bool IfcGeom::util::convert_wire_to_faces(const TopoDS_Wire& w, TopoDS_Compound& faces, const IfcGeom::util::wire_tolerance_settings& settings) {
bool is_2d = true;
TopExp_Explorer exp(w, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
double a, b;
Handle(Geom_Curve) crv = BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b);
if (crv->DynamicType() != STANDARD_TYPE(Geom_Line)) {
is_2d = false;
break;
}
Handle(Geom_Line) line = Handle(Geom_Line)::DownCast(crv);
if (line->Lin().Direction().Z() > ALMOST_ZERO) {
is_2d = false;
break;
}
}
TopTools_ListOfShape results;
if (settings.use_wire_intersection_check && util::wire_intersections(w, results, settings)) {
Logger::Warning("Self-intersections with " + boost::lexical_cast<std::string>(results.Extent()) + " cycles detected");
} else {
results.Clear();
results.Append(w);
}
TopoDS_Compound C;
BRep_Builder B;
B.MakeCompound(faces);
std::list<std::pair<double, TopoDS_Face>> face_list;
double max_area = 0.;
TopTools_ListIteratorOfListOfShape it(results);
for (; it.More(); it.Next()) {
const TopoDS_Wire& wire = TopoDS::Wire(it.Value());
if (!is_2d) {
// For 2d wires (e.g. profiles) a higher tolerance for plane fitting is never required.
ShapeFix_ShapeTolerance FTol;
FTol.SetTolerance(wire, settings.precision, TopAbs_WIRE);
}
BRepBuilderAPI_MakeFace mf(wire, false);
BRepBuilderAPI_FaceError er = mf.Error();
if (er != BRepBuilderAPI_FaceDone) {
Logger::Error("Failed to create face.");
continue;
}
TopoDS_Face face = mf.Face();
const double m = face_area(face);
face_list.push_back({ m, face });
if (m > max_area) {
max_area = m;
}
}
for (auto& p : face_list) {
if (p.first >= max_area / 10.) {
B.Add(faces, p.second);
} else {
Logger::Warning("Ignoring self-intersection loop with area " + boost::lexical_cast<std::string>(p.first));
}
}
return true;
}
@@ -0,0 +1,60 @@
#ifndef WIRE_UTILS_H
#define WIRE_UTILS_H
#include <gp_Pln.hxx>
#include <Geom_Curve.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Compound.hxx>
#include <TColgp_SequenceOfPnt.hxx>
#include <TopTools_ListOfShape.hxx>
#include <vector>
namespace IfcGeom {
namespace util {
bool approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps);
bool flatten_wire(TopoDS_Wire& wire, double eps);
enum triangulate_wire_result {
TRIANGULATE_WIRE_FAIL,
TRIANGULATE_WIRE_OK,
TRIANGULATE_WIRE_NON_MANIFOLD,
};
struct wire_tolerance_settings {
bool use_wire_intersection_check;
bool use_wire_intersection_tolerance;
double vertex_clustering_epsilon;
double precision;
};
/// Triangulate the set of wires. The firstmost wire is assumed to be the outer wire.
triangulate_wire_result triangulate_wire(const std::vector<TopoDS_Wire>& wires, TopTools_ListOfShape& faces);
bool wire_intersections(const TopoDS_Wire& wire, TopTools_ListOfShape& wires, const wire_tolerance_settings& settings);
void select_largest(const TopTools_ListOfShape& shapes, TopoDS_Shape& largest);
bool convert_wire_to_face(const TopoDS_Wire& wire, TopoDS_Face& face, const IfcGeom::util::wire_tolerance_settings& settings);
bool convert_wire_to_faces(const TopoDS_Wire& wire, TopoDS_Compound& face, const IfcGeom::util::wire_tolerance_settings& settings);
void assert_closed_wire(TopoDS_Wire& wire, double tol);
bool fill_nonmanifold_wires_with_planar_faces(TopoDS_Shape& shape, double tol);
void remove_duplicate_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol);
void remove_collinear_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol);
bool wire_to_sequence_of_point(const TopoDS_Wire&, TColgp_SequenceOfPnt&);
void sequence_of_point_to_wire(const TColgp_SequenceOfPnt&, TopoDS_Wire&, bool closed);
bool convert_curve_to_wire(const Handle(Geom_Curve)& curve, TopoDS_Wire& wire);
}
}
#endif