mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-09 21:53:40 +00:00
File renames, build script and cmake updates
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 IFCSHAPELIST_H
|
||||
#define IFCSHAPELIST_H
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomRenderStyles.h"
|
||||
|
||||
#include <gp_GTrsf.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace IfcGeom {
|
||||
|
||||
class IFC_GEOM_API IfcRepresentationShapeItem {
|
||||
private:
|
||||
int id;
|
||||
gp_GTrsf placement;
|
||||
TopoDS_Shape shape;
|
||||
std::shared_ptr<const SurfaceStyle> style;
|
||||
public:
|
||||
IfcRepresentationShapeItem(int id, const gp_GTrsf& placement, const TopoDS_Shape& shape, std::shared_ptr<const SurfaceStyle> style)
|
||||
: id(id), placement(placement), shape(shape), style(style) {}
|
||||
IfcRepresentationShapeItem(int id, const gp_GTrsf& placement, const TopoDS_Shape& shape)
|
||||
: id(id), placement(placement), shape(shape), style(0) {}
|
||||
IfcRepresentationShapeItem(int id, const TopoDS_Shape& shape, std::shared_ptr<const SurfaceStyle> style)
|
||||
: id(id), shape(shape), style(style) {}
|
||||
IfcRepresentationShapeItem(int id, const TopoDS_Shape& shape)
|
||||
: id(id), shape(shape), style(0) {}
|
||||
void append(const gp_GTrsf& trsf) { placement.Multiply(trsf); }
|
||||
void prepend(const gp_GTrsf& trsf) { placement.PreMultiply(trsf); }
|
||||
const TopoDS_Shape& Shape() const { return shape; }
|
||||
const gp_GTrsf& Placement() const { return placement; }
|
||||
bool hasStyle() const { return !!style; }
|
||||
const SurfaceStyle& Style() const { return *style; }
|
||||
const std::shared_ptr<const SurfaceStyle> StylePtr() const { return style; }
|
||||
void setStyle(std::shared_ptr<const SurfaceStyle> newStyle) { style = newStyle; }
|
||||
int ItemId() const { return id; }
|
||||
};
|
||||
|
||||
typedef std::vector<IfcRepresentationShapeItem> IfcRepresentationShapeItems;
|
||||
|
||||
namespace util {
|
||||
bool flatten_shape_list(const IfcGeom::IfcRepresentationShapeItems& shapes, TopoDS_Shape& result, bool fuse, double tol);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,167 @@
|
||||
#ifndef ITERATOR_KERNEL_H
|
||||
#define ITERATOR_KERNEL_H
|
||||
|
||||
#include "../ifcparse/IfcFile.h"
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomIteratorSettings.h"
|
||||
#include "../ifcgeom_schema_agnostic/IfcRepresentationShapeItem.h"
|
||||
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
#include <boost/preprocessor/seq/size.hpp>
|
||||
#include <boost/preprocessor/seq/pop_back.hpp>
|
||||
#include <boost/preprocessor/comparison/greater.hpp>
|
||||
#include <boost/preprocessor/selection/min.hpp>
|
||||
|
||||
|
||||
// @tfk A macro cannot define an include (I think), so here we can't
|
||||
// loop over the sequence of schema identifiers, but rather we have
|
||||
// unroll the loop with at least the amount of schemas we'd like support
|
||||
// for and then overflow into an existing empty include file.
|
||||
|
||||
/*
|
||||
#define INCLUDE_SCHEMA(n) \
|
||||
BOOST_PP_IIF(BOOST_PP_GREATER(BOOST_PP_SEQ_SIZE(SCHEMA_SEQ), n), BOOST_PP_STRINGIZE(../ifcparse/BOOST_PP_CAT(Ifc,BOOST_PP_SEQ_ELEM(BOOST_PP_MIN(n, BOOST_PP_SEQ_SIZE(BOOST_PP_SEQ_POP_BACK(SCHEMA_SEQ))),SCHEMA_SEQ)).h), "empty.h")
|
||||
|
||||
#include INCLUDE_SCHEMA(0)
|
||||
#include INCLUDE_SCHEMA(1)
|
||||
#include INCLUDE_SCHEMA(2)
|
||||
#include INCLUDE_SCHEMA(3)
|
||||
#include INCLUDE_SCHEMA(4)
|
||||
#include INCLUDE_SCHEMA(5)
|
||||
#include INCLUDE_SCHEMA(6)
|
||||
#include INCLUDE_SCHEMA(7)
|
||||
#include INCLUDE_SCHEMA(8)
|
||||
#include INCLUDE_SCHEMA(9)
|
||||
*/
|
||||
|
||||
#include <boost/function.hpp>
|
||||
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <gp_Ax2.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Trsf2d.hxx>
|
||||
#include <gp_GTrsf2d.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_GTrsf.hxx>
|
||||
|
||||
static const double ALMOST_ZERO = 1.e-9;
|
||||
|
||||
template <typename T>
|
||||
inline static bool ALMOST_THE_SAME(const T& a, const T& b, double tolerance = ALMOST_ZERO) {
|
||||
return fabs(a - b) < tolerance;
|
||||
}
|
||||
|
||||
namespace IfcGeom {
|
||||
|
||||
class BRepElement;
|
||||
|
||||
class Kernel {
|
||||
private:
|
||||
Kernel* implementation_;
|
||||
|
||||
protected:
|
||||
Kernel() {};
|
||||
|
||||
public:
|
||||
// Tolerances and settings for various geometrical operations:
|
||||
enum GeomValue {
|
||||
// Specifies the deflection of the mesher
|
||||
// Default: 0.001m / 1mm
|
||||
GV_DEFLECTION_TOLERANCE,
|
||||
// Specifies the minimal area of a face to be included in an IfcConnectedFaceset
|
||||
// Read-only
|
||||
GV_MINIMAL_FACE_AREA,
|
||||
// Specifies the threshold distance under which cartesian points are deemed equal
|
||||
// Read-only
|
||||
GV_POINT_EQUALITY_TOLERANCE,
|
||||
// Specifies maximum number of faces for a shell to be reoriented.
|
||||
// Default: -1
|
||||
GV_MAX_FACES_TO_ORIENT,
|
||||
// The length unit used the creation of TopoDS_Shapes, primarily affects the
|
||||
// interpretation of IfcCartesianPoints and IfcVector magnitudes
|
||||
// DefaultL 1.0
|
||||
GV_LENGTH_UNIT,
|
||||
// The plane angle unit used for the creation of TopoDS_Shapes, primarily affects
|
||||
// the interpretation of IfcParamaterValues of IfcTrimmedCurves
|
||||
// Default: -1.0 (= not set, fist try degrees, then radians)
|
||||
GV_PLANEANGLE_UNIT,
|
||||
// The precision used in boolean operations, setting this value too low results
|
||||
// in artefacts and potentially modelling failures
|
||||
// Default: 0.00001 (obtained from IfcGeometricRepresentationContext if available)
|
||||
GV_PRECISION,
|
||||
// Whether to process shapes of type Face or higher (1) Wire or lower (-1) or all (0)
|
||||
GV_DIMENSIONALITY,
|
||||
GV_LAYERSET_FIRST,
|
||||
GV_DISABLE_BOOLEAN_RESULT,
|
||||
GV_NO_WIRE_INTERSECTION_CHECK,
|
||||
GV_PRECISION_FACTOR,
|
||||
GV_NO_WIRE_INTERSECTION_TOLERANCE,
|
||||
GV_DEBUG_BOOLEAN,
|
||||
GV_BOOLEAN_ATTEMPT_2D
|
||||
};
|
||||
|
||||
IFC_PARSE_API Kernel(IfcParse::IfcFile* file_);
|
||||
|
||||
virtual ~Kernel() {}
|
||||
|
||||
virtual void setValue(GeomValue var, double value) {
|
||||
implementation_->setValue(var, value);
|
||||
}
|
||||
|
||||
virtual double getValue(GeomValue var) const {
|
||||
return implementation_->getValue(var);
|
||||
}
|
||||
|
||||
virtual BRepElement* convert(
|
||||
const IteratorSettings& settings, IfcUtil::IfcBaseClass* representation,
|
||||
IfcUtil::IfcBaseClass* product)
|
||||
{
|
||||
return implementation_->convert(settings, representation, product);
|
||||
}
|
||||
|
||||
virtual IfcRepresentationShapeItems convert(IfcUtil::IfcBaseClass* item) {
|
||||
return implementation_->convert(item);
|
||||
}
|
||||
|
||||
virtual bool convert_placement(IfcUtil::IfcBaseClass* item, gp_Trsf& trsf) {
|
||||
return implementation_->convert_placement(item, trsf);
|
||||
}
|
||||
|
||||
|
||||
IFC_PARSE_API static IfcUtil::IfcBaseEntity* get_decomposing_entity(IfcUtil::IfcBaseEntity*, bool include_openings = true);
|
||||
IFC_PARSE_API static std::map<std::string, IfcUtil::IfcBaseEntity*> get_layers(IfcUtil::IfcBaseEntity*);
|
||||
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
typedef boost::function1<Kernel*, IfcParse::IfcFile*> kernel_fn;
|
||||
|
||||
class KernelFactoryImplementation : public std::map<std::string, kernel_fn> {
|
||||
public:
|
||||
KernelFactoryImplementation();
|
||||
void bind(const std::string& schema_name, kernel_fn);
|
||||
Kernel* construct(const std::string& schema_name, IfcParse::IfcFile*);
|
||||
};
|
||||
|
||||
KernelFactoryImplementation& kernel_implementations();
|
||||
}
|
||||
|
||||
class IFC_GEOM_API geometry_exception : public std::exception {
|
||||
protected:
|
||||
std::string message;
|
||||
public:
|
||||
geometry_exception(const std::string& m)
|
||||
: message(m) {}
|
||||
virtual ~geometry_exception() throw () {}
|
||||
virtual const char* what() const throw() {
|
||||
return message.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
class IFC_GEOM_API too_many_faces_exception : public geometry_exception {
|
||||
public:
|
||||
too_many_faces_exception()
|
||||
: geometry_exception("Too many faces for operation") {}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,140 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 GEOMETRYSERIALIZER_H
|
||||
#define GEOMETRYSERIALIZER_H
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/Serializer.h"
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomElement.h"
|
||||
|
||||
class SerializerSettings : public IfcGeom::IteratorSettings
|
||||
{
|
||||
public:
|
||||
enum Setting : uint64_t
|
||||
{
|
||||
/// Use entity names instead of unique IDs for naming elements.
|
||||
/// Applicable for OBJ, DAE, and SVG output.
|
||||
USE_ELEMENT_NAMES = 1U << (IfcGeom::IteratorSettings::NUM_SETTINGS + 1U),
|
||||
/// Use entity GUIDs instead of unique IDs for naming elements.
|
||||
/// Applicable for OBJ, DAE, and SVG output.
|
||||
USE_ELEMENT_GUIDS = 1U << (IfcGeom::IteratorSettings::NUM_SETTINGS + 2U),
|
||||
/// Use material names instead of unique IDs for naming materials.
|
||||
/// Applicable for OBJ and DAE output.
|
||||
USE_MATERIAL_NAMES = 1U << (IfcGeom::IteratorSettings::NUM_SETTINGS + 3U),
|
||||
/// Use element types instead of unique IDs for naming elements.
|
||||
/// Applicable for DAE output.
|
||||
USE_ELEMENT_TYPES = 1U << (IfcGeom::IteratorSettings::NUM_SETTINGS + 4U),
|
||||
/// Order the elements using their IfcBuildingStorey parent
|
||||
/// Applicable for DAE output
|
||||
USE_ELEMENT_HIERARCHY = 1U << (IfcGeom::IteratorSettings::NUM_SETTINGS + 5U),
|
||||
/// Use step ids for naming elements.
|
||||
/// Applicable for OBJ, DAE, and SVG output.
|
||||
USE_ELEMENT_STEPIDS = 1U << (IfcGeom::IteratorSettings::NUM_SETTINGS + 6U),
|
||||
/// Use Y UP .
|
||||
/// Applicable for OBJ output.
|
||||
USE_Y_UP = 1ULL << (IfcGeom::IteratorSettings::NUM_SETTINGS + 7ULL),
|
||||
/// Number of different setting flags.
|
||||
NUM_SETTINGS = 7
|
||||
};
|
||||
|
||||
SerializerSettings()
|
||||
: precision(DEFAULT_PRECISION) { }
|
||||
|
||||
/// Sets the precision used to format floating-point values, 15 by default.
|
||||
/// Use a negative value to use the system's default precision (should be 6 typically).
|
||||
short precision;
|
||||
|
||||
enum { DEFAULT_PRECISION = 15 };
|
||||
};
|
||||
|
||||
class stream_or_filename {
|
||||
private:
|
||||
std::shared_ptr<std::ofstream> ofs_;
|
||||
std::shared_ptr<std::ostringstream> oss_;
|
||||
boost::optional<std::string> filename_;
|
||||
|
||||
public:
|
||||
std::ostream& stream;
|
||||
|
||||
stream_or_filename(const std::string& fn)
|
||||
: ofs_(new std::ofstream(IfcUtil::path::from_utf8(fn).c_str()))
|
||||
, stream(*ofs_)
|
||||
{}
|
||||
|
||||
stream_or_filename()
|
||||
: oss_(new std::ostringstream)
|
||||
, stream(*oss_)
|
||||
{}
|
||||
|
||||
std::string get_value() const {
|
||||
return oss_->str();
|
||||
}
|
||||
|
||||
boost::optional<std::string> filename() const {
|
||||
return filename_;
|
||||
}
|
||||
|
||||
bool is_ready() {
|
||||
if (ofs_) {
|
||||
return ofs_->is_open();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GeometrySerializer : public Serializer {
|
||||
public:
|
||||
enum read_type { READ_BREP, READ_TRIANGULATION };
|
||||
|
||||
GeometrySerializer(const SerializerSettings& settings) : settings_(settings) {}
|
||||
virtual ~GeometrySerializer() {}
|
||||
|
||||
virtual bool isTesselated() const = 0;
|
||||
virtual void write(const IfcGeom::TriangulationElement* o) = 0;
|
||||
virtual void write(const IfcGeom::BRepElement* o) = 0;
|
||||
virtual void setUnitNameAndMagnitude(const std::string& name, float magnitude) = 0;
|
||||
virtual IfcGeom::Element* read(IfcParse::IfcFile& f, const std::string& guid, const std::string& representation_id, read_type rt = READ_BREP) = 0;
|
||||
|
||||
const SerializerSettings& settings() const { return settings_; }
|
||||
SerializerSettings& settings() { return settings_; }
|
||||
|
||||
/// Returns ID for the object depending on the used setting.
|
||||
virtual std::string object_id(const IfcGeom::Element* o)
|
||||
{
|
||||
if (settings_.get(SerializerSettings::USE_ELEMENT_GUIDS)) return o->guid();
|
||||
if (settings_.get(SerializerSettings::USE_ELEMENT_NAMES)) return o->name();
|
||||
if (settings_.get(SerializerSettings::USE_ELEMENT_STEPIDS)) return "id-" + boost::lexical_cast<std::string>(o->id());
|
||||
return o->unique_id();
|
||||
}
|
||||
|
||||
protected:
|
||||
SerializerSettings settings_;
|
||||
};
|
||||
|
||||
class WriteOnlyGeometrySerializer : public GeometrySerializer {
|
||||
public:
|
||||
WriteOnlyGeometrySerializer(const SerializerSettings& settings) : GeometrySerializer(settings) {}
|
||||
|
||||
virtual IfcGeom::Element* read(IfcParse::IfcFile&, const std::string&, const std::string&, read_type = READ_BREP) {
|
||||
throw std::runtime_error("Not supported");
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,221 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 IFCGEOMELEMENT_H
|
||||
#define IFCGEOMELEMENT_H
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../ifcparse/Argument.h"
|
||||
#include "../ifcparse/IfcGlobalId.h"
|
||||
#include "../ifcparse/IfcLogger.h"
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomRepresentation.h"
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomIteratorSettings.h"
|
||||
#include "../ifcgeom_schema_agnostic/ifc_geom_api.h"
|
||||
|
||||
namespace IfcGeom {
|
||||
|
||||
class Matrix {
|
||||
private:
|
||||
std::vector<double> _data;
|
||||
public:
|
||||
Matrix(const ElementSettings& settings, const gp_Trsf& trsf) {
|
||||
// Convert the gp_Trsf into a 4x3 Matrix
|
||||
// Note that in case the CONVERT_BACK_UNITS setting is enabled
|
||||
// the translation component of the matrix needs to be divided
|
||||
// by the magnitude of the IFC model length unit because
|
||||
// internally in IfcOpenShell everything is measured in meters.
|
||||
for(int i = 1; i < 5; ++i) {
|
||||
for (int j = 1; j < 4; ++j) {
|
||||
const double trsf_value = trsf.Value(j,i);
|
||||
const double matrix_value = i == 4 && settings.get(IteratorSettings::CONVERT_BACK_UNITS)
|
||||
? trsf_value / settings.unit_magnitude()
|
||||
: trsf_value;
|
||||
_data.push_back(static_cast<double>(matrix_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::vector<double>& data() const { return _data; }
|
||||
};
|
||||
|
||||
class Transformation {
|
||||
private:
|
||||
ElementSettings settings_;
|
||||
gp_Trsf trsf_;
|
||||
Matrix matrix_;
|
||||
public:
|
||||
Transformation(const ElementSettings& settings, const gp_Trsf& trsf)
|
||||
: settings_(settings)
|
||||
, trsf_(trsf)
|
||||
, matrix_(settings, trsf)
|
||||
{}
|
||||
const gp_Trsf& data() const { return trsf_; }
|
||||
const Matrix& matrix() const { return matrix_; }
|
||||
|
||||
Transformation inverted() const {
|
||||
return Transformation(settings_, trsf_.Inverted());
|
||||
}
|
||||
|
||||
Transformation multiplied(const Transformation& other) const {
|
||||
return Transformation(settings_, trsf_.Multiplied(other.data()));
|
||||
}
|
||||
};
|
||||
|
||||
class Element {
|
||||
private:
|
||||
int _id;
|
||||
int _parent_id;
|
||||
std::string _name;
|
||||
std::string _type;
|
||||
std::string _guid;
|
||||
std::string _context;
|
||||
std::string _unique_id;
|
||||
Transformation _transformation;
|
||||
IfcUtil::IfcBaseEntity* product_;
|
||||
std::vector<const IfcGeom::Element*> _parents;
|
||||
public:
|
||||
|
||||
friend bool operator == (const Element& element1, const Element& element2) {
|
||||
return element1.id() == element2.id();
|
||||
}
|
||||
|
||||
// Use the id to compare, or the elevation is the elements are IfcBuildingStoreys and the elevation is set
|
||||
friend bool operator < (const Element& element1, const Element& element2) {
|
||||
if (element1.type() == "IfcBuildingStorey" && element2.type() == "IfcBuildingStorey") {
|
||||
size_t attr_index = element1.product()->declaration().attribute_index("Elevation");
|
||||
Argument* elev_attr1 = element1.product()->data().getArgument(attr_index);
|
||||
Argument* elev_attr2 = element2.product()->data().getArgument(attr_index);
|
||||
|
||||
if (!elev_attr1->isNull() && !elev_attr2->isNull()) {
|
||||
double elev1 = *elev_attr1;
|
||||
double elev2 = *elev_attr2;
|
||||
|
||||
return elev1 < elev2;
|
||||
}
|
||||
}
|
||||
|
||||
return element1.id() < element2.id();
|
||||
}
|
||||
|
||||
int id() const { return _id; }
|
||||
int parent_id() const { return _parent_id; }
|
||||
const std::string& name() const { return _name; }
|
||||
const std::string& type() const { return _type; }
|
||||
const std::string& guid() const { return _guid; }
|
||||
const std::string& context() const { return _context; }
|
||||
const std::string& unique_id() const { return _unique_id; }
|
||||
const Transformation& transformation() const { return _transformation; }
|
||||
IfcUtil::IfcBaseEntity* product() const { return product_; }
|
||||
const std::vector<const IfcGeom::Element*> parents() const { return _parents; }
|
||||
void SetParents(std::vector<const IfcGeom::Element*> newparents) { _parents = newparents; }
|
||||
|
||||
Element(const ElementSettings& settings, int id, int parent_id, const std::string& name, const std::string& type,
|
||||
const std::string& guid, const std::string& context, const gp_Trsf& trsf, IfcUtil::IfcBaseEntity* product)
|
||||
: _id(id), _parent_id(parent_id), _name(name), _type(type), _guid(guid), _context(context), _transformation(settings, trsf)
|
||||
, product_(product)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
if (type == "IfcProject") {
|
||||
oss << "project";
|
||||
} else {
|
||||
try {
|
||||
oss << "product-" << IfcParse::IfcGlobalId(guid).formatted();
|
||||
} catch (const std::exception& e) {
|
||||
oss << "product";
|
||||
Logger::Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_context.empty()) {
|
||||
std::string ctx = _context;
|
||||
boost::to_lower(ctx);
|
||||
boost::replace_all(ctx, " ", "-");
|
||||
oss << "-" << ctx;
|
||||
}
|
||||
|
||||
_unique_id = oss.str();
|
||||
}
|
||||
virtual ~Element() {}
|
||||
};
|
||||
|
||||
class BRepElement : public Element {
|
||||
private:
|
||||
boost::shared_ptr<IfcGeom::Representation::BRep> _geometry;
|
||||
public:
|
||||
const boost::shared_ptr<IfcGeom::Representation::BRep>& geometry_pointer() const { return _geometry; }
|
||||
const IfcGeom::Representation::BRep& geometry() const { return *_geometry; }
|
||||
BRepElement(int id, int parent_id, const std::string& name, const std::string& type, const std::string& guid,
|
||||
const std::string& context, const gp_Trsf& trsf, const boost::shared_ptr<IfcGeom::Representation::BRep>& geometry,
|
||||
IfcUtil::IfcBaseEntity* product)
|
||||
: Element(geometry->settings() ,id, parent_id, name, type, guid, context, trsf, product)
|
||||
, _geometry(geometry)
|
||||
{}
|
||||
|
||||
bool calculate_projected_surface_area(double& along_x, double& along_y, double& along_z) const {
|
||||
const auto& trsf = this->transformation().data();
|
||||
const gp_Mat& mat = trsf.HVectorialPart();
|
||||
gp_Ax3 ax(trsf.TranslationPart(), mat.Column(3), mat.Column(1));
|
||||
return geometry().calculate_projected_surface_area(ax, along_x, along_y, along_z);
|
||||
}
|
||||
private:
|
||||
BRepElement(const BRepElement& other);
|
||||
BRepElement& operator=(const BRepElement& other);
|
||||
};
|
||||
|
||||
class TriangulationElement : public Element {
|
||||
private:
|
||||
boost::shared_ptr< IfcGeom::Representation::Triangulation > _geometry;
|
||||
public:
|
||||
const IfcGeom::Representation::Triangulation& geometry() const { return *_geometry; }
|
||||
const boost::shared_ptr< IfcGeom::Representation::Triangulation>& geometry_pointer() const { return _geometry; }
|
||||
TriangulationElement(const IfcGeom::BRepElement& shape_model)
|
||||
: Element(shape_model)
|
||||
, _geometry(boost::shared_ptr<IfcGeom::Representation::Triangulation>(new IfcGeom::Representation::Triangulation(shape_model.geometry())))
|
||||
{}
|
||||
TriangulationElement(const IfcGeom::Element& element, const boost::shared_ptr<IfcGeom::Representation::Triangulation>& geometry)
|
||||
: Element(element)
|
||||
, _geometry(geometry)
|
||||
{}
|
||||
private:
|
||||
TriangulationElement(const TriangulationElement& other);
|
||||
TriangulationElement& operator=(const TriangulationElement& other);
|
||||
};
|
||||
|
||||
class SerializedElement : public Element {
|
||||
private:
|
||||
IfcGeom::Representation::Serialization* _geometry;
|
||||
public:
|
||||
const IfcGeom::Representation::Serialization& geometry() const { return *_geometry; }
|
||||
SerializedElement(const BRepElement& shape_model)
|
||||
: Element(shape_model)
|
||||
, _geometry(new IfcGeom::Representation::Serialization(shape_model.geometry()))
|
||||
{}
|
||||
virtual ~SerializedElement() {
|
||||
delete _geometry;
|
||||
}
|
||||
private:
|
||||
SerializedElement(const SerializedElement& other);
|
||||
SerializedElement& operator=(const SerializedElement& other);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,266 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
/** @file IfcGeomFilter.h
|
||||
@brief A set of predefined product filters for IfcGeom::Iterator */
|
||||
|
||||
#ifndef IFCGEOMFILTER_H
|
||||
#define IFCGEOMFILTER_H
|
||||
|
||||
#include <boost/version.hpp>
|
||||
#if BOOST_VERSION >= 107800 && defined(_MSC_VER)
|
||||
#define BOOST_REGEX_NO_W32
|
||||
#endif
|
||||
|
||||
#include "Kernel.h"
|
||||
#include "../ifcparse/IfcFile.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace IfcGeom {
|
||||
/// The filter function (free or member function) or function object (use boost::ref() to reference to it)
|
||||
/// should return true if the geometry for the product is wanted to be included in the output.
|
||||
/// http://www.boost.org/doc/libs/1_62_0/doc/html/function/tutorial.html
|
||||
typedef boost::function<bool(IfcUtil::IfcBaseEntity*)> filter_t;
|
||||
|
||||
struct filter
|
||||
{
|
||||
filter() : include(false), traverse(false), traverse_openings(false) {}
|
||||
filter(bool incl, bool trav, bool trav_openings = false) : include(incl), traverse(trav), traverse_openings(trav_openings) {}
|
||||
/// Should the product be included (true) or excluded (false).
|
||||
bool include;
|
||||
/// If traversal requested, traverse to the parents to see if they satisfy the criteria. E.g. we might be looking for
|
||||
/// children of a storey named "Level 20", or children of entities that have no representation, e.g. IfcCurtainWall.
|
||||
bool traverse;
|
||||
/// Include opening relationships as part of traversal.
|
||||
bool traverse_openings;
|
||||
/// Optional description for the filtering criteria of this filter.
|
||||
std::string description;
|
||||
|
||||
bool match(IfcUtil::IfcBaseEntity* prod, const filter_t& pred) const {
|
||||
bool is_match = pred(prod);
|
||||
if (!is_match && traverse) {
|
||||
is_match = traverse_match(prod, pred);
|
||||
}
|
||||
return is_match == include;
|
||||
}
|
||||
|
||||
bool traverse_match(IfcUtil::IfcBaseEntity* prod, const filter_t& pred) const
|
||||
{
|
||||
IfcUtil::IfcBaseEntity* parent, *current = prod;
|
||||
while ((parent = IfcGeom::Kernel::get_decomposing_entity(current, traverse_openings)) != nullptr) {
|
||||
if (pred(parent)) {
|
||||
return true;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct wildcard_filter : public filter {
|
||||
wildcard_filter() : filter(false, false) {}
|
||||
wildcard_filter(bool include, bool traverse, const std::set<std::string>& patterns)
|
||||
: filter(include, traverse) {
|
||||
populate(patterns);
|
||||
}
|
||||
|
||||
std::set<boost::regex> values;
|
||||
|
||||
void populate(const std::set<std::string>& patterns) {
|
||||
values.clear();
|
||||
for(auto& pattern: patterns) {
|
||||
values.insert(wildcard_string_to_regex(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
bool match(const std::string &str) const { return match_values(values, str); }
|
||||
|
||||
static bool match_values(const std::set<boost::regex>& values, const std::string &str) {
|
||||
for(auto& r: values) {
|
||||
if (boost::regex_match(str, r)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static boost::regex wildcard_string_to_regex(std::string str) {
|
||||
// Escape all non-"*?" regex special chars
|
||||
static const std::string special_chars = "\\^.$|()[]+/";
|
||||
for(auto c: special_chars) {
|
||||
std::string char_str(1, c);
|
||||
boost::replace_all(str, char_str, "\\" + char_str);
|
||||
}
|
||||
// Convert "*?" to their regex equivalents
|
||||
boost::replace_all(str, "?", ".");
|
||||
boost::replace_all(str, "*", ".*");
|
||||
return boost::regex(str);
|
||||
}
|
||||
};
|
||||
|
||||
/// @note supports only string arguments for now
|
||||
struct attribute_filter : public wildcard_filter {
|
||||
std::string attribute_name;
|
||||
|
||||
attribute_filter() {}
|
||||
attribute_filter(const std::string& attribute_name)
|
||||
: attribute_name(attribute_name) {}
|
||||
|
||||
std::string value(IfcUtil::IfcBaseEntity* prod) const {
|
||||
try {
|
||||
return (std::string) *prod->get(attribute_name);
|
||||
} catch (...) {
|
||||
// Either
|
||||
// (a) not an attribute name for this entity instance
|
||||
// (b) not a string attribute
|
||||
// (c) null for this entity instance
|
||||
// @todo: validate the filters with a reference to the schema
|
||||
// probably in IfcGeomIteratorImplementation
|
||||
return "<invalid>";
|
||||
}
|
||||
}
|
||||
|
||||
bool match(IfcUtil::IfcBaseEntity* prod) const {
|
||||
return wildcard_filter::match(value(prod));
|
||||
}
|
||||
|
||||
bool operator()(IfcUtil::IfcBaseEntity* prod) const {
|
||||
return filter::match(prod, std::bind(&attribute_filter::match, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void update_description() {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << (traverse ? "traverse " : "") << (include ? "include" : "exclude");
|
||||
std::vector<std::string> patterns;
|
||||
for (auto& r : values) {
|
||||
patterns.push_back("\"" + r.str() + "\"");
|
||||
}
|
||||
|
||||
ss << " " << attribute_name;
|
||||
ss << " values " << boost::algorithm::join(patterns, " ");
|
||||
|
||||
description = ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct layer_filter : public wildcard_filter {
|
||||
typedef std::map<std::string, IfcUtil::IfcBaseEntity*> layer_map_t;
|
||||
|
||||
layer_filter() {}
|
||||
layer_filter(bool include, bool traverse, const std::set<std::string>& patterns)
|
||||
: wildcard_filter(include, traverse, patterns) {}
|
||||
|
||||
bool match(IfcUtil::IfcBaseEntity* prod) const {
|
||||
layer_map_t layers = IfcGeom::Kernel::get_layers(prod);
|
||||
return std::find_if(layers.begin(), layers.end(), wildcards_match(values)) != layers.end();
|
||||
}
|
||||
|
||||
bool operator()(IfcUtil::IfcBaseEntity* prod) const {
|
||||
return filter::match(prod, std::bind(&layer_filter::match, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
struct wildcards_match {
|
||||
wildcards_match(const std::set<boost::regex>& patterns) : patterns(patterns) {}
|
||||
bool operator()(const layer_map_t::value_type& layer_map_value) const {
|
||||
return wildcard_filter::match_values(patterns, layer_map_value.first);
|
||||
}
|
||||
|
||||
std::set<boost::regex> patterns;
|
||||
};
|
||||
|
||||
void update_description() {
|
||||
std::stringstream ss;
|
||||
ss << (traverse ? "traverse " : "") << (include ? "include" : "exclude") << " layers";
|
||||
std::vector<std::string> str_values;
|
||||
BOOST_FOREACH(const boost::regex& r, values) {
|
||||
str_values.push_back(" \"" + r.str() + "\"");
|
||||
}
|
||||
ss << boost::algorithm::join(str_values, " ");
|
||||
description = ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct entity_filter : public filter {
|
||||
std::set<std::string> entity_names;
|
||||
|
||||
entity_filter() {}
|
||||
entity_filter(bool include, bool traverse, const std::set<std::string>& entity_names)
|
||||
: filter(include, traverse)
|
||||
, entity_names(entity_names) {}
|
||||
|
||||
bool match(IfcUtil::IfcBaseEntity* prod) const {
|
||||
// The set is iterated over to able to filter on subtypes.
|
||||
for (auto& name : entity_names) {
|
||||
if (prod->declaration().is(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(IfcUtil::IfcBaseEntity* prod) const {
|
||||
return filter::match(prod, std::bind(&entity_filter::match, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void update_description() {
|
||||
std::stringstream ss;
|
||||
ss << (traverse ? "traverse " : "") << (include ? "include" : "exclude") << " entities";
|
||||
for (auto& name : entity_names) {
|
||||
ss << " " << name;
|
||||
}
|
||||
description = ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct instance_id_filter : public filter {
|
||||
std::set<int> instance_ids_;
|
||||
|
||||
instance_id_filter() {}
|
||||
instance_id_filter(bool include, bool traverse, const std::set<int>& instance_ids)
|
||||
: filter(include, traverse)
|
||||
, instance_ids_(instance_ids) {}
|
||||
|
||||
bool match(IfcUtil::IfcBaseEntity* prod) const {
|
||||
return instance_ids_.find(prod->data().id()) != instance_ids_.end();
|
||||
}
|
||||
|
||||
bool operator()(IfcUtil::IfcBaseEntity* prod) const {
|
||||
return filter::match(prod, std::bind(&instance_id_filter::match, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void update_description() {
|
||||
std::stringstream ss;
|
||||
ss << (traverse ? "traverse " : "") << (include ? "include" : "exclude") << " ids";
|
||||
for (auto& id : instance_ids_) {
|
||||
ss << " " << id;
|
||||
}
|
||||
description = ss.str();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,145 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
/********************************************************************************
|
||||
* *
|
||||
* Geometrical data in an IFC file consists of shapes (IfcShapeRepresentation) *
|
||||
* and instances (SUBTYPE OF IfcBuildingElement e.g. IfcWindow). *
|
||||
* *
|
||||
* IfcGeom::Representation::Triangulation is a class that represents a *
|
||||
* triangulated IfcShapeRepresentation. *
|
||||
* Triangulation.verts is a 1 dimensional vector of float defining the *
|
||||
* cartesian coordinates of the vertices of the triangulated shape in the *
|
||||
* format of [x1,y1,z1,..,xn,yn,zn] *
|
||||
* Triangulation.faces is a 1 dimensional vector of int containing the *
|
||||
* indices of the triangles referencing positions in Triangulation.verts *
|
||||
* Triangulation.edges is a 1 dimensional vector of int in {0,1} that dictates*
|
||||
* the visibility of the edges that span the faces in Triangulation.faces *
|
||||
* *
|
||||
* IfcGeom::Element represents the actual IfcBuildingElements. *
|
||||
* IfcGeomObject.name is the GUID of the element *
|
||||
* IfcGeomObject.type is the datatype of the element e.g. IfcWindow *
|
||||
* IfcGeomObject.mesh is a pointer to an IfcMesh *
|
||||
* IfcGeomObject.transformation.matrix is a 4x3 matrix that defines the *
|
||||
* orientation and translation of the mesh in relation to the world origin *
|
||||
* *
|
||||
* IfcGeom::Iterator::initialize() *
|
||||
* finds the most suitable representation contexts. Returns true iff *
|
||||
* at least a single representation will process successfully *
|
||||
* *
|
||||
* IfcGeom::Iterator::get() *
|
||||
* returns a pointer to the current IfcGeom::Element *
|
||||
* *
|
||||
* IfcGeom::Iterator::next() *
|
||||
* returns true iff a following entity is available for a successive call to *
|
||||
* IfcGeom::Iterator::get() *
|
||||
* *
|
||||
* IfcGeom::Iterator::progress() *
|
||||
* returns an int in [0..100] that indicates the overall progress *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef IFCGEOMITERATOR_H
|
||||
#define IFCGEOMITERATOR_H
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/IteratorImplementation.h"
|
||||
|
||||
// The infamous min & max Win32 #defines can leak here from OCE depending on the build configuration
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace IfcGeom {
|
||||
|
||||
class IFC_GEOM_API Iterator {
|
||||
private:
|
||||
Iterator(const Iterator&); // N/I
|
||||
Iterator& operator=(const Iterator&); // N/I
|
||||
|
||||
IfcParse::IfcFile* file_;
|
||||
IfcGeom::IteratorSettings settings_;
|
||||
std::vector<IfcGeom::filter_t> filters_;
|
||||
|
||||
IteratorImplementation* implementation_;
|
||||
|
||||
public:
|
||||
Iterator(const IfcGeom::IteratorSettings& settings, IfcParse::IfcFile* file, int num_threads = 1)
|
||||
: file_(file)
|
||||
, settings_(settings)
|
||||
{
|
||||
try {
|
||||
implementation_ = iterator_implementations().construct(file_->schema()->name(), settings, file, filters_, num_threads);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
implementation_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Iterator(const IfcGeom::IteratorSettings& settings, IfcParse::IfcFile* file, const std::vector<IfcGeom::filter_t>& filters, int num_threads = 1)
|
||||
: file_(file)
|
||||
, settings_(settings)
|
||||
, filters_(filters)
|
||||
{
|
||||
try {
|
||||
implementation_ = iterator_implementations().construct(file_->schema()->name(), settings, file, filters_, num_threads);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
implementation_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool initialize() {
|
||||
if (implementation_) {
|
||||
return implementation_->initialize();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int progress() const { return implementation_->progress(); }
|
||||
|
||||
void compute_bounds(bool with_geometry) { implementation_->compute_bounds(with_geometry); }
|
||||
|
||||
const gp_XYZ& bounds_min() const { return implementation_->bounds_min(); }
|
||||
const gp_XYZ& bounds_max() const { return implementation_->bounds_max(); }
|
||||
|
||||
const std::string& unit_name() const { return implementation_->getUnitName(); }
|
||||
|
||||
double unit_magnitude() const { return implementation_->getUnitMagnitude(); }
|
||||
|
||||
IfcParse::IfcFile* file() const { return implementation_->file(); }
|
||||
|
||||
IfcUtil::IfcBaseClass* next() const { return implementation_->next(); }
|
||||
|
||||
Element* get() { return implementation_->get(); }
|
||||
|
||||
BRepElement* get_native() { return implementation_->get_native(); }
|
||||
|
||||
const Element* get_object(int id) { return implementation_->get_object(id); }
|
||||
|
||||
IfcUtil::IfcBaseClass* create() { return implementation_->create(); }
|
||||
|
||||
void set_cache(GeometrySerializer* cache) { return implementation_->set_cache(cache); }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "IfcGeomIteratorSettings.h"
|
||||
|
||||
#include "../ifcparse/IfcLogger.h"
|
||||
|
||||
void IfcGeom::IteratorSettings::set_deflection_tolerance(double value)
|
||||
{
|
||||
deflection_tolerance_ = value;
|
||||
if (deflection_tolerance_ <= 1e-6) {
|
||||
Logger::Message(Logger::LOG_WARNING, "Deflection tolerance cannot be set to <= 1e-6; using the default value 1e-3");
|
||||
deflection_tolerance_ = 1e-3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 IFCGEOMITERATORSETTINGS_H
|
||||
#define IFCGEOMITERATORSETTINGS_H
|
||||
|
||||
#include "ifc_geom_api.h"
|
||||
|
||||
#include "../ifcparse/IfcException.h"
|
||||
#include "../ifcparse/IfcBaseClass.h"
|
||||
|
||||
#include <set>
|
||||
#include <array>
|
||||
|
||||
namespace IfcGeom
|
||||
{
|
||||
class IFC_GEOM_API IteratorSettings
|
||||
{
|
||||
public:
|
||||
/// Enumeration of setting identifiers. These settings define the
|
||||
/// behaviour of various aspects of IfcOpenShell.
|
||||
enum Setting : uint64_t
|
||||
{
|
||||
/// Specifies whether vertices are welded, meaning that the coordinates
|
||||
/// vector will only contain unique xyz-triplets. This results in a
|
||||
/// manifold mesh which is useful for modelling applications, but might
|
||||
/// result in unwanted shading artifacts in rendering applications.
|
||||
WELD_VERTICES = 1,
|
||||
/// Specifies whether to apply the local placements of building elements
|
||||
/// directly to the coordinates of the representation mesh rather than
|
||||
/// to represent the local placement in the 4x3 matrix, which will in that
|
||||
/// case be the identity matrix.
|
||||
USE_WORLD_COORDS = 1 << 1,
|
||||
/// Internally IfcOpenShell measures everything in meters. This settings
|
||||
/// specifies whether to convert IfcGeomObjects back to the units in which
|
||||
/// the geometry in the IFC file is specified.
|
||||
CONVERT_BACK_UNITS = 1 << 2,
|
||||
/// Specifies whether to use the Open Cascade BREP format for representation
|
||||
/// items rather than to create triangle meshes. This is useful is IfcOpenShell
|
||||
/// is used as a library in an application that is also built on Open Cascade.
|
||||
USE_BREP_DATA = 1 << 3,
|
||||
/// Specifies whether to sew IfcConnectedFaceSets (open and closed shells) to
|
||||
/// TopoDS_Shells or whether to keep them as a loose collection of faces.
|
||||
SEW_SHELLS = 1 << 4,
|
||||
/// Disables the subtraction of IfcOpeningElement representations from
|
||||
/// the related building element representations.
|
||||
DISABLE_OPENING_SUBTRACTIONS = 1 << 5,
|
||||
/// Disables the triangulation of the topological representations. Useful if
|
||||
/// the client application understands Open Cascade's native format.
|
||||
DISABLE_TRIANGULATION = 1 << 6,
|
||||
/// Applies default materials to entity instances without a surface style or
|
||||
/// product-level material association.
|
||||
APPLY_DEFAULT_MATERIALS = 1 << 7,
|
||||
/// Specifies whether to include subtypes of IfcCurve.
|
||||
INCLUDE_CURVES = 1 << 8,
|
||||
/// Specifies whether to exclude subtypes of IfcSolidModel and IfcSurface.
|
||||
EXCLUDE_SOLIDS_AND_SURFACES = 1 << 9,
|
||||
/// Disables computation of normals. Saves time and file size and is useful
|
||||
/// in instances where you're going to recompute normals for the exported
|
||||
/// model in other modelling application in any case.
|
||||
NO_NORMALS = 1 << 10,
|
||||
/// Generates UVs by using simple box projection. Requires normals.
|
||||
/// Applicable for OBJ and DAE output.
|
||||
GENERATE_UVS = 1 << 11,
|
||||
/// Specifies whether to slice representations according to associated
|
||||
/// IfcMaterialLayerSets.
|
||||
APPLY_LAYERSETS = 1 << 12,
|
||||
/// Emit the relative placements from IFC instead of a flat listing of
|
||||
/// absolute placements.
|
||||
ELEMENT_HIERARCHY = 1 << 13,
|
||||
/// Emit placements relative to the IfcSite. Useful if the IfcSite itself
|
||||
/// introduces a placement with a large geospatial offset that inhibits
|
||||
/// rendering.
|
||||
SITE_LOCAL_PLACEMENT = 1 << 14,
|
||||
/// Emit placements relative to the IfcBuilding. Useful if the IfcBuilding
|
||||
/// itself introduces a placement with a large geospatial offset that
|
||||
/// inhibits rendering.
|
||||
BUILDING_LOCAL_PLACEMENT = 1 << 15,
|
||||
/// After geometry interpretation, lookup an IfcOpenShell-specific quantity set
|
||||
/// and compare values for validation.
|
||||
VALIDATE_QUANTITIES = 1 << 16,
|
||||
/// Assigns the first layer material to the entire product
|
||||
LAYERSET_FIRST = 1 << 17,
|
||||
/// Adds arrow heads to edge segments to signify edge direction. Useful as a
|
||||
/// debugging mechanism for face orientation or advanced brep IfcOrientedEdge.
|
||||
EDGE_ARROWS = 1 << 18,
|
||||
/// Disables the evaluation of IfcBooleanResult and simply returns FirstOperand
|
||||
DISABLE_BOOLEAN_RESULT = 1 << 19,
|
||||
/// Disables wire intersection checks. These checks are done on faces to prevent
|
||||
/// self-intersections of face bounds. Self-intersections reduce the reliability
|
||||
/// of boolean operations and may lead to crashes.
|
||||
NO_WIRE_INTERSECTION_CHECK = 1 << 20,
|
||||
/// Set wire intersection tolerance to 0. By default the above check is done
|
||||
/// using a tolerance criterium. So that when a vertex is a certain epsilon
|
||||
/// distance away from an edge this is flagged as an intersection.
|
||||
NO_WIRE_INTERSECTION_TOLERANCE = 1 << 21,
|
||||
/// Strictly use the tolerance from the IFC model. Typically this value is
|
||||
/// increased 10-fold to have more reliable boolean subtraction results.
|
||||
STRICT_TOLERANCE = 1 << 22,
|
||||
/// Write boolean operands to file in current directory for debugging purposes
|
||||
DEBUG_BOOLEAN = 1 << 23,
|
||||
/// Try to perform boolean subtractions in 2d. Defaults to true.
|
||||
BOOLEAN_ATTEMPT_2D = 1 << 24,
|
||||
/// Number of different setting flags.
|
||||
NUM_SETTINGS = 25,
|
||||
};
|
||||
|
||||
IteratorSettings()
|
||||
: settings_(WELD_VERTICES | BOOLEAN_ATTEMPT_2D) // OR options that default to true here
|
||||
, deflection_tolerance_(1.e-3)
|
||||
, angular_tolerance_(0.5)
|
||||
{
|
||||
}
|
||||
|
||||
/// Note that this is independent of the IFC length unit, one millimeter by default.
|
||||
double deflection_tolerance() const { return deflection_tolerance_; }
|
||||
double angular_tolerance() const { return angular_tolerance_; }
|
||||
double force_space_transparency() const { return force_space_transparency_; }
|
||||
std::set<int> context_ids() const { return context_ids_; }
|
||||
|
||||
/// @todo Using deflection tolerance of 1e-6 or smaller hangs the conversion, research more in-depth.
|
||||
/// This bug can be reproduced e.g. with the Duplex model that can be found from http://www.nibs.org/?page=bsa_commonbimfiles#project1
|
||||
void set_deflection_tolerance(double value);
|
||||
|
||||
void set_angular_tolerance(double value) {
|
||||
angular_tolerance_ = value;
|
||||
}
|
||||
|
||||
void force_space_transparency(double value) {
|
||||
force_space_transparency_ = value;
|
||||
}
|
||||
|
||||
void set_context_ids(std::vector<int> value) {
|
||||
context_ids_ = std::set<int>(value.begin(), value.end());
|
||||
}
|
||||
|
||||
/// Get boolean value for a single settings or for a combination of settings.
|
||||
bool get(uint64_t setting) const
|
||||
{
|
||||
/// @todo If unknown setting value/combination: throw IfcParse::IfcException("Invalid IteratorSetting")?
|
||||
return (settings_ & setting) != 0;
|
||||
}
|
||||
|
||||
/// Set boolean value for a single settings or for a combination of settings.
|
||||
void set(uint64_t setting, bool value)
|
||||
{
|
||||
/// @todo If unknown setting value/combination: throw IfcParse::IfcException("Invalid IteratorSetting")?
|
||||
if (value) {
|
||||
settings_ |= setting;
|
||||
} else {
|
||||
settings_ &= ~setting;
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional offset that is applied to serialized objects, (0,0,0) by default.
|
||||
std::array<double,3> offset = std::array<double,3>{0.0, 0.0, 0.0};
|
||||
/// Optional rotation that is applied to serialized objects, (0,0,0,1) by default.
|
||||
std::array<double,4> rotation = std::array<double,4>{0.0, 0.0, 0.0, 1.0};
|
||||
|
||||
uint64_t get_raw() const {
|
||||
return settings_;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t settings_;
|
||||
double deflection_tolerance_, angular_tolerance_, force_space_transparency_;
|
||||
std::set<int> context_ids_;
|
||||
};
|
||||
|
||||
class IFC_GEOM_API ElementSettings : public IteratorSettings
|
||||
{
|
||||
public:
|
||||
ElementSettings(const IteratorSettings& settings,
|
||||
double unit_magnitude,
|
||||
const std::string& element_type)
|
||||
: IteratorSettings(settings)
|
||||
, unit_magnitude_(unit_magnitude)
|
||||
, element_type_(element_type)
|
||||
{
|
||||
}
|
||||
|
||||
double unit_magnitude() const { return unit_magnitude_; }
|
||||
const std::string& element_type() const { return element_type_; }
|
||||
|
||||
private:
|
||||
double unit_magnitude_;
|
||||
std::string element_type_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "IfcGeomMaterial.h"
|
||||
|
||||
static double black[3] = {0.,0.,0.};
|
||||
static const std::string no_name = "";
|
||||
|
||||
IfcGeom::Material::Material() {}
|
||||
IfcGeom::Material::Material(const std::shared_ptr<const IfcGeom::SurfaceStyle>& style) : style(style) {}
|
||||
|
||||
bool IfcGeom::Material::hasDiffuse() const { return style && style->Diffuse() ? true : false; }
|
||||
bool IfcGeom::Material::hasSpecular() const { return style && style->Specular() ? true : false; }
|
||||
bool IfcGeom::Material::hasTransparency() const { return style && style->Transparency() ? true : false; }
|
||||
bool IfcGeom::Material::hasSpecularity() const { return style && style->Specularity() ? true : false; }
|
||||
const double* IfcGeom::Material::diffuse() const { if (hasDiffuse()) return &((*style->Diffuse()).R()); else return black; }
|
||||
const double* IfcGeom::Material::specular() const { if (hasSpecular()) return &((*style->Specular()).R()); else return black; }
|
||||
double IfcGeom::Material::transparency() const { if (hasTransparency()) return *style->Transparency(); else return 0; }
|
||||
double IfcGeom::Material::specularity() const { if (hasSpecularity()) return *style->Specularity(); else return 0; }
|
||||
const std::string &IfcGeom::Material::name() const { return style ? style->Name() : no_name; }
|
||||
const std::string &IfcGeom::Material::original_name() const { return style ? style->original_name() : no_name; }
|
||||
bool IfcGeom::Material::operator==(const IfcGeom::Material& other) const { return style == other.style; }
|
||||
|
||||
const IfcGeom::SurfaceStyle& IfcGeom::Material::get_style() const {
|
||||
return *style;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 IFCGEOMMATERIAL_H
|
||||
#define IFCGEOMMATERIAL_H
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomRenderStyles.h"
|
||||
|
||||
namespace IfcGeom {
|
||||
|
||||
class IFC_GEOM_API Material {
|
||||
private:
|
||||
std::shared_ptr<const IfcGeom::SurfaceStyle> style;
|
||||
public:
|
||||
Material();
|
||||
explicit Material(const std::shared_ptr<const IfcGeom::SurfaceStyle>&);
|
||||
|
||||
bool hasDiffuse() const;
|
||||
bool hasSpecular() const;
|
||||
bool hasTransparency() const;
|
||||
bool hasSpecularity() const;
|
||||
const double* diffuse() const;
|
||||
const double* specular() const;
|
||||
double transparency() const;
|
||||
double specularity() const;
|
||||
const std::string &name() const;
|
||||
const std::string &original_name() const;
|
||||
bool operator==(const Material& other) const;
|
||||
|
||||
const IfcGeom::SurfaceStyle& get_style() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 IFCGEOMRENDERSTYLES_H
|
||||
#define IFCGEOMRENDERSTYLES_H
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/ifc_geom_api.h"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
|
||||
namespace IfcGeom {
|
||||
class IFC_GEOM_API SurfaceStyle {
|
||||
public:
|
||||
class ColorComponent {
|
||||
private:
|
||||
double data[3];
|
||||
public:
|
||||
ColorComponent(double r, double g, double b) {
|
||||
data[0] = r; data[1] = g; data[2] = b;
|
||||
}
|
||||
const double& R() const { return data[0]; }
|
||||
const double& G() const { return data[1]; }
|
||||
const double& B() const { return data[2]; }
|
||||
double& R() { return data[0]; }
|
||||
double& G() { return data[1]; }
|
||||
double& B() { return data[2]; }
|
||||
};
|
||||
private:
|
||||
std::string name;
|
||||
std::string original_name_;
|
||||
boost::optional<int> id;
|
||||
boost::optional<ColorComponent> diffuse, specular;
|
||||
boost::optional<double> transparency;
|
||||
boost::optional<double> specularity;
|
||||
public:
|
||||
SurfaceStyle() : name("surface-style") {}
|
||||
SurfaceStyle(int id) : id(id) {
|
||||
std::stringstream sstr;
|
||||
sstr << "surface-style-" << id;
|
||||
this->name = sstr.str();
|
||||
}
|
||||
SurfaceStyle(const std::string& name) : name(name), original_name_(name) {}
|
||||
SurfaceStyle(int id, const std::string& name) : original_name_(name), id(id)
|
||||
{
|
||||
std::stringstream sstr;
|
||||
std::string sanitized = name;
|
||||
boost::to_lower(sanitized);
|
||||
std::replace_if(sanitized.begin(), sanitized.end(), [](char c) {
|
||||
return c < 0x21 || c > 0x7e;
|
||||
}, '-');
|
||||
sstr << "surface-style-" << id << "-" << sanitized;
|
||||
this->name = sstr.str();
|
||||
}
|
||||
|
||||
// Not used at this point. In fact, equality testing in the current
|
||||
// architecture can just as easily be accomplished by comparing the
|
||||
// pointer addresses of the styles, as they are always referenced
|
||||
// from out of a global map of some sort.
|
||||
bool operator==(const SurfaceStyle& other) {
|
||||
return name == other.name;
|
||||
}
|
||||
|
||||
/// ID name, e.g. "surface-style-66675-metal---aluminium"
|
||||
const std::string& Name() const { return name; }
|
||||
|
||||
/// Original name, if available, e.g. "Metal - Aluminium"
|
||||
const std::string& original_name() const { return original_name_; }
|
||||
|
||||
const boost::optional<ColorComponent>& Diffuse() const { return diffuse; }
|
||||
const boost::optional<ColorComponent>& Specular() const { return specular; }
|
||||
const boost::optional<double>& Transparency() const { return transparency; }
|
||||
const boost::optional<double>& Specularity() const { return specularity; }
|
||||
const boost::optional<int>& Id() const { return id; }
|
||||
|
||||
boost::optional<ColorComponent>& Diffuse() { return diffuse; }
|
||||
boost::optional<ColorComponent>& Specular() { return specular; }
|
||||
boost::optional<double>& Transparency() { return transparency; }
|
||||
boost::optional<double>& Specularity() { return specularity; }
|
||||
boost::optional<int>& Id() { return id; }
|
||||
};
|
||||
|
||||
IFC_GEOM_API std::shared_ptr<const IfcGeom::SurfaceStyle> get_default_style(const std::string& ifc_type);
|
||||
|
||||
IFC_GEOM_API SurfaceStyle& update_default_style(const std::string& ifc_type);
|
||||
|
||||
IFC_GEOM_API void set_default_style_file(const std::string& json_file);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,532 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "IfcGeomRepresentation.h"
|
||||
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <BRep_Builder.hxx>
|
||||
#include <Geom_Plane.hxx>
|
||||
#include <TopoDS_Compound.hxx>
|
||||
#include <BRepGProp.hxx>
|
||||
#include <GProp_GProps.hxx>
|
||||
|
||||
#include "../ifcparse/IfcLogger.h"
|
||||
#include "../ifcgeom_schema_agnostic/Kernel.h"
|
||||
#include "../ifcgeom_schema_agnostic/base_utils.h"
|
||||
|
||||
IfcGeom::Representation::Serialization::Serialization(const BRep& brep)
|
||||
: Representation(brep.settings())
|
||||
, id_(brep.id())
|
||||
{
|
||||
TopoDS_Compound compound = brep.as_compound();
|
||||
for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = brep.begin(); it != brep.end(); ++ it) {
|
||||
int sid = -1;
|
||||
|
||||
if (it->hasStyle() && it->Style().Diffuse()) {
|
||||
const IfcGeom::SurfaceStyle::ColorComponent& clr = *it->Style().Diffuse();
|
||||
surface_styles_.push_back(clr.R());
|
||||
surface_styles_.push_back(clr.G());
|
||||
surface_styles_.push_back(clr.B());
|
||||
|
||||
sid = it->Style().Id().get_value_or(-1);
|
||||
} else {
|
||||
surface_styles_.push_back(-1.);
|
||||
surface_styles_.push_back(-1.);
|
||||
surface_styles_.push_back(-1.);
|
||||
}
|
||||
|
||||
if (it->hasStyle() && it->Style().Transparency()) {
|
||||
surface_styles_.push_back(1. - *it->Style().Transparency());
|
||||
} else {
|
||||
surface_styles_.push_back(1.);
|
||||
}
|
||||
|
||||
surface_style_ids_.push_back(sid);
|
||||
}
|
||||
std::stringstream sstream;
|
||||
BRepTools::Write(compound,sstream);
|
||||
brep_data_ = sstream.str();
|
||||
}
|
||||
|
||||
// todo copied from kernel
|
||||
#include <BRepBuilderAPI_Transform.hxx>
|
||||
#include <BRepBuilderAPI_GTransform.hxx>
|
||||
|
||||
TopoDS_Shape 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 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());
|
||||
}
|
||||
}
|
||||
|
||||
TopoDS_Compound IfcGeom::Representation::BRep::as_compound(bool force_meters) const {
|
||||
TopoDS_Compound compound;
|
||||
BRep_Builder builder;
|
||||
builder.MakeCompound(compound);
|
||||
for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = begin(); it != end(); ++it) {
|
||||
const TopoDS_Shape& s = it->Shape();
|
||||
gp_GTrsf trsf = it->Placement();
|
||||
|
||||
if (!force_meters && settings().get(IteratorSettings::CONVERT_BACK_UNITS)) {
|
||||
gp_Trsf scale;
|
||||
scale.SetScaleFactor(1.0 / settings().unit_magnitude());
|
||||
trsf.PreMultiply(scale);
|
||||
}
|
||||
|
||||
const TopoDS_Shape moved_shape = apply_transformation(s, trsf);
|
||||
builder.Add(compound, moved_shape);
|
||||
}
|
||||
return compound;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void accumulate(const gp_Ax3& ax, const gp_Dir& normal, double area, double& along_x, double& along_y, double& along_z) {
|
||||
along_x += area * fabs(ax.XDirection().Dot(normal));
|
||||
along_y += area * fabs(ax.YDirection().Dot(normal));
|
||||
along_z += area * fabs(ax.Direction().Dot(normal));
|
||||
}
|
||||
|
||||
void surface_area_along_direction(double tol, const TopoDS_Shape& s, const gp_Ax3& ax, double& along_x, double& along_y, double& along_z) {
|
||||
along_x = along_y = along_z = 0.;
|
||||
|
||||
bool meshed = false;
|
||||
|
||||
TopExp_Explorer exp(s, TopAbs_FACE);
|
||||
for (; exp.More(); exp.Next()) {
|
||||
const TopoDS_Face& face = TopoDS::Face(exp.Current());
|
||||
Handle(Geom_Surface) surf = BRep_Tool::Surface(face);
|
||||
Handle(Geom_Plane) plane = Handle(Geom_Plane)::DownCast(surf);
|
||||
|
||||
if (surf->DynamicType() == STANDARD_TYPE(Geom_Plane)) {
|
||||
GProp_GProps prop_area;
|
||||
BRepGProp::SurfaceProperties(face, prop_area);
|
||||
const double area = prop_area.Mass();
|
||||
|
||||
accumulate(ax, plane->Position().Direction(), area, along_x, along_y, along_z);
|
||||
} else {
|
||||
|
||||
if (!meshed) {
|
||||
try {
|
||||
BRepMesh_IncrementalMesh(s, tol);
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape");
|
||||
return;
|
||||
}
|
||||
meshed = true;
|
||||
}
|
||||
|
||||
TopLoc_Location loc;
|
||||
Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(face, loc);
|
||||
if (!tri.IsNull()) {
|
||||
std::vector<gp_XYZ> coords;
|
||||
coords.reserve(tri->NbNodes());
|
||||
|
||||
for (int i = 1; i <= tri->NbNodes(); ++i) {
|
||||
coords.push_back(tri->Node(i).Transformed(loc).XYZ());
|
||||
}
|
||||
|
||||
const Poly_Array1OfTriangle& triangles = tri->Triangles();
|
||||
for (int i = 1; i <= triangles.Length(); ++i) {
|
||||
int n1, n2, n3;
|
||||
|
||||
if (face.Orientation() == TopAbs_REVERSED) {
|
||||
triangles(i).Get(n3, n2, n1);
|
||||
} else {
|
||||
triangles(i).Get(n1, n2, n3);
|
||||
}
|
||||
|
||||
const gp_XYZ& pt1 = coords[n1 - 1];
|
||||
const gp_XYZ& pt2 = coords[n2 - 1];
|
||||
const gp_XYZ& pt3 = coords[n3 - 1];
|
||||
const gp_Vec v1 = pt2 - pt1;
|
||||
const gp_Vec v2 = pt3 - pt2;
|
||||
const gp_Vec v3 = pt1 - pt3;
|
||||
const gp_Vec normal_vector = v1 ^ v2;
|
||||
if (normal_vector.Magnitude() > ALMOST_ZERO) {
|
||||
gp_Dir normal = gp_Dir();
|
||||
|
||||
double edge_lengths[3] = { v1.Magnitude(), v2.Magnitude(), v3.Magnitude() };
|
||||
std::sort(&edge_lengths[0], &edge_lengths[2]);
|
||||
|
||||
const double& a = edge_lengths[0];
|
||||
const double& b = edge_lengths[1];
|
||||
const double& c = edge_lengths[2];
|
||||
|
||||
const double area = 0.25 * sqrt((a + (b + c))*(c - (a - b))*(c + (a - b))*(a + (b - c)));
|
||||
accumulate(ax, normal, area, along_x, along_y, along_z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IfcGeom::Representation::BRep::calculate_surface_area(double& area) const {
|
||||
try {
|
||||
area = 0.;
|
||||
|
||||
for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = begin(); it != end(); ++it) {
|
||||
GProp_GProps prop;
|
||||
BRepGProp::SurfaceProperties(it->Shape(), prop);
|
||||
area += prop.Mass();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (...) {
|
||||
Logger::Error("Error during calculation of surface area");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IfcGeom::Representation::BRep::calculate_volume(double& volume) const {
|
||||
try {
|
||||
volume = 0.;
|
||||
|
||||
for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = begin(); it != end(); ++it) {
|
||||
if (util::is_manifold(it->Shape())) {
|
||||
GProp_GProps prop;
|
||||
BRepGProp::VolumeProperties(it->Shape(), prop);
|
||||
volume += prop.Mass();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (...) {
|
||||
Logger::Error("Error during calculation of volume");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IfcGeom::Representation::BRep::calculate_projected_surface_area(const gp_Ax3 & ax, double & along_x, double & along_y, double & along_z) const {
|
||||
try {
|
||||
along_x = along_y = along_z = 0.;
|
||||
|
||||
for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = begin(); it != end(); ++it) {
|
||||
double x, y, z;
|
||||
surface_area_along_direction(settings().deflection_tolerance(), it->Shape(), ax, x, y, z);
|
||||
|
||||
if (util::is_manifold(it->Shape())) {
|
||||
x /= 2.;
|
||||
y /= 2.;
|
||||
z /= 2.;
|
||||
}
|
||||
|
||||
along_x += x;
|
||||
along_y += y;
|
||||
along_z += z;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (...) {
|
||||
Logger::Error("Error during calculation of projected surface area");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model)
|
||||
: Representation(shape_model.settings())
|
||||
, id_(shape_model.id())
|
||||
, weld_offset_(0)
|
||||
{
|
||||
for (IfcGeom::IfcRepresentationShapeItems::const_iterator iit = shape_model.begin(); iit != shape_model.end(); ++iit) {
|
||||
|
||||
// Don't weld vertices that belong to different items to prevent non-manifold situations.
|
||||
weld_offset_ += welds.size();
|
||||
welds.clear();
|
||||
|
||||
int surface_style_id = -1;
|
||||
if (iit->hasStyle()) {
|
||||
Material adapter(iit->StylePtr());
|
||||
std::vector<Material>::const_iterator jt = std::find(_materials.begin(), _materials.end(), adapter);
|
||||
if (jt == _materials.end()) {
|
||||
surface_style_id = (int)_materials.size();
|
||||
_materials.push_back(adapter);
|
||||
} else {
|
||||
surface_style_id = (int)(jt - _materials.begin());
|
||||
}
|
||||
}
|
||||
|
||||
if (settings().get(IteratorSettings::APPLY_DEFAULT_MATERIALS) && surface_style_id == -1) {
|
||||
Material material(IfcGeom::get_default_style(settings().element_type()));
|
||||
std::vector<Material>::const_iterator mit = std::find(_materials.begin(), _materials.end(), material);
|
||||
if (mit == _materials.end()) {
|
||||
surface_style_id = (int)_materials.size();
|
||||
_materials.push_back(material);
|
||||
} else {
|
||||
surface_style_id = (int)(mit - _materials.begin());
|
||||
}
|
||||
}
|
||||
|
||||
const TopoDS_Shape& s = iit->Shape();
|
||||
const gp_GTrsf& trsf = iit->Placement();
|
||||
|
||||
// Triangulate the shape
|
||||
try {
|
||||
BRepMesh_IncrementalMesh(s, settings().deflection_tolerance(), false, settings().angular_tolerance());
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterates over the faces of the shape
|
||||
int num_faces = 0;
|
||||
TopExp_Explorer exp;
|
||||
for (exp.Init(s, TopAbs_FACE); exp.More(); exp.Next(), ++num_faces) {
|
||||
TopoDS_Face face = TopoDS::Face(exp.Current());
|
||||
TopLoc_Location loc;
|
||||
Handle_Poly_Triangulation tri = BRep_Tool::Triangulation(face, loc);
|
||||
|
||||
if (tri.IsNull()) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Triangulation missing for face");
|
||||
} else {
|
||||
// A 3x3 matrix to rotate the vertex normals
|
||||
const gp_Mat rotation_matrix = trsf.VectorialPart();
|
||||
|
||||
// Keep track of the number of times an edge is used
|
||||
// Manifold edges (i.e. edges used twice) are deemed invisible
|
||||
std::map<std::pair<int, int>, int> edgecount;
|
||||
std::vector<std::pair<int, int> > edges_temp;
|
||||
|
||||
std::vector<gp_XYZ> coords;
|
||||
BRepGProp_Face prop(face);
|
||||
std::map<int, int> dict;
|
||||
|
||||
// Vertex normals are only calculated if vertices are not welded and calculation is not disable explicitly.
|
||||
const bool calculate_normals = !settings().get(IteratorSettings::WELD_VERTICES) &&
|
||||
!settings().get(IteratorSettings::NO_NORMALS);
|
||||
|
||||
for (int i = 1; i <= tri->NbNodes(); ++i) {
|
||||
coords.push_back(tri->Node(i).Transformed(loc).XYZ());
|
||||
trsf.Transforms(*coords.rbegin());
|
||||
dict[i] = addVertex(surface_style_id, *coords.rbegin());
|
||||
|
||||
if (calculate_normals) {
|
||||
const gp_Pnt2d& uv = tri->UVNode(i);
|
||||
gp_Pnt p;
|
||||
gp_Vec normal_direction;
|
||||
prop.Normal(uv.X(), uv.Y(), p, normal_direction);
|
||||
gp_Vec normal(0., 0., 0.);
|
||||
if (normal_direction.Magnitude() > 1.e-9) {
|
||||
normal = gp_Dir(normal_direction.XYZ() * rotation_matrix);
|
||||
} else {
|
||||
Handle_Geom_Surface surf = BRep_Tool::Surface(face);
|
||||
// Special case the normal at the poles of a spherical surface
|
||||
if (surf->DynamicType() == STANDARD_TYPE(Geom_SphericalSurface)) {
|
||||
if (fabs(fabs(uv.Y()) - M_PI / 2.) < 1.e-9) {
|
||||
const bool is_top = uv.Y() > 0;
|
||||
const bool is_forward = face.Orientation() == TopAbs_FORWARD;
|
||||
const double z = (is_top == is_forward) ? 1. : -1.;
|
||||
normal = gp_Dir(gp_XYZ(0, 0, z) * rotation_matrix);
|
||||
}
|
||||
}
|
||||
// TODO: Do the same for conical surfaces, but they are rare in IFC.
|
||||
}
|
||||
_normals.push_back(normal.X());
|
||||
_normals.push_back(normal.Y());
|
||||
_normals.push_back(normal.Z());
|
||||
}
|
||||
}
|
||||
|
||||
const Poly_Array1OfTriangle& triangles = tri->Triangles();
|
||||
for (int i = 1; i <= triangles.Length(); ++i) {
|
||||
int n1, n2, n3;
|
||||
if (face.Orientation() == TopAbs_REVERSED)
|
||||
triangles(i).Get(n3, n2, n1);
|
||||
else triangles(i).Get(n1, n2, n3);
|
||||
|
||||
/* An alternative would be to calculate normals based
|
||||
* on the coordinates of the mesh vertices */
|
||||
/*
|
||||
const gp_XYZ pt1 = coords[n1-1];
|
||||
const gp_XYZ pt2 = coords[n2-1];
|
||||
const gp_XYZ pt3 = coords[n3-1];
|
||||
const gp_XYZ v1 = pt2-pt1;
|
||||
const gp_XYZ v2 = pt3-pt2;
|
||||
gp_Dir normal = gp_Dir(v1^v2);
|
||||
_normals.push_back((float)normal.X());
|
||||
_normals.push_back((float)normal.Y());
|
||||
_normals.push_back((float)normal.Z());
|
||||
*/
|
||||
|
||||
_faces.push_back(dict[n1]);
|
||||
_faces.push_back(dict[n2]);
|
||||
_faces.push_back(dict[n3]);
|
||||
|
||||
_material_ids.push_back(surface_style_id);
|
||||
|
||||
addEdge(dict[n1], dict[n2], edgecount, edges_temp);
|
||||
addEdge(dict[n2], dict[n3], edgecount, edges_temp);
|
||||
addEdge(dict[n3], dict[n1], edgecount, edges_temp);
|
||||
}
|
||||
for (std::vector<std::pair<int, int> >::const_iterator jt = edges_temp.begin(); jt != edges_temp.end(); ++jt) {
|
||||
if (edgecount[*jt] == 1) {
|
||||
// non manifold edge, face boundary
|
||||
_edges.push_back(jt->first);
|
||||
_edges.push_back(jt->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_normals.empty() && settings().get(IfcGeom::IteratorSettings::GENERATE_UVS)) {
|
||||
uvs_ = box_project_uvs(_verts, _normals);
|
||||
}
|
||||
|
||||
if (num_faces == 0) {
|
||||
// Edges are only emitted if there are no faces. A mixed representation of faces
|
||||
// and loose edges is discouraged by the standard. An alternative would be to use
|
||||
// TopExp_Explorer texp(s, TopAbs_EDGE, TopAbs_FACE) to find edges that do not
|
||||
// belong to any face.
|
||||
for (TopExp_Explorer texp(s, TopAbs_EDGE); texp.More(); texp.Next()) {
|
||||
BRepAdaptor_Curve crv(TopoDS::Edge(texp.Current()));
|
||||
GCPnts_QuasiUniformDeflection tessellater(crv, settings().deflection_tolerance());
|
||||
int n = tessellater.NbPoints();
|
||||
int previous = -1;
|
||||
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
gp_XYZ p = tessellater.Value(i).XYZ();
|
||||
|
||||
int current = addVertex(surface_style_id, p);
|
||||
|
||||
std::vector<std::pair<int, int>> segments;
|
||||
if (i > 1) {
|
||||
segments.push_back(std::make_pair(previous, current));
|
||||
}
|
||||
|
||||
if (settings().get(IfcGeom::IteratorSettings::EDGE_ARROWS)) {
|
||||
// In case you want direction arrows on your edges
|
||||
double u = tessellater.Parameter(i);
|
||||
gp_XYZ p2, p3;
|
||||
gp_Pnt tmp;
|
||||
gp_Vec tmp2;
|
||||
crv.D1(u, tmp, tmp2);
|
||||
gp_Dir d1, d2, d3, d4;
|
||||
d1 = tmp2;
|
||||
if (texp.Current().Orientation() == TopAbs_REVERSED) {
|
||||
d1 = -d1;
|
||||
}
|
||||
if (fabs(d1.Z()) < 0.5) {
|
||||
d2 = d1.Crossed(gp::DZ());
|
||||
} else {
|
||||
d2 = d1.Crossed(gp::DY());
|
||||
}
|
||||
d3 = d1.XYZ() + d2.XYZ();
|
||||
d4 = d1.XYZ() - d2.XYZ();
|
||||
p2 = p - d3.XYZ() / 10.;
|
||||
p3 = p - d4.XYZ() / 10.;
|
||||
trsf.Transforms(p2);
|
||||
trsf.Transforms(p3);
|
||||
trsf.Transforms(p);
|
||||
|
||||
int left = addVertex(surface_style_id, p2);
|
||||
int right = addVertex(surface_style_id, p3);
|
||||
|
||||
segments.push_back(std::make_pair(left, current));
|
||||
segments.push_back(std::make_pair(right, current));
|
||||
}
|
||||
|
||||
for (auto& sgmt : segments) {
|
||||
_edges.push_back(sgmt.first);
|
||||
_edges.push_back(sgmt.second);
|
||||
_material_ids.push_back(surface_style_id);
|
||||
}
|
||||
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BRepTools::Clean(s);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates UVs for a single mesh using box projection.
|
||||
/// @todo Very simple impl. Assumes that input vertices and normals match 1:1.
|
||||
|
||||
std::vector<double> IfcGeom::Representation::Triangulation::box_project_uvs(const std::vector<double>& vertices, const std::vector<double>& normals)
|
||||
{
|
||||
std::vector<double> uvs;
|
||||
uvs.resize(vertices.size() / 3 * 2);
|
||||
for (size_t uv_idx = 0, v_idx = 0;
|
||||
uv_idx < uvs.size() && v_idx < vertices.size() && v_idx < normals.size();
|
||||
uv_idx += 2, v_idx += 3) {
|
||||
|
||||
double n_x = normals[v_idx], n_y = normals[v_idx + 1], n_z = normals[v_idx + 2];
|
||||
double v_x = vertices[v_idx], v_y = vertices[v_idx + 1], v_z = vertices[v_idx + 2];
|
||||
|
||||
if (std::abs(n_x) > std::abs(n_y) && std::abs(n_x) > std::abs(n_z)) {
|
||||
uvs[uv_idx] = v_z;
|
||||
uvs[uv_idx + 1] = v_y;
|
||||
}
|
||||
if (std::abs(n_y) > std::abs(n_x) && std::abs(n_y) > std::abs(n_z)) {
|
||||
uvs[uv_idx] = v_x;
|
||||
uvs[uv_idx + 1] = v_z;
|
||||
}
|
||||
if (std::abs(n_z) > std::abs(n_x) && std::abs(n_z) > std::abs(n_y)) {
|
||||
uvs[uv_idx] = v_x;
|
||||
uvs[uv_idx + 1] = v_y;
|
||||
}
|
||||
}
|
||||
|
||||
return uvs;
|
||||
}
|
||||
|
||||
int IfcGeom::Representation::Triangulation::addVertex(int material_index, const gp_XYZ & p) {
|
||||
const bool convert = settings().get(IteratorSettings::CONVERT_BACK_UNITS);
|
||||
const double X = convert ? (p.X() / settings().unit_magnitude()) : p.X();
|
||||
const double Y = convert ? (p.Y() / settings().unit_magnitude()) : p.Y();
|
||||
const double Z = convert ? (p.Z() / settings().unit_magnitude()) : p.Z();
|
||||
int i = (int)_verts.size() / 3;
|
||||
if (settings().get(IteratorSettings::WELD_VERTICES)) {
|
||||
const VertexKey key = std::make_pair(material_index, std::make_pair(X, std::make_pair(Y, Z)));
|
||||
typename VertexKeyMap::const_iterator it = welds.find(key);
|
||||
if (it != welds.end()) return it->second;
|
||||
i = (int)(welds.size() + weld_offset_);
|
||||
welds[key] = i;
|
||||
}
|
||||
_verts.push_back(X);
|
||||
_verts.push_back(Y);
|
||||
_verts.push_back(Z);
|
||||
return i;
|
||||
}
|
||||
|
||||
void IfcGeom::Representation::Triangulation::addEdge(int n1, int n2, std::map<std::pair<int, int>, int>& edgecount, std::vector<std::pair<int, int>>& edges_temp) {
|
||||
const Edge e = Edge((std::min)(n1, n2), (std::max)(n1, n2));
|
||||
if (edgecount.find(e) == edgecount.end()) edgecount[e] = 1;
|
||||
else edgecount[e] ++;
|
||||
edges_temp.push_back(e);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 IFCGEOMREPRESENTATION_H
|
||||
#define IFCGEOMREPRESENTATION_H
|
||||
|
||||
#include <BRepMesh_IncrementalMesh.hxx>
|
||||
#include <BRepGProp_Face.hxx>
|
||||
|
||||
#include <Poly_Triangulation.hxx>
|
||||
#include <TColgp_Array1OfPnt.hxx>
|
||||
#include <TColgp_Array1OfPnt2d.hxx>
|
||||
|
||||
#include <TopoDS.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
|
||||
#include <BRepAdaptor_Curve.hxx>
|
||||
#include <GCPnts_QuasiUniformDeflection.hxx>
|
||||
#include <Geom_SphericalSurface.hxx>
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomIteratorSettings.h"
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomMaterial.h"
|
||||
#include "../ifcgeom_schema_agnostic/IfcRepresentationShapeItem.h"
|
||||
|
||||
#include <TopoDS_Compound.hxx>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace IfcGeom {
|
||||
|
||||
namespace Representation {
|
||||
|
||||
class IFC_GEOM_API Representation {
|
||||
Representation(const Representation&); //N/A
|
||||
Representation& operator =(const Representation&); //N/A
|
||||
protected:
|
||||
const ElementSettings settings_;
|
||||
public:
|
||||
explicit Representation(const ElementSettings& settings)
|
||||
: settings_(settings)
|
||||
{}
|
||||
const ElementSettings& settings() const { return settings_; }
|
||||
virtual ~Representation() {}
|
||||
};
|
||||
|
||||
class IFC_GEOM_API BRep : public Representation {
|
||||
private:
|
||||
std::string id_;
|
||||
const IfcGeom::IfcRepresentationShapeItems shapes_;
|
||||
BRep(const BRep& other);
|
||||
BRep& operator=(const BRep& other);
|
||||
public:
|
||||
BRep(const ElementSettings& settings, const std::string& id, const IfcGeom::IfcRepresentationShapeItems& shapes)
|
||||
: Representation(settings)
|
||||
, id_(id)
|
||||
, shapes_(shapes)
|
||||
{}
|
||||
virtual ~BRep() {}
|
||||
IfcGeom::IfcRepresentationShapeItems::const_iterator begin() const { return shapes_.begin(); }
|
||||
IfcGeom::IfcRepresentationShapeItems::const_iterator end() const { return shapes_.end(); }
|
||||
const IfcGeom::IfcRepresentationShapeItems& shapes() const { return shapes_; }
|
||||
const std::string& id() const { return id_; }
|
||||
TopoDS_Compound as_compound(bool force_meters = false) const;
|
||||
|
||||
bool calculate_volume(double&) const;
|
||||
bool calculate_surface_area(double&) const;
|
||||
bool calculate_projected_surface_area(const gp_Ax3& ax, double& along_x, double& along_y, double& along_z) const;
|
||||
};
|
||||
|
||||
class IFC_GEOM_API Serialization : public Representation {
|
||||
private:
|
||||
std::string id_;
|
||||
std::string brep_data_;
|
||||
std::vector<double> surface_styles_;
|
||||
std::vector<int> surface_style_ids_;
|
||||
public:
|
||||
const std::string& brep_data() const { return brep_data_; }
|
||||
const std::vector<double>& surface_styles() const { return surface_styles_; }
|
||||
const std::vector<int>& surface_style_ids() const { return surface_style_ids_; }
|
||||
Serialization(const BRep& brep);
|
||||
virtual ~Serialization() {}
|
||||
const std::string& id() const { return id_; }
|
||||
private:
|
||||
Serialization();
|
||||
Serialization(const Serialization&);
|
||||
Serialization& operator=(const Serialization&);
|
||||
};
|
||||
|
||||
class Triangulation : public Representation {
|
||||
private:
|
||||
// A nested pair of floats and a material index to be able to store an XYZ coordinate in a map.
|
||||
// TODO: Make this a std::tuple when compilers add support for that.
|
||||
typedef typename std::pair<double, std::pair<double, double> > Coordinate;
|
||||
typedef typename std::pair<int, Coordinate> VertexKey;
|
||||
typedef std::map<VertexKey, int> VertexKeyMap;
|
||||
typedef std::pair<int, int> Edge;
|
||||
|
||||
std::string id_;
|
||||
std::vector<double> _verts;
|
||||
std::vector<int> _faces;
|
||||
std::vector<int> _edges;
|
||||
std::vector<double> _normals;
|
||||
std::vector<double> uvs_;
|
||||
std::vector<int> _material_ids;
|
||||
std::vector<Material> _materials;
|
||||
size_t weld_offset_;
|
||||
VertexKeyMap welds;
|
||||
|
||||
// when read from serialization, the element needs to take ownership of the styles,
|
||||
// the material vector is constructor off of this.
|
||||
// @todo this can be improved
|
||||
std::vector<std::shared_ptr<IfcGeom::SurfaceStyle>> styles_;
|
||||
|
||||
public:
|
||||
const std::string& id() const { return id_; }
|
||||
const std::vector<double>& verts() const { return _verts; }
|
||||
const std::vector<int>& faces() const { return _faces; }
|
||||
const std::vector<int>& edges() const { return _edges; }
|
||||
const std::vector<double>& normals() const { return _normals; }
|
||||
const std::vector<double>& uvs() const { return uvs_; }
|
||||
const std::vector<int>& material_ids() const { return _material_ids; }
|
||||
const std::vector<Material>& materials() const { return _materials; }
|
||||
|
||||
Triangulation(const BRep& shape_model);
|
||||
|
||||
Triangulation(
|
||||
ElementSettings settings,
|
||||
const std::string& id,
|
||||
const std::vector<double>& verts,
|
||||
const std::vector<int>& faces,
|
||||
const std::vector<int>& edges,
|
||||
const std::vector<double>& normals,
|
||||
const std::vector<double>& uvs,
|
||||
const std::vector<int>& material_ids,
|
||||
const std::vector<std::shared_ptr<IfcGeom::SurfaceStyle>>& styles)
|
||||
: Representation(settings)
|
||||
, id_(id)
|
||||
, _verts(verts)
|
||||
, _faces(faces)
|
||||
, _edges(edges)
|
||||
, _normals(normals)
|
||||
, uvs_(uvs)
|
||||
, _material_ids(material_ids)
|
||||
, styles_(styles)
|
||||
{
|
||||
for (auto& s : styles_) {
|
||||
_materials.push_back(IfcGeom::Material(s));
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~Triangulation() {}
|
||||
|
||||
/// Generates UVs for a single mesh using box projection.
|
||||
/// @todo Very simple impl. Assumes that input vertices and normals match 1:1.
|
||||
static std::vector<double> box_project_uvs(const std::vector<double> &vertices, const std::vector<double> &normals);
|
||||
|
||||
private:
|
||||
/// Welds vertices that belong to different faces
|
||||
int addVertex(int material_index, const gp_XYZ& p);
|
||||
void addEdge(int n1, int n2, std::map<std::pair<int, int>, int>& edgecount, std::vector<std::pair<int, int> >& edges_temp);
|
||||
|
||||
Triangulation();
|
||||
Triangulation(const Triangulation&);
|
||||
Triangulation& operator=(const Triangulation&);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "Serialization.h"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
#include <boost/preprocessor/seq/for_each.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#define EXTERNAL_DEFS_1(r, data, elem) \
|
||||
IfcUtil::IfcBaseClass* BOOST_PP_CAT(tesselate_Ifc, elem)(const TopoDS_Shape& shape, double deflection);
|
||||
|
||||
#define EXTERNAL_DEFS_2(r, data, elem) \
|
||||
IfcUtil::IfcBaseClass* BOOST_PP_CAT(serialise_Ifc, elem)(const TopoDS_Shape& shape, bool advanced);
|
||||
|
||||
#define CONDITIONAL_CALL(r, data, elem) \
|
||||
if (schema_name_lower == BOOST_PP_STRINGIZE(BOOST_PP_CAT(elem,))) { \
|
||||
return BOOST_PP_CAT(METHOD_NAME, elem)(shape, arg_2); \
|
||||
}
|
||||
|
||||
namespace IfcGeom {
|
||||
BOOST_PP_SEQ_FOR_EACH(EXTERNAL_DEFS_1, , SCHEMA_SEQ);
|
||||
BOOST_PP_SEQ_FOR_EACH(EXTERNAL_DEFS_2, , SCHEMA_SEQ);
|
||||
}
|
||||
|
||||
#define METHOD_NAME tesselate_Ifc
|
||||
|
||||
IfcUtil::IfcBaseClass* IfcGeom::tesselate(const std::string& schema_name, const TopoDS_Shape& shape, double arg_2) {
|
||||
// @todo an ugly hack to guarantee schemas are initialised.
|
||||
try {
|
||||
IfcParse::schema_by_name("IFC2X3");
|
||||
} catch (IfcParse::IfcException&) {}
|
||||
|
||||
const std::string schema_name_lower = boost::to_lower_copy(schema_name.substr(3));
|
||||
|
||||
BOOST_PP_SEQ_FOR_EACH(CONDITIONAL_CALL, , SCHEMA_SEQ);
|
||||
|
||||
throw IfcParse::IfcException("No geometry serialization available for " + schema_name);
|
||||
}
|
||||
|
||||
#undef METHOD_NAME
|
||||
#define METHOD_NAME serialise_Ifc
|
||||
|
||||
IfcUtil::IfcBaseClass* IfcGeom::serialise(const std::string& schema_name, const TopoDS_Shape& shape, bool arg_2) {
|
||||
// @todo an ugly hack to guarantee schemas are initialised.
|
||||
try {
|
||||
IfcParse::schema_by_name("IFC2X3");
|
||||
} catch (IfcParse::IfcException&) {}
|
||||
|
||||
const std::string schema_name_lower = boost::to_lower_copy(schema_name.substr(3));
|
||||
|
||||
BOOST_PP_SEQ_FOR_EACH(CONDITIONAL_CALL, , SCHEMA_SEQ);
|
||||
|
||||
throw IfcParse::IfcException("No geometry serialization available for " + schema_name);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "../ifcparse/IfcBaseClass.h"
|
||||
|
||||
#include "../ifcgeom_schema_agnostic/ifc_geom_api.h"
|
||||
|
||||
#include <TopoDS_Shape.hxx>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace IfcGeom {
|
||||
IFC_GEOM_API IfcUtil::IfcBaseClass* tesselate(const std::string& schema_name, const TopoDS_Shape& shape, double deflection);
|
||||
IFC_GEOM_API IfcUtil::IfcBaseClass* serialise(const std::string& schema_name, const TopoDS_Shape& shape, bool advanced);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 SERIALIZER_H
|
||||
#define SERIALIZER_H
|
||||
|
||||
#include "../ifcparse/IfcFile.h"
|
||||
|
||||
class Serializer {
|
||||
public:
|
||||
virtual ~Serializer() {}
|
||||
|
||||
virtual bool ready() = 0;
|
||||
virtual void writeHeader() = 0;
|
||||
virtual void finalize() = 0;
|
||||
virtual void setFile(IfcParse::IfcFile*) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "../ifcgeom_schema_agnostic/IfcGeomRenderStyles.h"
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
static std::map<std::string, std::shared_ptr<IfcGeom::SurfaceStyle>> default_materials;
|
||||
static std::shared_ptr<IfcGeom::SurfaceStyle> default_material;
|
||||
static bool default_materials_initialized = false;
|
||||
|
||||
void InitDefaultMaterials() {
|
||||
default_materials.insert(std::make_pair("IfcSite", std::make_shared<IfcGeom::SurfaceStyle>("IfcSite")));
|
||||
default_materials["IfcSite"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.75, 0.8, 0.65));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcSlab", std::make_shared<IfcGeom::SurfaceStyle>("IfcSlab")));
|
||||
default_materials["IfcSlab"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.4, 0.4, 0.4));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcWallStandardCase", std::make_shared<IfcGeom::SurfaceStyle>("IfcWallStandardCase")));
|
||||
default_materials["IfcWallStandardCase"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.9, 0.9, 0.9));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcWall", std::make_shared<IfcGeom::SurfaceStyle>("IfcWall")));
|
||||
default_materials["IfcWall"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.9, 0.9, 0.9));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcWindow", std::make_shared<IfcGeom::SurfaceStyle>("IfcWindow")));
|
||||
default_materials["IfcWindow"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.75, 0.8, 0.75));
|
||||
default_materials["IfcWindow"]->Transparency().reset(0.3);
|
||||
|
||||
default_materials.insert(std::make_pair("IfcDoor", std::make_shared<IfcGeom::SurfaceStyle>("IfcDoor")));
|
||||
default_materials["IfcDoor"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.55, 0.3, 0.15));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcBeam", std::make_shared<IfcGeom::SurfaceStyle>("IfcBeam")));
|
||||
default_materials["IfcBeam"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.75, 0.7, 0.7));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcRailing", std::make_shared<IfcGeom::SurfaceStyle>("IfcRailing")));
|
||||
default_materials["IfcRailing"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.65, 0.6, 0.6));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcMember", std::make_shared<IfcGeom::SurfaceStyle>("IfcMember")));
|
||||
default_materials["IfcMember"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.65, 0.6, 0.6));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcPlate", std::make_shared<IfcGeom::SurfaceStyle>("IfcPlate")));
|
||||
default_materials["IfcPlate"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.8, 0.8, 0.8));
|
||||
|
||||
default_materials.insert(std::make_pair("IfcSpace", std::make_shared<IfcGeom::SurfaceStyle>("IfcSpace")));
|
||||
default_materials["IfcWindow"]->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.65, 0.75, 0.8));
|
||||
default_materials["IfcWindow"]->Transparency().reset(0.8);
|
||||
|
||||
default_material = std::make_shared<IfcGeom::SurfaceStyle>("DefaultMaterial");
|
||||
default_material->Diffuse().reset(IfcGeom::SurfaceStyle::ColorComponent(0.7, 0.7, 0.7));
|
||||
|
||||
default_materials_initialized = true;
|
||||
}
|
||||
|
||||
boost::optional<IfcGeom::SurfaceStyle::ColorComponent> read_colour_component(const boost::optional<pt::ptree&> list) {
|
||||
if (!list) {
|
||||
return boost::none;
|
||||
}
|
||||
double rgb[3];
|
||||
int i = 0;
|
||||
for (pt::ptree::value_type &colour : list.get()) {
|
||||
if (3 <= i) {
|
||||
throw std::runtime_error("rgb array over 3 elements large");
|
||||
}
|
||||
rgb[i] = colour.second.get_value<double>();
|
||||
i++;
|
||||
}
|
||||
if (i != 3) {
|
||||
throw std::runtime_error("rgb array less than 3 elements large (was " + std::to_string(i) + ")");
|
||||
}
|
||||
return IfcGeom::SurfaceStyle::ColorComponent(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
void IfcGeom::set_default_style_file(const std::string& json_file) {
|
||||
if (!default_materials_initialized) InitDefaultMaterials();
|
||||
default_materials.clear();
|
||||
|
||||
// @todo this will probably need to be updated for UTF-8 paths on Windows
|
||||
pt::ptree root;
|
||||
pt::read_json(json_file, root);
|
||||
|
||||
for (pt::ptree::value_type &material_pair : root) {
|
||||
std::string name = material_pair.first;
|
||||
default_materials.insert(std::make_pair(name, std::make_shared<IfcGeom::SurfaceStyle>(name)));
|
||||
|
||||
pt::ptree material = material_pair.second;
|
||||
boost::optional<pt::ptree&> diffuse = material.get_child_optional("diffuse");
|
||||
default_materials[name]->Diffuse() = read_colour_component(diffuse);
|
||||
|
||||
boost::optional<pt::ptree&> specular = material.get_child_optional("specular");
|
||||
default_materials[name]->Specular() = read_colour_component(specular);
|
||||
|
||||
if (material.get_child_optional("specular-roughness")) {
|
||||
default_materials[name]->Specularity().reset(1.0 / material.get<double>("specular-roughness"));
|
||||
}
|
||||
if (material.get_child_optional("transparency")) {
|
||||
default_materials[name]->Transparency() = material.get<double>("transparency");
|
||||
}
|
||||
}
|
||||
|
||||
// Is "*" present? If yes, remove it and make it the default style.
|
||||
auto it = default_materials.find("*");
|
||||
if (it != default_materials.end()) {
|
||||
default_material = it->second;
|
||||
default_materials.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const IfcGeom::SurfaceStyle> IfcGeom::get_default_style(const std::string& s) {
|
||||
static std::mutex m;
|
||||
std::lock_guard<std::mutex> lk(m);
|
||||
|
||||
if (!default_materials_initialized) InitDefaultMaterials();
|
||||
auto it = default_materials.find(s);
|
||||
if (it == default_materials.end()) {
|
||||
default_materials.insert(std::make_pair(s, default_material));
|
||||
it = default_materials.find(s);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
IfcGeom::SurfaceStyle& IfcGeom::update_default_style(const std::string& s) {
|
||||
if (!default_materials_initialized) InitDefaultMaterials();
|
||||
auto it = default_materials.find(s);
|
||||
if (it == default_materials.end()) {
|
||||
throw std::runtime_error("No style registered for " + s);
|
||||
}
|
||||
return *it->second;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// A purposely empty file so that the unrolled loop
|
||||
// can overflow into an existing empty include file.
|
||||
@@ -0,0 +1,37 @@
|
||||
/********************************************************************************
|
||||
* *
|
||||
* 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 IFC_GEOM_API_H
|
||||
#define IFC_GEOM_API_H
|
||||
|
||||
#ifdef IFC_SHARED_BUILD
|
||||
#ifdef _WIN32
|
||||
#ifdef IFC_GEOM_EXPORTS
|
||||
#define IFC_GEOM_API __declspec(dllexport)
|
||||
#else
|
||||
#define IFC_GEOM_API __declspec(dllimport)
|
||||
#endif
|
||||
#else // simply assume *nix + GCC-like compiler
|
||||
#define IFC_GEOM_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
#else
|
||||
#define IFC_GEOM_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user