Program Listing for File ub_tree.h¶
↰ Return to documentation for file (src/ifcgeom/kernels/opencascade/ub_tree.h)
// This file was generated with the assistance of an AI coding tool.
/********************************************************************************
* *
* 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 IFCOPENSHELL_OPENCASCADE_UB_TREE_H
#define IFCOPENSHELL_OPENCASCADE_UB_TREE_H
#include "../../../ifcparse/file.h"
#include "../../../ifcgeom/element.h"
#include "../../../ifcgeom/iterator.h"
#include "../../../ifcgeom/tree.h"
#include "../../kernel_registry.h"
#include "../../../ifcparse/exception.h"
#include "opencascade_conversion_result.h"
#include "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 <Standard_Macro.hxx>
#include <Standard_Version.hxx>
#include <TopoDS_Shape.hxx>
#include <Standard_Integer.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <NCollection_DataMap.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepExtrema_ExtPF.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS.hxx>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <type_traits>
#include <utility>
#include <vector>
namespace ifcopenshell::geom {
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 {
inline gp_Pnt make_point(const ifcopenshell::geom::tree_point& point) {
return gp_Pnt(point[0], point[1], point[2]);
}
inline Bnd_Box make_box(const ifcopenshell::geom::tree_box& bounds) {
Bnd_Box box;
box.Add(make_point(bounds[0]));
box.Add(make_point(bounds[1]));
return box;
}
// Selection is keyed by the product instances the elements were added
// with, so anything else can not be looked up.
inline void check_product(const express::base& instance) {
if (!instance || !instance.declaration().is("IfcProduct")) {
throw ifcopenshell::exception("Instance should be an IfcProduct");
}
}
template <typename T>
std::vector<express::base> to_base_vector(const std::vector<T>& ts) {
std::vector<express::base> result;
result.reserve(ts.size());
for (const auto& t : ts) {
result.push_back(t);
}
return result;
}
// Selection implementation based on an NCollection_UBTree over bounding
// boxes. The T-typed members are the interface consumed by the geometry
// kernel, which instantiates this template with integer indices. When
// instantiated with a type derived from express::base, the T-typed
// select() and select_box() members also implement the corresponding
// virtual methods of the abstract tree interface.
template <typename T>
class ub_tree : public ifcopenshell::geom::tree {
public:
std::string backend_id() const override {
return "opencascade.brep";
}
// The iterator overload of the base class is not hidden by the members below.
using ifcopenshell::geom::tree::add_file;
void add_file(ifcopenshell::file& file, const ifcopenshell::geom::settings& settings) override {
if constexpr (std::is_base_of_v<express::base, T>) {
auto settings_ = settings;
settings_.get<ifcopenshell::geom::settings::IteratorOutput>().value = ifcopenshell::geom::settings::NATIVE;
settings_.get<ifcopenshell::geom::settings::UseWorldCoords>().value = true;
settings_.get<ifcopenshell::geom::settings::ReorientShells>().value = true;
ifcopenshell::geom::iterator iterator(ifcopenshell::geom::kernels::construct(&file, "opencascade", settings_), settings_, &file, {}, 1);
add_file(iterator);
} else {
ifcopenshell::geom::tree::add_file(file, settings);
}
}
void add_element(ifcopenshell::geom::element* element) override {
if constexpr (std::is_base_of_v<express::base, T>) {
auto* brep = dynamic_cast<ifcopenshell::geom::native_element*>(element);
if (!brep) {
throw ifcopenshell::exception("Tree backend '" + backend_id() + "' requires native brep elements");
}
TopoDS_Shape compound = transformed_compound(brep);
add(brep->product(), compound);
auto git = brep->geometry().begin();
if (enable_face_styles_) {
TopoDS_Iterator it(compound);
for (; it.More(); it.Next(), ++git) {
// 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(), git->style_ptr());
size_t index;
if (sit == styles_.end()) {
index = styles_.size();
styles_.push_back(git->style_ptr());
} 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);
}
}
}
} else {
ifcopenshell::geom::tree::add_element(element);
}
}
std::vector<express::base> select_box(const ifcopenshell::geom::tree_point& point) const override {
if constexpr (std::is_base_of_v<express::base, T>) {
return to_base_vector(select_box(make_point(point)));
} else {
return ifcopenshell::geom::tree::select_box(point);
}
}
std::vector<express::base> select_box(const ifcopenshell::geom::tree_box& bounds, bool completely_within = false) const override {
if constexpr (std::is_base_of_v<express::base, T>) {
return to_base_vector(select_box(make_box(bounds), completely_within));
} else {
return ifcopenshell::geom::tree::select_box(bounds, completely_within);
}
}
std::vector<express::base> select(const ifcopenshell::geom::element* element, bool completely_within = false, double extend = -1.e-5) const override {
if constexpr (std::is_base_of_v<express::base, T>) {
auto* brep = dynamic_cast<const ifcopenshell::geom::native_element*>(element);
if (!brep) {
throw ifcopenshell::exception("Tree backend '" + backend_id() + "' requires native brep elements");
}
return to_base_vector(select(transformed_compound(brep), completely_within, extend));
} else {
return ifcopenshell::geom::tree::select(element, completely_within, extend);
}
}
std::vector<express::base> select(const ifcopenshell::geom::tree_point& point, double extend = 0.0) const override {
if constexpr (std::is_base_of_v<express::base, T>) {
return to_base_vector(select(make_point(point), extend));
} else {
return ifcopenshell::geom::tree::select(point, extend);
}
}
std::vector<ifcopenshell::geom::ray_intersection_result> select_ray(const ifcopenshell::geom::tree_point& origin, const ifcopenshell::geom::tree_point& direction, double length = 1000.) const override {
if constexpr (std::is_base_of_v<express::base, T>) {
gp_Pnt p0 = make_point(origin);
gp_Dir d(direction[0], direction[1], direction[2]);
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.template as<express::entity>(),
{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;
} else {
return ifcopenshell::geom::tree::select_ray(origin, direction, length);
}
}
const std::vector<double>& distances() const override {
return distances_;
}
const std::vector<double>& protrusion_distances() const override {
return protrusion_distances_;
}
bool enable_face_styles() const override {
return enable_face_styles_;
}
void enable_face_styles(bool enable) override {
enable_face_styles_ = enable;
}
const std::vector<ifcopenshell::geom::taxonomy::style::ptr>& styles() const override {
return styles_;
}
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 {
if constexpr (std::is_base_of_v<express::base, T>) {
check_product(t);
}
typename shape_map::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 {
if constexpr (std::is_base_of_v<express::base, T>) {
check_product(t);
}
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 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> spatial_tree;
typedef std::map<T, TopoDS_Shape> shape_map;
typedef NCollection_DataMap<TopoDS_Shape, int, TopTools_ShapeMapHasher> face_style_map;
spatial_tree tree_;
shape_map shapes_;
// @todo this is ugly, embed this in the return type
mutable std::vector<double> distances_;
mutable std::vector<double> protrusion_distances_;
bool enable_face_styles_ = false;
face_style_map face_styles_;
std::vector<ifcopenshell::geom::taxonomy::style::ptr> styles_;
class selector : public spatial_tree::Selector
{
public:
selector(const Bnd_Box& b)
: spatial_tree::Selector()
, bounds_(b)
{}
bool Reject(const Bnd_Box& b) const {
return bounds_.IsOut(b);
}
bool Accept(const T& o) {
results_.push_back(o);
return true;
}
const std::vector<T>& results() const {
return results_;
}
private:
std::vector<T> results_;
const Bnd_Box& bounds_;
};
private:
// The compound shape of a native element in world coordinates.
TopoDS_Shape transformed_compound(const ifcopenshell::geom::native_element* elem) const {
auto* compound_generic = static_cast<ifcopenshell::geom::open_cascade_shape*>(elem->geometry().as_compound());
TopoDS_Shape compound(std::move(compound_generic->shape()));
delete compound_generic;
const auto& m = elem->transformation().data()->ccomponents();
gp_Trsf tr;
tr.SetValues(
m(0, 0), m(0, 1), m(0, 2), m(0, 3),
m(1, 0), m(1, 1), m(1, 2), m(1, 3),
m(2, 0), m(2, 1), m(2, 2), m(2, 3)
);
compound.Move(tr);
return compound;
}
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;
}
};
}
}
#endif