mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
Merge branch 'v0.8.0' into feat/IfcFixedReferenceSweptAreaSolid
This commit is contained in:
@@ -22,6 +22,16 @@
|
||||
using namespace ifcopenshell::geometry;
|
||||
|
||||
bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::ptr item, IfcGeom::ConversionResults& results) {
|
||||
if (settings_.get<settings::CacheShapes>().get()) {
|
||||
auto it = cache_.find(item);
|
||||
if (it != cache_.end()) {
|
||||
results = it->second;
|
||||
Logger::Notice("Cache hit #" + std::to_string(item->instance->as<IfcUtil::IfcBaseEntity>()->id()) +
|
||||
" -> #" + std::to_string(it->first->instance->as<IfcUtil::IfcBaseEntity>()->id()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto with_exception_handling = [&](auto fn) {
|
||||
try {
|
||||
return fn();
|
||||
@@ -44,11 +54,18 @@ bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::pt
|
||||
}
|
||||
};
|
||||
|
||||
bool res;
|
||||
if (propagate_exceptions) {
|
||||
return without_exception_handling(process_with_upgrade);
|
||||
res = without_exception_handling(process_with_upgrade);
|
||||
} else {
|
||||
return with_exception_handling(process_with_upgrade);
|
||||
res = with_exception_handling(process_with_upgrade);
|
||||
}
|
||||
|
||||
if (settings_.get<settings::CacheShapes>().get() && res) {
|
||||
cache_.insert({ item, results });
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
const Settings& ifcopenshell::geometry::kernels::AbstractKernel::settings() const
|
||||
@@ -212,6 +229,7 @@ ifcopenshell::geometry::kernels::AbstractKernel* ifcopenshell::geometry::kernels
|
||||
|
||||
for (auto it = kernels.begin(); it != kernels.end(); ++it) {
|
||||
(**it).propagate_exceptions = it == kernels.begin();
|
||||
(**it).partial_success_is_success = it == kernels.end() - 1;
|
||||
}
|
||||
|
||||
if (!kernels.empty()) {
|
||||
@@ -225,7 +243,9 @@ ifcopenshell::geometry::kernels::AbstractKernel* ifcopenshell::geometry::kernels
|
||||
bool ifcopenshell::geometry::kernels::AbstractKernel::convert_impl(const taxonomy::collection::ptr collection, IfcGeom::ConversionResults& r) {
|
||||
auto s = r.size();
|
||||
for (auto& c : collection->children) {
|
||||
convert(c, r);
|
||||
if (!convert(c, r) && !partial_success_is_success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto i = s; i < r.size(); ++i) {
|
||||
if (collection->matrix) {
|
||||
|
||||
@@ -34,11 +34,14 @@ namespace ifcopenshell {
|
||||
namespace geometry { namespace kernels {
|
||||
|
||||
class IFC_GEOM_API AbstractKernel {
|
||||
private:
|
||||
std::unordered_map<taxonomy::item::ptr, IfcGeom::ConversionResults, ifcopenshell::geometry::taxonomy::hash_functor, ifcopenshell::geometry::taxonomy::equal_functor> cache_;
|
||||
protected:
|
||||
std::string geometry_library_;
|
||||
Settings settings_;
|
||||
public:
|
||||
bool propagate_exceptions = false;
|
||||
bool partial_success_is_success = true;
|
||||
|
||||
AbstractKernel(const std::string& geometry_library, const Settings& settings)
|
||||
: geometry_library_(geometry_library)
|
||||
@@ -108,7 +111,7 @@ namespace {
|
||||
/* A compile-time for loop over the taxonomy kinds */
|
||||
template <size_t N>
|
||||
struct dispatch_conversion {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel* kernel, ifcopenshell::geometry::taxonomy::kinds item_kind, const ifcopenshell::geometry::taxonomy::ptr item, IfcGeom::ConversionResults& results) {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel* kernel, ifcopenshell::geometry::taxonomy::kinds item_kind, const ifcopenshell::geometry::taxonomy::ptr& item, IfcGeom::ConversionResults& results) {
|
||||
if (N == item_kind) {
|
||||
auto concrete_item = std::static_pointer_cast<ifcopenshell::geometry::taxonomy::type_by_kind::type<N>>(item);
|
||||
return kernel->convert_impl(concrete_item, results);
|
||||
@@ -120,7 +123,7 @@ namespace {
|
||||
|
||||
template <>
|
||||
struct dispatch_conversion<ifcopenshell::geometry::taxonomy::type_by_kind::max> {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel*, ifcopenshell::geometry::taxonomy::kinds, const ifcopenshell::geometry::taxonomy::ptr item, IfcGeom::ConversionResults&) {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel*, ifcopenshell::geometry::taxonomy::kinds, const ifcopenshell::geometry::taxonomy::ptr& item, IfcGeom::ConversionResults&) {
|
||||
Logger::Error("No conversion for " + std::to_string(item->kind()));
|
||||
return false;
|
||||
}
|
||||
@@ -128,7 +131,7 @@ namespace {
|
||||
|
||||
template <size_t N>
|
||||
struct dispatch_with_upgrade {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel* kernel, const ifcopenshell::geometry::taxonomy::ptr item, IfcGeom::ConversionResults& results) {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel* kernel, const ifcopenshell::geometry::taxonomy::ptr& item, IfcGeom::ConversionResults& results) {
|
||||
auto concrete_item = ifcopenshell::geometry::taxonomy::template dcast<ifcopenshell::geometry::taxonomy::upgrades::type<N>>(item);
|
||||
if (concrete_item) {
|
||||
return kernel->convert_impl(concrete_item, results);
|
||||
@@ -140,7 +143,7 @@ namespace {
|
||||
|
||||
template <>
|
||||
struct dispatch_with_upgrade<ifcopenshell::geometry::taxonomy::upgrades::max> {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel*, const ifcopenshell::geometry::taxonomy::ptr item, IfcGeom::ConversionResults&) {
|
||||
static bool dispatch(ifcopenshell::geometry::kernels::AbstractKernel*, const ifcopenshell::geometry::taxonomy::ptr& item, IfcGeom::ConversionResults&) {
|
||||
Logger::Error("No conversion with upgrade for " + std::to_string(item->kind()));
|
||||
return false;
|
||||
}
|
||||
@@ -162,7 +165,7 @@ namespace {
|
||||
/* A compile-time for loop over the curve kinds */
|
||||
template <typename T, size_t N = 0>
|
||||
struct dispatch_curve_creation {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr item, T& visitor) {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr& item, T& visitor) {
|
||||
constexpr auto KindIndex = TupleTypeIndex<std::tuple_element_t<N, ifcopenshell::geometry::taxonomy::impl::CurvesTuple>, ifcopenshell::geometry::taxonomy::impl::KindsTuple>::value;
|
||||
if (item->kind() == KindIndex) {
|
||||
auto concrete_item = std::static_pointer_cast<ifcopenshell::geometry::taxonomy::curves::type<N>>(item);
|
||||
@@ -176,7 +179,7 @@ namespace {
|
||||
|
||||
template <typename T>
|
||||
struct dispatch_curve_creation<T, ifcopenshell::geometry::taxonomy::curves::max> {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr item, T&) {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr& item, T&) {
|
||||
Logger::Error("No conversion for " + std::to_string(item->kind()));
|
||||
return false;
|
||||
}
|
||||
@@ -185,7 +188,7 @@ namespace {
|
||||
/* A compile-time for loop over the curve kinds */
|
||||
template <typename T, size_t N = 0>
|
||||
struct dispatch_surface_creation {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr item, T& visitor) {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr& item, T& visitor) {
|
||||
auto v = ifcopenshell::geometry::taxonomy::template dcast<ifcopenshell::geometry::taxonomy::surfaces::type<N>>(item);
|
||||
if (v && item->kind() == v->kind()) {
|
||||
visitor(v);
|
||||
@@ -198,7 +201,7 @@ namespace {
|
||||
|
||||
template <typename T>
|
||||
struct dispatch_surface_creation<T, ifcopenshell::geometry::taxonomy::surfaces::max> {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr item, T&) {
|
||||
static bool dispatch(const ifcopenshell::geometry::taxonomy::ptr& item, T&) {
|
||||
Logger::Error("No conversion for " + std::to_string(item->kind()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace ifcopenshell {
|
||||
struct HasDefault<T, decltype((void)T::defaultvalue, 0)> : std::true_type { };
|
||||
#endif
|
||||
|
||||
template <typename Derived, typename T>
|
||||
template <typename Derived, typename T, bool Internal=false>
|
||||
struct SettingBase {
|
||||
typedef T base_type;
|
||||
|
||||
@@ -61,7 +61,9 @@ namespace ifcopenshell {
|
||||
return x;
|
||||
}
|
||||
};
|
||||
if constexpr (std::is_same_v<T, bool>) {
|
||||
if constexpr (Internal) {
|
||||
// do nothing, this is an internal setting and not supposed to be set from the command line
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
// @todo bool_switch doesn't work with optional unfortunately...
|
||||
value.emplace();
|
||||
desc.add_options()(Derived::name, apply_default(po::bool_switch(&*value)), Derived::description);
|
||||
@@ -120,19 +122,19 @@ namespace ifcopenshell {
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct LengthUnit : public SettingBase<LengthUnit, double> {
|
||||
struct LengthUnit : public SettingBase<LengthUnit, double, true> {
|
||||
static constexpr const char* const name = "length-unit";
|
||||
static constexpr const char* const description = "";
|
||||
static constexpr double defaultvalue = 1.0;
|
||||
};
|
||||
|
||||
struct PlaneUnit : public SettingBase<PlaneUnit, double> {
|
||||
struct PlaneUnit : public SettingBase<PlaneUnit, double, true> {
|
||||
static constexpr const char* const name = "angle-unit";
|
||||
static constexpr const char* const description = "";
|
||||
static constexpr double defaultvalue = 1.0;
|
||||
};
|
||||
|
||||
struct Precision : public SettingBase<Precision, double> {
|
||||
struct Precision : public SettingBase<Precision, double, true> {
|
||||
static constexpr const char* const name = "precision";
|
||||
static constexpr const char* const description = "";
|
||||
static constexpr double defaultvalue = 0.00001;
|
||||
@@ -228,17 +230,17 @@ namespace ifcopenshell {
|
||||
|
||||
struct ContextIds : public SettingBase<ContextIds, std::set<int>> {
|
||||
static constexpr const char* const name = "context-ids";
|
||||
static constexpr const char* const description = "";
|
||||
static constexpr const char* const description = "List of comma separated context ids to process - e.g. '15,29' (no quotes needed).";
|
||||
};
|
||||
|
||||
struct ContextTypes : public SettingBase<ContextIds, std::set<std::string>> {
|
||||
struct ContextTypes : public SettingBase<ContextTypes, std::set<std::string>> {
|
||||
static constexpr const char* const name = "context-types";
|
||||
static constexpr const char* const description = "";
|
||||
static constexpr const char* const description = "Currently option has no effect.";
|
||||
};
|
||||
|
||||
struct ContextIdentifiers : public SettingBase<ContextIds, std::set<std::string>> {
|
||||
struct ContextIdentifiers : public SettingBase<ContextIdentifiers, std::set<std::string>> {
|
||||
static constexpr const char* const name = "context-identifiers";
|
||||
static constexpr const char* const description = "";
|
||||
static constexpr const char* const description = "Currently option has no effect.";
|
||||
};
|
||||
|
||||
enum OutputDimensionalityTypes {
|
||||
@@ -251,7 +253,10 @@ namespace ifcopenshell {
|
||||
|
||||
struct OutputDimensionality : public SettingBase<OutputDimensionality, OutputDimensionalityTypes> {
|
||||
static constexpr const char* const name = "dimensionality";
|
||||
static constexpr const char* const description = "Specifies whether to include curves and/or surfaces and solids in the output result. Defaults to only surfaces and solids.";
|
||||
static constexpr const char* const description =
|
||||
"Specifies whether to include curves and/or surfaces and solids in the output result. "
|
||||
"Defaults to only surfaces and solids (SURFACES_AND_SOLIDS). "
|
||||
"Other possible values are CURVES, CURVES_SURFACES_AND_SOLIDS.";
|
||||
static constexpr OutputDimensionalityTypes defaultvalue = SURFACES_AND_SOLIDS;
|
||||
};
|
||||
|
||||
@@ -342,6 +347,12 @@ namespace ifcopenshell {
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct PermissiveShapeReuse : public SettingBase<PermissiveShapeReuse, bool> {
|
||||
static constexpr const char* const name = "permissive-shape-reuse";
|
||||
static constexpr const char* const description = "Traverse geometry-level transformations and apply to product-level placement in order to increase reuse of geometries";
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct ForceSpaceTransparency : public SettingBase<ForceSpaceTransparency, double> {
|
||||
static constexpr const char* const name = "force-space-transparency";
|
||||
static constexpr const char* const description = "Overrides transparency of spaces in geometry output.";
|
||||
@@ -353,6 +364,12 @@ namespace ifcopenshell {
|
||||
static constexpr int defaultvalue = 16;
|
||||
};
|
||||
|
||||
struct CgalSmoothAngleDegrees : public SettingBase<CgalSmoothAngleDegrees, double> {
|
||||
static constexpr const char* const name = "cgal-smooth-angle-degrees";
|
||||
static constexpr const char* const description = "Angle in degrees under which adjacent facets will have averaged vertex normals in CGAL output. NB irrespective of original IFC geometry types. Defaults to -1 to disable smoothing.";
|
||||
static constexpr double defaultvalue = -1.;
|
||||
};
|
||||
|
||||
struct KeepBoundingBoxes : public SettingBase<KeepBoundingBoxes, bool> {
|
||||
static constexpr const char* const name = "keep-bounding-boxes";
|
||||
static constexpr const char* const description =
|
||||
@@ -421,6 +438,39 @@ namespace ifcopenshell {
|
||||
static constexpr const char* const description = "Try to emit original edge face boundary edges instead of recomputed ones based on face normal. Falls back to triangulated data in case of boolean operands and faces with holes.";
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct OcctNoCleanTriangulation : public SettingBase<OcctNoCleanTriangulation, bool, true> {
|
||||
static constexpr const char* const name = "no-clean-triangulation";
|
||||
static constexpr const char* const description = "Don't clean triangulations, might cause memory leaks";
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct CacheShapes : public SettingBase<CacheShapes, bool> {
|
||||
static constexpr const char* const name = "cache-shapes";
|
||||
static constexpr const char* const description = "Experimental as not all topology hash functions fully implemented";
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct DeferProcessingFirstElement : public SettingBase<DeferProcessingFirstElement, bool, true> {
|
||||
static constexpr const char* const name = "defer-processing-first-element";
|
||||
static constexpr const char* const description = "Don't process first element in Iterator::initialize call()";
|
||||
static constexpr bool defaultvalue = false;
|
||||
};
|
||||
|
||||
struct MaxOffset : public SettingBase<MaxOffset, double> {
|
||||
static constexpr const char* const name = "max-offset";
|
||||
static constexpr const char* const description = "Maximum translation offset to be observed after which median offset in model gets removed and logged. Requires --no-parallel-mapping.";
|
||||
};
|
||||
|
||||
struct MaxOffsetDeviation : public SettingBase<MaxOffsetDeviation, double> {
|
||||
static constexpr const char* const name = "max-offset-deviation";
|
||||
static constexpr const char* const description = "To retain field of view, completely remove elements outside of the median offset. Requires --no-parallel-mapping.";
|
||||
};
|
||||
|
||||
struct ApplyOffset : public SettingBase<ApplyOffset, std::vector<double>> {
|
||||
static constexpr const char* const name = "apply-offset";
|
||||
static constexpr const char* const description = "Slight variation of --model-offset where large offsets are applied by negating existing large offsets to retain maximum precision. Requires --no-parallel-mapping.";
|
||||
};
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
@@ -596,7 +646,7 @@ namespace ifcopenshell {
|
||||
};
|
||||
|
||||
class IFC_GEOM_API Settings : public SettingsContainer<
|
||||
std::tuple<MesherLinearDeflection, MesherAngularDeflection, ReorientShells, LengthUnit, PlaneUnit, Precision, OutputDimensionality, LayersetFirst, DisableBooleanResult, NoWireIntersectionCheck, NoWireIntersectionTolerance, PrecisionFactor, DebugBooleanOperations, BooleanAttempt2d, SurfaceColour, WeldVertices, UseWorldCoords, UnifyShapes, UseMaterialNames, ConvertBackUnits, ContextIds, ContextTypes, ContextIdentifiers, IteratorOutput, DisableOpeningSubtractions, ApplyDefaultMaterials, DontEmitNormals, GenerateUvs, ApplyLayerSets, UseElementHierarchy, ValidateQuantities, EdgeArrows, BuildingLocalPlacement, SiteLocalPlacement, ForceSpaceTransparency, CircleSegments, KeepBoundingBoxes, ComputeCurvature, FunctionStepType, FunctionStepParam, NoParallelMapping, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges>
|
||||
std::tuple<MesherLinearDeflection, MesherAngularDeflection, ReorientShells, LengthUnit, PlaneUnit, Precision, OutputDimensionality, LayersetFirst, DisableBooleanResult, NoWireIntersectionCheck, NoWireIntersectionTolerance, PrecisionFactor, DebugBooleanOperations, BooleanAttempt2d, SurfaceColour, WeldVertices, UseWorldCoords, UnifyShapes, UseMaterialNames, ConvertBackUnits, ContextIds, ContextTypes, ContextIdentifiers, IteratorOutput, DisableOpeningSubtractions, ApplyDefaultMaterials, DontEmitNormals, GenerateUvs, ApplyLayerSets, UseElementHierarchy, ValidateQuantities, EdgeArrows, BuildingLocalPlacement, SiteLocalPlacement, ForceSpaceTransparency, CircleSegments, CgalSmoothAngleDegrees, KeepBoundingBoxes, ComputeCurvature, FunctionStepType, FunctionStepParam, NoParallelMapping, PermissiveShapeReuse, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges, OcctNoCleanTriangulation, CacheShapes, DeferProcessingFirstElement, MaxOffset, MaxOffsetDeviation, ApplyOffset>
|
||||
>
|
||||
{};
|
||||
}
|
||||
|
||||
@@ -170,12 +170,15 @@ IfcGeom::Representation::Serialization::Serialization(const BRep& brep)
|
||||
}
|
||||
|
||||
if (brep.begin() != brep.end()) {
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
if (std::dynamic_pointer_cast<ifcopenshell::geometry::OpenCascadeShape>(brep.begin()->Shape())) {
|
||||
ConversionResultShape* shape = brep.as_compound();
|
||||
ifcopenshell::geometry::taxonomy::matrix4 identity;
|
||||
shape->Serialize(identity, brep_data_);
|
||||
delete shape;
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
for (auto it = brep.begin(); it != brep.end(); ++it) {
|
||||
std::string part;
|
||||
it->Shape()->Serialize(*it->Placement(), part);
|
||||
@@ -187,7 +190,21 @@ IfcGeom::Representation::Serialization::Serialization(const BRep& brep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool is_non_uniform(const Eigen::Matrix4d& M, double eps = 1e-6)
|
||||
{
|
||||
Eigen::Matrix3d L = M.block<3, 3>(0, 0);
|
||||
double sx = L.col(0).norm();
|
||||
double sy = L.col(1).norm();
|
||||
double sz = L.col(2).norm();
|
||||
return !(
|
||||
std::abs(sx - sy) < eps &&
|
||||
std::abs(sx - sz) < eps &&
|
||||
std::abs(sy - sz) < eps
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IfcGeom::ConversionResultShape* IfcGeom::Representation::BRep::as_compound(bool force_meters) const {
|
||||
@@ -199,17 +216,35 @@ IfcGeom::ConversionResultShape* IfcGeom::Representation::BRep::as_compound(bool
|
||||
for (auto it = begin(); it != end(); ++it) {
|
||||
const TopoDS_Shape& s = *std::static_pointer_cast<ifcopenshell::geometry::OpenCascadeShape>(it->Shape());
|
||||
|
||||
// @todo, check
|
||||
gp_GTrsf trsf;
|
||||
if (it->Placement()->components_) {
|
||||
gp_Trsf tr;
|
||||
const auto& m = it->Placement()->ccomponents();
|
||||
tr.SetValues(
|
||||
m(0, 0), m(0, 1), m(0, 2), m(0, 3),
|
||||
m(1, 0), m(1, 1), m(1, 2), m(1, 3),
|
||||
m(2, 0), m(2, 1), m(2, 2), m(2, 3)
|
||||
);
|
||||
trsf = tr;
|
||||
// This is either a bug or very finicky, but this appears to be the only
|
||||
// way to get the transformation metadata to line up.
|
||||
//
|
||||
// - If gp_GTrsf.form is other, applying the transformation will result in
|
||||
// a conversion to b-spline surfaces for about everything, which impacts
|
||||
// performance and breaks detection of view volume in the svg serializer,
|
||||
// which would be the case when setting the SetVectorialPart() block
|
||||
// unconditionally.
|
||||
// (calling SetForm() afterwards to detect CompoundTrsf over Other would
|
||||
// set Scale to zero (bug?))
|
||||
if (is_non_uniform(m)) {
|
||||
trsf.SetVectorialPart(gp_Mat(
|
||||
m(0, 0), m(0, 1), m(0, 2),
|
||||
m(1, 0), m(1, 1), m(1, 2),
|
||||
m(2, 0), m(2, 1), m(2, 2)
|
||||
));
|
||||
trsf.SetTranslationPart(gp_XYZ(m(0, 3), m(1, 3), m(2, 3)));
|
||||
} else {
|
||||
gp_Trsf tr;
|
||||
tr.SetValues(
|
||||
m(0, 0), m(0, 1), m(0, 2), m(0, 3),
|
||||
m(1, 0), m(1, 1), m(1, 2), m(1, 3),
|
||||
m(2, 0), m(2, 1), m(2, 2), m(2, 3)
|
||||
);
|
||||
trsf = tr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!force_meters && settings().get<ifcopenshell::geometry::settings::ConvertBackUnits>().get()) {
|
||||
@@ -222,7 +257,7 @@ IfcGeom::ConversionResultShape* IfcGeom::Representation::BRep::as_compound(bool
|
||||
builder.Add(compound, moved_shape);
|
||||
}
|
||||
|
||||
return new ifcopenshell::geometry::OpenCascadeShape(compound);
|
||||
return new ifcopenshell::geometry::OpenCascadeShape(std::move(compound));
|
||||
#else
|
||||
throw std::runtime_error("Not available without Open Cascade");
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,852 @@
|
||||
#include "Iterator.h"
|
||||
|
||||
/**
|
||||
* Initialize iterator's list of tasks.
|
||||
*
|
||||
* Will automatically process first element, if 'defer-processing-first-element' is not set to `true`.
|
||||
*
|
||||
* @return Returns true if the iterator is initialized with any elements, false otherwise.
|
||||
*
|
||||
* @note
|
||||
* - A true return value does not guarantee successful initialization of all elements.
|
||||
* Some elements may have failed to initialize. Check had_error_processing_elements()
|
||||
* to see whether there were errors during the initialization.
|
||||
*
|
||||
* - For non-concurrent iterators, a false return may occur if initialization of the first
|
||||
* element fails, even if subsequent elements could be initialized successfully.
|
||||
*/
|
||||
bool IfcGeom::Iterator::initialize() {
|
||||
using std::chrono::high_resolution_clock;
|
||||
|
||||
if (initialization_outcome_) {
|
||||
return *initialization_outcome_;
|
||||
}
|
||||
|
||||
time_points[0] = high_resolution_clock::now();
|
||||
std::vector<ifcopenshell::geometry::geometry_conversion_task> reps;
|
||||
if (num_threads_ != 1) {
|
||||
// @todo this shouldn't be necessary with properly immutable taxonomy items
|
||||
converter_->mapping()->use_caching() = false;
|
||||
}
|
||||
try {
|
||||
converter_->mapping()->get_representations(reps, filters_);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
}
|
||||
time_points[1] = high_resolution_clock::now();
|
||||
|
||||
for (auto& task : reps) {
|
||||
geometry_conversion_result res;
|
||||
res.index = task.index;
|
||||
if (!settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get()) {
|
||||
res.representation = task.representation;
|
||||
res.products_2 = task.products;
|
||||
} else {
|
||||
res.item = converter_->mapping()->map(task.representation);
|
||||
if (!res.item) {
|
||||
continue;
|
||||
}
|
||||
std::transform(task.products->begin(), task.products->end(), std::back_inserter(res.products), [this, &res](IfcUtil::IfcBaseClass* prod) {
|
||||
auto prod_item = converter_->mapping()->map(prod);
|
||||
return std::make_pair(prod->as<IfcUtil::IfcBaseEntity>(), ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::geom_item>(prod_item)->matrix);
|
||||
});
|
||||
}
|
||||
tasks_.push_back(res);
|
||||
}
|
||||
|
||||
if (settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get() && settings_.get<ifcopenshell::geometry::settings::PermissiveShapeReuse>().get()) {
|
||||
std::unordered_map<
|
||||
ifcopenshell::geometry::taxonomy::item::ptr,
|
||||
std::vector<std::pair<IfcUtil::IfcBaseEntity*, ifcopenshell::geometry::taxonomy::matrix4::ptr>>> folded;
|
||||
|
||||
for (auto& r : tasks_) {
|
||||
auto i = r.item;
|
||||
|
||||
Eigen::Matrix4d m4 = Eigen::Matrix4d::Identity();
|
||||
|
||||
while (auto col = std::dynamic_pointer_cast<ifcopenshell::geometry::taxonomy::collection>(i)) {
|
||||
if (col->children.size() == 1) {
|
||||
if (col->matrix) {
|
||||
m4 *= col->matrix->ccomponents();
|
||||
}
|
||||
i = col->children[0];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& p : r.products) {
|
||||
auto pl = ifcopenshell::geometry::taxonomy::matrix4::ptr(p.second->clone_());
|
||||
pl->components() *= m4;
|
||||
folded[i].push_back(
|
||||
{ p.first, pl }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (folded.size() < tasks_.size()) {
|
||||
auto old_size = tasks_.size();
|
||||
tasks_.clear();
|
||||
size_t i = 0;
|
||||
for (auto& p : folded) {
|
||||
tasks_.emplace_back();
|
||||
tasks_.back().index = i++;
|
||||
tasks_.back().item = p.first;
|
||||
tasks_.back().products = p.second;
|
||||
}
|
||||
Logger::Notice("Merged " + std::to_string(old_size) + " tasks into " + std::to_string(tasks_.size()) + " tasks due to permissive shape reuse");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get()) {
|
||||
remove_offset_();
|
||||
}
|
||||
|
||||
size_t num_products = 0;
|
||||
for (auto& r : tasks_) {
|
||||
num_products += !settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get() ? r.products_2->size() : r.products.size();
|
||||
}
|
||||
|
||||
time_points[2] = high_resolution_clock::now();
|
||||
|
||||
/*
|
||||
// What to do, map representation and product individually?
|
||||
// There needs to be two options, mapped item respecting (does that still work?), and optimized based on topology sorting.
|
||||
// Or is the sorting not necessary if we just cache?
|
||||
|
||||
std::vector<taxonomy::ptr> items;
|
||||
std::map<taxonomy::ptr, taxonomy::matrix4> placements;
|
||||
std::transform(products.begin(), products.end(), std::back_inserter(items), [this, &placements](IfcUtil::IfcBaseClass* p) {
|
||||
auto item = converter_->mapping()->map(p);
|
||||
// Product placements do not affect item reuse and should temporarily be swapped to identity
|
||||
if (item) {
|
||||
std::swap(placements[item], ((taxonomy::geom_ptr)item)->matrix);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
items.erase(std::remove(items.begin(), items.end(), nullptr), items.end());
|
||||
std::sort(items.begin(), items.end(), taxonomy::less);
|
||||
auto it = items.begin();
|
||||
while (it < items.end()) {
|
||||
auto jt = std::upper_bound(it, items.end(), *it, taxonomy::less);
|
||||
geometry_conversion_result r;
|
||||
r.item = *it;
|
||||
std::transform(it, jt, std::back_inserter(r.products), [&r, &placements](taxonomy::ptr product_node) {
|
||||
return std::make_pair((IfcUtil::IfcBaseEntity*) product_node->instance, placements[product_node]);
|
||||
});
|
||||
tasks_.push_back(r);
|
||||
it = jt;
|
||||
}
|
||||
*/
|
||||
|
||||
Logger::Notice("Created " + boost::lexical_cast<std::string>(tasks_.size()) + " tasks for " + boost::lexical_cast<std::string>(num_products) + " products");
|
||||
|
||||
if (tasks_.size() == 0) {
|
||||
Logger::Warning("No representations encountered, aborting");
|
||||
initialization_outcome_.reset(false);
|
||||
} else if (!settings_.get<ifcopenshell::geometry::settings::DeferProcessingFirstElement>().get()) {
|
||||
|
||||
task_iterator_ = tasks_.begin();
|
||||
|
||||
done = 0;
|
||||
total = (int)tasks_.size();
|
||||
|
||||
if (num_threads_ != 1) {
|
||||
init_future_ = std::async(std::launch::async, [this]() { process_concurrently(); });
|
||||
|
||||
// wait for the first element, because after init(), get() can be called.
|
||||
// so the element conversion must succeed
|
||||
initialization_outcome_ = wait_for_element();
|
||||
} else {
|
||||
initialization_outcome_ = create();
|
||||
}
|
||||
} else {
|
||||
initialization_outcome_.reset(true);
|
||||
}
|
||||
|
||||
return *initialization_outcome_;
|
||||
}
|
||||
|
||||
void IfcGeom::Iterator::process_finished_rep(geometry_conversion_result* rep) {
|
||||
if (rep->elements.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lk(element_ready_mutex_);
|
||||
|
||||
all_processed_elements_.insert(all_processed_elements_.end(), rep->elements.begin(), rep->elements.end());
|
||||
all_processed_native_elements_.insert(all_processed_native_elements_.end(), rep->breps.begin(), rep->breps.end());
|
||||
|
||||
if (!task_result_ptr_initialized) {
|
||||
task_result_iterator_ = all_processed_elements_.begin();
|
||||
native_task_result_iterator_ = all_processed_native_elements_.begin();
|
||||
task_result_ptr_initialized = true;
|
||||
}
|
||||
|
||||
progress_ = (int)(++processed_ * 100 / tasks_.size());
|
||||
}
|
||||
|
||||
void IfcGeom::Iterator::process_concurrently() {
|
||||
size_t conc_threads = num_threads_;
|
||||
if (conc_threads > tasks_.size()) {
|
||||
conc_threads = tasks_.size();
|
||||
}
|
||||
|
||||
kernel_pool.reserve(conc_threads);
|
||||
for (unsigned i = 0; i < conc_threads; ++i) {
|
||||
kernel_pool.push_back(new ifcopenshell::geometry::Converter(geometry_library_, ifc_file, settings_));
|
||||
}
|
||||
|
||||
std::vector<std::future<geometry_conversion_result*>> threadpool;
|
||||
|
||||
for (auto& rep : tasks_) {
|
||||
ifcopenshell::geometry::Converter* K = nullptr;
|
||||
if (threadpool.size() < kernel_pool.size()) {
|
||||
K = kernel_pool[threadpool.size()];
|
||||
}
|
||||
|
||||
while (threadpool.size() == conc_threads) {
|
||||
for (int i = 0; i < (int)threadpool.size(); i++) {
|
||||
auto& fu = threadpool[i];
|
||||
std::future_status status;
|
||||
status = fu.wait_for(std::chrono::seconds(0));
|
||||
if (status == std::future_status::ready) {
|
||||
process_finished_rep(fu.get());
|
||||
|
||||
std::swap(threadpool[i], threadpool.back());
|
||||
threadpool.pop_back();
|
||||
std::swap(kernel_pool[i], kernel_pool.back());
|
||||
K = kernel_pool.back();
|
||||
break;
|
||||
} // if
|
||||
} // for
|
||||
} // while
|
||||
|
||||
std::future<geometry_conversion_result*> fu = std::async(
|
||||
std::launch::async, [this](
|
||||
ifcopenshell::geometry::Converter* kernel,
|
||||
ifcopenshell::geometry::Settings settings,
|
||||
geometry_conversion_result* rep) {
|
||||
// Catch exceptions to be safe from freezing the iterator.
|
||||
try {
|
||||
this->create_element_(kernel, settings, rep);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(
|
||||
std::string("Exception '") + e.what() +
|
||||
std::string("' occurred while iterator was creating a shape: "),
|
||||
rep->item->instance
|
||||
);
|
||||
had_error_processing_elements_ = true;
|
||||
} catch (...) {
|
||||
Logger::Error(
|
||||
"Unknown exception occurred while iteartor was creating a shape: ",
|
||||
rep->item->instance
|
||||
);
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
return rep;
|
||||
},
|
||||
K,
|
||||
std::ref(settings_),
|
||||
&rep);
|
||||
|
||||
if (terminating_) {
|
||||
break;
|
||||
}
|
||||
|
||||
threadpool.emplace_back(std::move(fu));
|
||||
}
|
||||
|
||||
for (auto& fu : threadpool) {
|
||||
process_finished_rep(fu.get());
|
||||
}
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Logger::SetProduct(boost::none);
|
||||
|
||||
if (!terminating_) {
|
||||
Logger::Status("\rDone creating geometry (" + boost::lexical_cast<std::string>(all_processed_elements_.size()) +
|
||||
" objects) ");
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes model's bounding box (bounds_min and bounds_max).
|
||||
/// @note Can take several minutes for large files.
|
||||
void IfcGeom::Iterator::compute_bounds(bool with_geometry)
|
||||
{
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
bounds_min_.components()(i) = std::numeric_limits<double>::infinity();
|
||||
bounds_max_.components()(i) = -std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
if (with_geometry) {
|
||||
size_t num_created = 0;
|
||||
do {
|
||||
IfcGeom::Element* geom_object = get();
|
||||
const IfcGeom::TriangulationElement* o = static_cast<const IfcGeom::TriangulationElement*>(geom_object);
|
||||
const IfcGeom::Representation::Triangulation& mesh = o->geometry();
|
||||
auto mat = o->transformation().data()->ccomponents();
|
||||
Eigen::Vector4d vec, transformed;
|
||||
|
||||
for (typename std::vector<double>::const_iterator it = mesh.verts().begin(); it != mesh.verts().end();) {
|
||||
const double& x = *(it++);
|
||||
const double& y = *(it++);
|
||||
const double& z = *(it++);
|
||||
vec << x, y, z, 1.;
|
||||
transformed = mat * vec;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
bounds_min_.components()(i) = std::min(bounds_min_.components()(i), transformed(i));
|
||||
bounds_max_.components()(i) = std::max(bounds_max_.components()(i), transformed(i));
|
||||
}
|
||||
}
|
||||
} while (++num_created, next());
|
||||
} else {
|
||||
std::vector<ifcopenshell::geometry::geometry_conversion_task> reps;
|
||||
converter_->mapping()->get_representations(reps, filters_);
|
||||
|
||||
std::vector<IfcUtil::IfcBaseClass*> products;
|
||||
for (auto& r : reps) {
|
||||
std::copy(r.products->begin(), r.products->end(), std::back_inserter(products));
|
||||
}
|
||||
|
||||
for (auto& product : products) {
|
||||
auto prod_item = converter_->mapping()->map(product);
|
||||
auto vec = ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::geom_item>(prod_item)->matrix->translation_part();
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
bounds_min_.components()(i) = std::min(bounds_min_.components()(i), vec(i));
|
||||
bounds_max_.components()(i) = std::max(bounds_max_.components()(i), vec(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create_shape_model_for_next_entity() {
|
||||
geometry_conversion_result* task = nullptr;
|
||||
for (; task_iterator_ < tasks_.end();) {
|
||||
task = &*task_iterator_++;
|
||||
create_element_(converter_, settings_, task);
|
||||
if (task->elements.empty()) {
|
||||
task = nullptr;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (task) {
|
||||
process_finished_rep(task);
|
||||
return task->item->instance->as<IfcUtil::IfcBaseClass>();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void IfcGeom::Iterator::create_element_(ifcopenshell::geometry::Converter* kernel, ifcopenshell::geometry::Settings settings, geometry_conversion_result* rep)
|
||||
{
|
||||
if (!settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get()) {
|
||||
rep->item = kernel->mapping()->map(rep->representation);
|
||||
if (!rep->item) {
|
||||
return;
|
||||
}
|
||||
std::transform(rep->products_2->begin(), rep->products_2->end(), std::back_inserter(rep->products), [this, &rep, kernel](IfcUtil::IfcBaseClass* prod) {
|
||||
auto prod_item = kernel->mapping()->map(prod);
|
||||
return std::make_pair(prod->as<IfcUtil::IfcBaseEntity>(), ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::geom_item>(prod_item)->matrix);
|
||||
});
|
||||
} else {
|
||||
}
|
||||
|
||||
auto product_node = rep->products.front();
|
||||
const IfcUtil::IfcBaseEntity* product = product_node.first;
|
||||
const auto& place = product_node.second;
|
||||
|
||||
Logger::SetProduct(product);
|
||||
|
||||
IfcGeom::BRepElement* brep = static_cast<IfcGeom::BRepElement*>(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product->get("GlobalId"), std::to_string(rep->item->instance->as<IfcUtil::IfcBaseEntity>()->id()), [kernel, settings, product, place, rep]() {
|
||||
return kernel->create_brep_for_representation_and_product(rep->item, product, place);
|
||||
}));
|
||||
|
||||
if (!brep) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto elem = process_based_on_settings(settings, brep);
|
||||
if (!elem) {
|
||||
return;
|
||||
}
|
||||
|
||||
rep->breps = { brep };
|
||||
rep->elements = { elem };
|
||||
|
||||
for (auto it = rep->products.begin() + 1; it != rep->products.end(); ++it) {
|
||||
const auto& p = *it;
|
||||
const IfcUtil::IfcBaseEntity* product2 = p.first;
|
||||
const auto& place2 = p.second;
|
||||
|
||||
IfcGeom::BRepElement* brep2 = static_cast<IfcGeom::BRepElement*>(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product2->get("GlobalId"), std::to_string(rep->item->instance->as<IfcUtil::IfcBaseEntity>()->id()), [kernel, settings, product2, place2, brep]() {
|
||||
return kernel->create_brep_for_processed_representation(product2, place2, brep);
|
||||
}));
|
||||
if (brep2) {
|
||||
auto elem2 = process_based_on_settings(settings, brep2, dynamic_cast<IfcGeom::TriangulationElement*>(elem));
|
||||
if (elem2) {
|
||||
rep->breps.push_back(brep2);
|
||||
rep->elements.push_back(elem2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IfcGeom::Element* IfcGeom::Iterator::process_based_on_settings(ifcopenshell::geometry::Settings settings, IfcGeom::BRepElement* elem, IfcGeom::TriangulationElement* previous)
|
||||
{
|
||||
if (settings.get<ifcopenshell::geometry::settings::IteratorOutput>().get() == ifcopenshell::geometry::settings::SERIALIZED) {
|
||||
try {
|
||||
return new IfcGeom::SerializedElement(*elem);
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Getting a serialized element from model failed.");
|
||||
return nullptr;
|
||||
}
|
||||
} else if (settings.get<ifcopenshell::geometry::settings::IteratorOutput>().get() == ifcopenshell::geometry::settings::TRIANGULATED) {
|
||||
// the part before the hyphen is the representation id
|
||||
auto gid2 = elem->geometry().id();
|
||||
auto hyphen = gid2.find("-");
|
||||
if (hyphen != std::string::npos) {
|
||||
gid2 = gid2.substr(0, hyphen);
|
||||
}
|
||||
|
||||
return decorate_with_cache_(GeometrySerializer::READ_TRIANGULATION, elem->guid(), gid2, [elem, previous]() {
|
||||
try {
|
||||
if (!previous) {
|
||||
return new TriangulationElement(*elem);
|
||||
} else {
|
||||
return new TriangulationElement(*elem, previous->geometry_pointer());
|
||||
}
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Getting a triangulation element from model failed.");
|
||||
}
|
||||
return (TriangulationElement*)nullptr;
|
||||
});
|
||||
} else {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
|
||||
bool IfcGeom::Iterator::wait_for_element() {
|
||||
while (true) {
|
||||
size_t s;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(element_ready_mutex_);
|
||||
s = all_processed_elements_.size();
|
||||
}
|
||||
if (s > async_elements_returned_) {
|
||||
++async_elements_returned_;
|
||||
return true;
|
||||
} else if (finished_) {
|
||||
return false;
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IfcGeom::Iterator::log_timepoints() const {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration;
|
||||
using namespace std::string_literals;
|
||||
|
||||
std::array<std::string, 3> labels = {
|
||||
"Initializing mapping"s,
|
||||
"Performing mapping"s,
|
||||
"Geometry interpretation"s
|
||||
};
|
||||
|
||||
for (auto it = time_points.begin() + 1; it != time_points.end(); ++it) {
|
||||
auto jt = it - 1;
|
||||
duration<double, std::milli> ms_double = (*it) - (*jt);
|
||||
Logger::Notice(labels[std::distance(time_points.begin(), jt)] + " took " + std::to_string(ms_double.count()) + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
void IfcGeom::Iterator::validate_iterator_state() const {
|
||||
if (!initialization_outcome_) {
|
||||
throw std::runtime_error("Iterator not initialized");
|
||||
}
|
||||
|
||||
// Causes:
|
||||
// - iterator was initialized but there were no elements to process
|
||||
// - iterator was initialized but 'defer-processing-first-element' setting is enabled
|
||||
// and some element should be processed manually first
|
||||
if (!task_result_ptr_initialized) {
|
||||
throw std::runtime_error("No elements processed");
|
||||
}
|
||||
|
||||
if (task_result_ptr_exhausted) {
|
||||
throw std::runtime_error("Iterator is exhausted");
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves to the next shape representation, create its geometry, and returns the associated product.
|
||||
/// Use get() to retrieve the created geometry.
|
||||
const IfcUtil::IfcBaseClass* IfcGeom::Iterator::next() {
|
||||
using std::chrono::high_resolution_clock;
|
||||
validate_iterator_state();
|
||||
|
||||
if (*native_task_result_iterator_ != *task_result_iterator_) {
|
||||
delete* native_task_result_iterator_;
|
||||
}
|
||||
delete* task_result_iterator_;
|
||||
|
||||
if (num_threads_ != 1) {
|
||||
if (!wait_for_element()) {
|
||||
Logger::SetProduct(boost::none);
|
||||
time_points[3] = high_resolution_clock::now();
|
||||
log_timepoints();
|
||||
task_result_ptr_exhausted = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
task_result_iterator_++;
|
||||
native_task_result_iterator_++;
|
||||
|
||||
return (*task_result_iterator_)->product();
|
||||
} else {
|
||||
// Increment the iterator over the list of products using the current
|
||||
// shape representation
|
||||
if (task_result_iterator_ == --all_processed_elements_.end()) {
|
||||
if (!create()) {
|
||||
Logger::SetProduct(boost::none);
|
||||
time_points[3] = high_resolution_clock::now();
|
||||
log_timepoints();
|
||||
task_result_ptr_exhausted = true;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
task_result_iterator_++;
|
||||
native_task_result_iterator_++;
|
||||
|
||||
return (*task_result_iterator_)->product();
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the representation of the current geometrical entity.
|
||||
IfcGeom::Element* IfcGeom::Iterator::get()
|
||||
{
|
||||
validate_iterator_state();
|
||||
|
||||
auto ret = *task_result_iterator_;
|
||||
|
||||
// If we want to organize the element considering their hierarchy
|
||||
if (settings_.get<ifcopenshell::geometry::settings::UseElementHierarchy>().get()) {
|
||||
// We are going to build a vector with the element parents.
|
||||
// First, create the parent vector
|
||||
std::vector<const IfcGeom::Element*> parents;
|
||||
|
||||
// if the element has a parent
|
||||
if (ret->parent_id() != -1) {
|
||||
const IfcGeom::Element* parent_object = NULL;
|
||||
bool hasParent = true;
|
||||
|
||||
// get the parent
|
||||
try {
|
||||
parent_object = get_object(ret->parent_id());
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
hasParent = false;
|
||||
}
|
||||
|
||||
// Add the previously found parent to the vector
|
||||
if (hasParent) parents.insert(parents.begin(), parent_object);
|
||||
|
||||
// We need to find all the parents
|
||||
while (parent_object != NULL && hasParent && parent_object->parent_id() != -1) {
|
||||
// Find the next parent
|
||||
try {
|
||||
parent_object = get_object(parent_object->parent_id());
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
hasParent = false;
|
||||
}
|
||||
|
||||
// Add the previously found parent to the vector
|
||||
if (hasParent) parents.insert(parents.begin(), parent_object);
|
||||
|
||||
hasParent = hasParent && parent_object->parent_id() != -1;
|
||||
}
|
||||
|
||||
// when done push the parent list in the Element object
|
||||
ret->SetParents(parents);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const IfcGeom::Element* IfcGeom::Iterator::get_object(int id) {
|
||||
ifcopenshell::geometry::taxonomy::matrix4::ptr m4;
|
||||
int parent_id = -1;
|
||||
std::string instance_type, product_name, product_guid;
|
||||
IfcUtil::IfcBaseEntity* ifc_product = 0;
|
||||
|
||||
try {
|
||||
ifc_product = ifc_file->instance_by_id(id)->as<IfcUtil::IfcBaseEntity>();
|
||||
instance_type = ifc_product->declaration().name();
|
||||
|
||||
if (ifc_product->declaration().is("IfcRoot")) {
|
||||
product_guid = (std::string)ifc_product->get("GlobalId");
|
||||
product_name = ifc_product->get_value<std::string>("Name", "");
|
||||
}
|
||||
|
||||
auto parent_object = converter_->mapping()->get_decomposing_entity(ifc_product);
|
||||
if (parent_object) {
|
||||
parent_id = parent_object->id();
|
||||
}
|
||||
|
||||
// fails in case of IfcProject
|
||||
auto mapped = converter_->mapping()->map(ifc_product);
|
||||
auto casted = mapped ? ifcopenshell::geometry::taxonomy::dcast<ifcopenshell::geometry::taxonomy::geom_item>(mapped) : nullptr;
|
||||
|
||||
if (casted) {
|
||||
m4 = casted->matrix;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
}
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
catch (const Standard_Failure& e) {
|
||||
if (e.GetMessageString() && strlen(e.GetMessageString())) {
|
||||
Logger::Error(e.GetMessageString());
|
||||
} else {
|
||||
Logger::Error("Unknown error returning product");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
catch (...) {
|
||||
Logger::Error("Unknown error returning product");
|
||||
}
|
||||
|
||||
Element* ifc_object = new Element(settings_, id, parent_id, product_name, instance_type, product_guid, "", m4, ifc_product);
|
||||
return ifc_object;
|
||||
}
|
||||
|
||||
const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create() {
|
||||
const IfcUtil::IfcBaseClass* product = nullptr;
|
||||
try {
|
||||
product = create_shape_model_for_next_entity();
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
catch (const Standard_Failure& e) {
|
||||
if (e.GetMessageString() && strlen(e.GetMessageString())) {
|
||||
Logger::Error(e.GetMessageString());
|
||||
} else {
|
||||
Logger::Error("Unknown error creating geometry");
|
||||
}
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
#endif
|
||||
catch (...) {
|
||||
Logger::Error("Unknown error creating geometry");
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
ifcopenshell::geometry::taxonomy::direction3::ptr IfcGeom::Iterator::remove_offset_() {
|
||||
|
||||
using namespace ifcopenshell::geometry::taxonomy;
|
||||
using namespace ifcopenshell::geometry::settings;
|
||||
|
||||
if (!settings_.get<MaxOffset>().has()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!settings_.get<NoParallelMapping>().get()) {
|
||||
throw std::runtime_error("remove_offset() can only be called with defer-processing-first-element and no-parallel-mapping settings");
|
||||
}
|
||||
|
||||
auto collect_offset = [&](const item::ptr& itm, const std::vector<std::pair<IfcUtil::IfcBaseEntity*, matrix4::ptr>>& pr) -> std::pair<double, Eigen::Vector3d> {
|
||||
std::function<std::pair<double, Eigen::Vector3d>(const item::ptr&, Eigen::Matrix4d)> traverse;
|
||||
traverse = [&](const item::ptr& node, Eigen::Matrix4d m4) -> std::pair<double, Eigen::Vector3d> {
|
||||
if (auto shl = std::dynamic_pointer_cast<shell>(node)) {
|
||||
auto p = shl->centroid();
|
||||
Eigen::Vector4d v;
|
||||
v << p->components()(0), p->components()(1), p->components()(2), 1.0;
|
||||
Eigen::Vector3d translation_part = (m4 * v).head<3>();
|
||||
double translation_amnt = translation_part.norm();
|
||||
if (translation_amnt > settings_.get<MaxOffset>().get()) {
|
||||
return { translation_amnt, translation_part };
|
||||
} else {
|
||||
return { 0.0, Eigen::Vector3d::Zero() };
|
||||
}
|
||||
} else {
|
||||
if (auto gi = std::dynamic_pointer_cast<geom_item>(node)) {
|
||||
if (gi->matrix) {
|
||||
m4 = m4 * gi->matrix->ccomponents();
|
||||
}
|
||||
}
|
||||
Eigen::Vector3d translation_part = m4.block<3, 1>(0, 3);
|
||||
double translation_amnt = translation_part.norm();
|
||||
if (translation_amnt > settings_.get<MaxOffset>().get()) {
|
||||
return { translation_amnt, translation_part };
|
||||
} else if (auto col = std::dynamic_pointer_cast<collection>(node)) {
|
||||
std::vector<std::pair<double, Eigen::Vector3d>> child_transforms;
|
||||
for (const auto& child : col->children) {
|
||||
child_transforms.push_back(traverse(child, m4));
|
||||
}
|
||||
if (!child_transforms.empty()) {
|
||||
return *std::max_element(child_transforms.begin(), child_transforms.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
}
|
||||
}
|
||||
return { 0.0, Eigen::Vector3d::Zero() };
|
||||
}
|
||||
};
|
||||
|
||||
Eigen::Matrix4d m4 = Eigen::Matrix4d::Identity();
|
||||
if (pr.size() == 1 && pr[0].second) {
|
||||
m4 = pr[0].second->ccomponents();
|
||||
}
|
||||
return traverse(itm, m4);
|
||||
};
|
||||
|
||||
Eigen::Vector3d vec;
|
||||
|
||||
if (settings_.get<ApplyOffset>().has()) {
|
||||
auto vs = settings_.get<ApplyOffset>().get();
|
||||
if (vs.size() != 3) {
|
||||
throw std::runtime_error("ApplyOffset setting must be a vector of size 3");
|
||||
}
|
||||
vec = Eigen::Vector3d(vs[0], vs[1], vs[2]);
|
||||
} else {
|
||||
// Collect all norms and vectors
|
||||
std::vector<double> norms;
|
||||
std::vector<Eigen::Vector3d> vectors;
|
||||
for (const auto& task : tasks_) {
|
||||
auto result = collect_offset(task.item, task.products);
|
||||
norms.push_back(result.first);
|
||||
vectors.push_back(result.second);
|
||||
}
|
||||
|
||||
// Find the median norm index
|
||||
std::vector<double> sorted_norms = norms;
|
||||
std::nth_element(sorted_norms.begin(), sorted_norms.begin() + sorted_norms.size() / 2, sorted_norms.end());
|
||||
double median = sorted_norms[sorted_norms.size() / 2];
|
||||
auto median_it = std::find(norms.begin(), norms.end(), median);
|
||||
size_t median_index = std::distance(norms.begin(), median_it);
|
||||
|
||||
if (median_index >= vectors.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
vec = -vectors[median_index];
|
||||
}
|
||||
|
||||
Eigen::Matrix4d translation_matrix = Eigen::Matrix4d::Identity();
|
||||
translation_matrix.block<3, 1>(0, 3) = vec;
|
||||
|
||||
auto remove_offset = [&](const item::ptr& itm, const std::vector<std::pair<IfcUtil::IfcBaseEntity*, matrix4::ptr>>& pr) -> bool {
|
||||
std::function<bool(const item::ptr&, Eigen::Matrix4d)> traverse;
|
||||
traverse = [&](const item::ptr& node, Eigen::Matrix4d m4) -> bool {
|
||||
if (auto shl = std::dynamic_pointer_cast<shell>(node)) {
|
||||
auto p = shl->centroid();
|
||||
Eigen::Vector4d v;
|
||||
v << p->components()(0), p->components()(1), p->components()(2), 1.0;
|
||||
Eigen::Vector3d translation_part = (m4 * v).head<3>();
|
||||
double translation_amnt = translation_part.norm();
|
||||
if (translation_amnt > settings_.get<MaxOffset>().get()) {
|
||||
shl->matrix = make<matrix4>(translation_matrix);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
auto m4b = m4;
|
||||
if (auto gi = std::dynamic_pointer_cast<geom_item>(node)) {
|
||||
if (gi->matrix) {
|
||||
m4b = m4 * gi->matrix->ccomponents();
|
||||
}
|
||||
Eigen::Vector3d translation_part = m4b.block<3, 1>(0, 3);
|
||||
double translation_amnt = translation_part.norm();
|
||||
if (translation_amnt > settings_.get<MaxOffset>().get()) {
|
||||
auto inverted_rot_scale3 = m4.block<3, 3>(0, 0).inverse();
|
||||
Eigen::Matrix4d inverted_rot_scale = Eigen::Matrix4d::Identity();
|
||||
inverted_rot_scale.block<3, 3>(0, 0) = inverted_rot_scale3;
|
||||
if (!gi->matrix) {
|
||||
gi->matrix = make<matrix4>();
|
||||
}
|
||||
gi->matrix->components() = (inverted_rot_scale * translation_matrix) * gi->matrix->ccomponents();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool b = true;
|
||||
if (auto col = std::dynamic_pointer_cast<collection>(node)) {
|
||||
for (const auto& child : col->children) {
|
||||
if (!traverse(child, m4b)) {
|
||||
b = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
};
|
||||
|
||||
Eigen::Matrix4d m4 = Eigen::Matrix4d::Identity();
|
||||
if (pr.size() == 1 && pr[0].second) {
|
||||
m4 = pr[0].second->ccomponents();
|
||||
}
|
||||
return traverse(itm, m4);
|
||||
};
|
||||
|
||||
size_t num_offset_applied = 0;
|
||||
for (auto& task : tasks_) {
|
||||
bool all_applied = true;
|
||||
for (auto& p : task.products) {
|
||||
auto bb = p.second->components().block<3, 1>(0, 3);
|
||||
double translation_amnt = bb.norm();
|
||||
if (translation_amnt > settings_.get<MaxOffset>().get()) {
|
||||
// block has an underlying mutable ref to the matrix
|
||||
bb += vec;
|
||||
} else {
|
||||
all_applied = false;
|
||||
}
|
||||
}
|
||||
if (all_applied) {
|
||||
num_offset_applied += 1;
|
||||
continue;
|
||||
}
|
||||
if (remove_offset(task.item, task.products)) {
|
||||
num_offset_applied += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::Notice("Removed large offsets within " + std::to_string(num_offset_applied) + " products");
|
||||
Logger::Notice("Offset applied (" + std::to_string(vec(0)) + "," + std::to_string(vec(1)) + "," + std::to_string(vec(2)) + ")");
|
||||
|
||||
return make<direction3>(vec);
|
||||
}
|
||||
|
||||
IfcGeom::Iterator::~Iterator() {
|
||||
if (num_threads_ != 1) {
|
||||
terminating_ = true;
|
||||
|
||||
if (init_future_.valid()) {
|
||||
init_future_.wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& k : kernel_pool) {
|
||||
delete k;
|
||||
}
|
||||
|
||||
if (task_result_ptr_initialized) {
|
||||
while (task_result_iterator_ != --all_processed_elements_.end()) {
|
||||
if (*native_task_result_iterator_ != *task_result_iterator_) {
|
||||
delete* native_task_result_iterator_;
|
||||
}
|
||||
delete* task_result_iterator_++;
|
||||
native_task_result_iterator_++;
|
||||
}
|
||||
}
|
||||
|
||||
delete converter_;
|
||||
}
|
||||
+102
-662
@@ -84,13 +84,14 @@
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
|
||||
namespace {
|
||||
namespace IfcGeom {
|
||||
|
||||
struct geometry_conversion_result {
|
||||
int index;
|
||||
|
||||
// For NoParallelMapping==true
|
||||
ifcopenshell::geometry::taxonomy::ptr item;
|
||||
std::vector<std::pair<const IfcUtil::IfcBaseEntity*, ifcopenshell::geometry::taxonomy::matrix4::ptr>> products;
|
||||
std::vector<std::pair<IfcUtil::IfcBaseEntity*, ifcopenshell::geometry::taxonomy::matrix4::ptr>> products;
|
||||
|
||||
// For NoParallelMapping==false
|
||||
IfcUtil::IfcBaseEntity* representation;
|
||||
@@ -99,9 +100,7 @@ namespace {
|
||||
std::vector<IfcGeom::BRepElement*> breps;
|
||||
std::vector<IfcGeom::Element*> elements;
|
||||
};
|
||||
}
|
||||
|
||||
namespace IfcGeom {
|
||||
|
||||
class Iterator {
|
||||
private:
|
||||
@@ -118,14 +117,13 @@ namespace IfcGeom {
|
||||
std::list<IfcGeom::Element*> all_processed_elements_;
|
||||
std::list<IfcGeom::BRepElement*> all_processed_native_elements_;
|
||||
|
||||
typename std::list<IfcGeom::Element*>::const_iterator task_result_iterator_;
|
||||
typename std::list<IfcGeom::BRepElement*>::const_iterator native_task_result_iterator_;
|
||||
std::list<IfcGeom::Element*>::const_iterator task_result_iterator_;
|
||||
std::list<IfcGeom::BRepElement*>::const_iterator native_task_result_iterator_;
|
||||
|
||||
std::mutex element_ready_mutex_;
|
||||
bool task_result_ptr_initialized = false;
|
||||
// ?
|
||||
bool task_result_ptr_exhausted = false;
|
||||
size_t async_elements_returned_ = 0;
|
||||
size_t task_result_index_ = 0;
|
||||
|
||||
ifcopenshell::geometry::Settings settings_;
|
||||
IfcParse::IfcFile* ifc_file;
|
||||
@@ -155,313 +153,13 @@ namespace IfcGeom {
|
||||
|
||||
// Should not be destructed because, destructor is blocking
|
||||
std::future<void> init_future_;
|
||||
std::mutex caching_mutex_;
|
||||
|
||||
std::array<std::chrono::high_resolution_clock::time_point, 4> time_points;
|
||||
|
||||
/// @todo public/private sections all over the place: move all public to the beginning of the class
|
||||
public:
|
||||
void set_cache(GeometrySerializer* cache) { cache_ = cache; }
|
||||
|
||||
const std::string& unit_name() const { return converter_->mapping()->get_length_unit_name(); }
|
||||
double unit_magnitude() const { return converter_->mapping()->get_length_unit(); }
|
||||
// Check if error occurred during iterator initialization or iteration over elements.
|
||||
bool had_error_processing_elements() const { return had_error_processing_elements_; }
|
||||
|
||||
boost::optional<bool> initialization_outcome_;
|
||||
|
||||
/**
|
||||
* @return Returns true if the iterator is initialized with any elements, false otherwise.
|
||||
*
|
||||
* @note
|
||||
* - A true return value does not guarantee successful initialization of all elements.
|
||||
* Some elements may have failed to initialize. Check had_error_processing_elements()
|
||||
* to see whether there were errors during the initialization.
|
||||
*
|
||||
* - For non-concurrent iterators, a false return may occur if initialization of the first
|
||||
* element fails, even if subsequent elements could be initialized successfully.
|
||||
*/
|
||||
bool initialize() {
|
||||
using std::chrono::high_resolution_clock;
|
||||
|
||||
if (initialization_outcome_) {
|
||||
return *initialization_outcome_;
|
||||
}
|
||||
|
||||
time_points[0] = high_resolution_clock::now();
|
||||
std::vector<ifcopenshell::geometry::geometry_conversion_task> reps;
|
||||
if (num_threads_ != 1) {
|
||||
// @todo this shouldn't be necessary with properly immutable taxonomy items
|
||||
converter_->mapping()->use_caching() = false;
|
||||
}
|
||||
try {
|
||||
converter_->mapping()->get_representations(reps, filters_);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
}
|
||||
time_points[1] = high_resolution_clock::now();
|
||||
|
||||
for (auto& task : reps) {
|
||||
geometry_conversion_result res;
|
||||
res.index = task.index;
|
||||
if (!settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get()) {
|
||||
res.representation = task.representation;
|
||||
res.products_2 = task.products;
|
||||
} else {
|
||||
res.item = converter_->mapping()->map(task.representation);
|
||||
if (!res.item) {
|
||||
continue;
|
||||
}
|
||||
std::transform(task.products->begin(), task.products->end(), std::back_inserter(res.products), [this, &res](IfcUtil::IfcBaseClass* prod) {
|
||||
auto prod_item = converter_->mapping()->map(prod);
|
||||
return std::make_pair(prod->as<IfcUtil::IfcBaseEntity>(), ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::geom_item>(prod_item)->matrix);
|
||||
});
|
||||
}
|
||||
tasks_.push_back(res);
|
||||
}
|
||||
|
||||
size_t num_products = 0;
|
||||
for (auto& r : tasks_) {
|
||||
num_products += !settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get() ? r.products_2->size() : r.products.size();
|
||||
}
|
||||
|
||||
time_points[2] = high_resolution_clock::now();
|
||||
|
||||
/*
|
||||
// What to do, map representation and product individually?
|
||||
// There needs to be two options, mapped item respecting (does that still work?), and optimized based on topology sorting.
|
||||
// Or is the sorting not necessary if we just cache?
|
||||
|
||||
std::vector<taxonomy::ptr> items;
|
||||
std::map<taxonomy::ptr, taxonomy::matrix4> placements;
|
||||
std::transform(products.begin(), products.end(), std::back_inserter(items), [this, &placements](IfcUtil::IfcBaseClass* p) {
|
||||
auto item = converter_->mapping()->map(p);
|
||||
// Product placements do not affect item reuse and should temporarily be swapped to identity
|
||||
if (item) {
|
||||
std::swap(placements[item], ((taxonomy::geom_ptr)item)->matrix);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
items.erase(std::remove(items.begin(), items.end(), nullptr), items.end());
|
||||
std::sort(items.begin(), items.end(), taxonomy::less);
|
||||
auto it = items.begin();
|
||||
while (it < items.end()) {
|
||||
auto jt = std::upper_bound(it, items.end(), *it, taxonomy::less);
|
||||
geometry_conversion_result r;
|
||||
r.item = *it;
|
||||
std::transform(it, jt, std::back_inserter(r.products), [&r, &placements](taxonomy::ptr product_node) {
|
||||
return std::make_pair((IfcUtil::IfcBaseEntity*) product_node->instance, placements[product_node]);
|
||||
});
|
||||
tasks_.push_back(r);
|
||||
it = jt;
|
||||
}
|
||||
*/
|
||||
|
||||
Logger::Notice("Created " + boost::lexical_cast<std::string>(tasks_.size()) + " tasks for " + boost::lexical_cast<std::string>(num_products) + " products");
|
||||
|
||||
if (tasks_.size() == 0) {
|
||||
Logger::Warning("No representations encountered, aborting");
|
||||
initialization_outcome_.reset(false);
|
||||
} else {
|
||||
|
||||
task_iterator_ = tasks_.begin();
|
||||
|
||||
task_result_index_ = 0;
|
||||
done = 0;
|
||||
total = (int) tasks_.size();
|
||||
|
||||
if (num_threads_ != 1) {
|
||||
init_future_ = std::async(std::launch::async, [this]() { process_concurrently(); });
|
||||
|
||||
// wait for the first element, because after init(), get() can be called.
|
||||
// so the element conversion must succeed
|
||||
initialization_outcome_ = wait_for_element();
|
||||
} else {
|
||||
initialization_outcome_ = create();
|
||||
}
|
||||
}
|
||||
|
||||
return *initialization_outcome_;
|
||||
}
|
||||
|
||||
size_t processed_ = 0;
|
||||
|
||||
void process_finished_rep(geometry_conversion_result* rep) {
|
||||
if (rep->elements.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lk(element_ready_mutex_);
|
||||
|
||||
all_processed_elements_.insert(all_processed_elements_.end(), rep->elements.begin(), rep->elements.end());
|
||||
all_processed_native_elements_.insert(all_processed_native_elements_.end(), rep->breps.begin(), rep->breps.end());
|
||||
|
||||
if (!task_result_ptr_initialized) {
|
||||
task_result_iterator_ = all_processed_elements_.begin();
|
||||
native_task_result_iterator_ = all_processed_native_elements_.begin();
|
||||
task_result_ptr_initialized = true;
|
||||
}
|
||||
|
||||
progress_ = (int) (++processed_ * 100 / tasks_.size());
|
||||
}
|
||||
|
||||
void process_concurrently() {
|
||||
size_t conc_threads = num_threads_;
|
||||
if (conc_threads > tasks_.size()) {
|
||||
conc_threads = tasks_.size();
|
||||
}
|
||||
|
||||
kernel_pool.reserve(conc_threads);
|
||||
for (unsigned i = 0; i < conc_threads; ++i) {
|
||||
kernel_pool.push_back(new ifcopenshell::geometry::Converter(geometry_library_, ifc_file, settings_));
|
||||
}
|
||||
|
||||
std::vector<std::future<geometry_conversion_result*>> threadpool;
|
||||
|
||||
for (auto& rep : tasks_) {
|
||||
ifcopenshell::geometry::Converter* K = nullptr;
|
||||
if (threadpool.size() < kernel_pool.size()) {
|
||||
K = kernel_pool[threadpool.size()];
|
||||
}
|
||||
|
||||
while (threadpool.size() == conc_threads) {
|
||||
for (int i = 0; i < (int)threadpool.size(); i++) {
|
||||
auto& fu = threadpool[i];
|
||||
std::future_status status;
|
||||
status = fu.wait_for(std::chrono::seconds(0));
|
||||
if (status == std::future_status::ready) {
|
||||
process_finished_rep(fu.get());
|
||||
|
||||
std::swap(threadpool[i], threadpool.back());
|
||||
threadpool.pop_back();
|
||||
std::swap(kernel_pool[i], kernel_pool.back());
|
||||
K = kernel_pool.back();
|
||||
break;
|
||||
} // if
|
||||
} // for
|
||||
} // while
|
||||
|
||||
std::future<geometry_conversion_result*> fu = std::async(
|
||||
std::launch::async, [this](
|
||||
ifcopenshell::geometry::Converter* kernel,
|
||||
ifcopenshell::geometry::Settings settings,
|
||||
geometry_conversion_result* rep) {
|
||||
// Catch exceptions to be safe from freezing the iterator.
|
||||
try {
|
||||
this->create_element_(kernel, settings, rep);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(
|
||||
std::string("Exception '") + e.what() +
|
||||
std::string("' occurred while iterator was creating a shape: "),
|
||||
rep->item->instance
|
||||
);
|
||||
had_error_processing_elements_ = true;
|
||||
} catch (...) {
|
||||
Logger::Error(
|
||||
"Unknown exception occurred while iteartor was creating a shape: ",
|
||||
rep->item->instance
|
||||
);
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
return rep;
|
||||
},
|
||||
K,
|
||||
std::ref(settings_),
|
||||
&rep);
|
||||
|
||||
if (terminating_) {
|
||||
break;
|
||||
}
|
||||
|
||||
threadpool.emplace_back(std::move(fu));
|
||||
}
|
||||
|
||||
for (auto& fu : threadpool) {
|
||||
process_finished_rep(fu.get());
|
||||
}
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Logger::SetProduct(boost::none);
|
||||
|
||||
if (!terminating_) {
|
||||
Logger::Status("\rDone creating geometry (" + boost::lexical_cast<std::string>(all_processed_elements_.size()) +
|
||||
" objects) ");
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes model's bounding box (bounds_min and bounds_max).
|
||||
/// @note Can take several minutes for large files.
|
||||
void compute_bounds(bool with_geometry)
|
||||
{
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
bounds_min_.components()(i) = std::numeric_limits<double>::infinity();
|
||||
bounds_max_.components()(i) = -std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
if (with_geometry) {
|
||||
size_t num_created = 0;
|
||||
do {
|
||||
IfcGeom::Element* geom_object = get();
|
||||
const IfcGeom::TriangulationElement* o = static_cast<const IfcGeom::TriangulationElement*>(geom_object);
|
||||
const IfcGeom::Representation::Triangulation& mesh = o->geometry();
|
||||
auto mat = o->transformation().data()->ccomponents();
|
||||
Eigen::Vector4d vec, transformed;
|
||||
|
||||
for (typename std::vector<double>::const_iterator it = mesh.verts().begin(); it != mesh.verts().end();) {
|
||||
const double& x = *(it++);
|
||||
const double& y = *(it++);
|
||||
const double& z = *(it++);
|
||||
vec << x, y, z, 1.;
|
||||
transformed = mat * vec;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
bounds_min_.components()(i) = std::min(bounds_min_.components()(i), transformed(i));
|
||||
bounds_max_.components()(i) = std::max(bounds_max_.components()(i), transformed(i));
|
||||
}
|
||||
}
|
||||
} while (++num_created, next());
|
||||
} else {
|
||||
std::vector<ifcopenshell::geometry::geometry_conversion_task> reps;
|
||||
converter_->mapping()->get_representations(reps, filters_);
|
||||
|
||||
std::vector<IfcUtil::IfcBaseClass*> products;
|
||||
for (auto& r : reps) {
|
||||
std::copy(r.products->begin(), r.products->end(), std::back_inserter(products));
|
||||
}
|
||||
|
||||
for (auto& product : products) {
|
||||
auto prod_item = converter_->mapping()->map(product);
|
||||
auto vec = ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::geom_item>(prod_item)->matrix->translation_part();
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
bounds_min_.components()(i) = std::min(bounds_min_.components()(i), vec(i));
|
||||
bounds_max_.components()(i) = std::max(bounds_max_.components()(i), vec(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int progress() const {
|
||||
return progress_;
|
||||
}
|
||||
|
||||
std::string getLog() const { return Logger::GetLog(); }
|
||||
|
||||
IfcParse::IfcFile* file() const { return ifc_file; }
|
||||
|
||||
const std::vector<IfcGeom::filter_t>& filters() const { return filters_; }
|
||||
std::vector<IfcGeom::filter_t>& filters() { return filters_; }
|
||||
|
||||
const ifcopenshell::geometry::taxonomy::point3& bounds_min() const { return bounds_min_; }
|
||||
const ifcopenshell::geometry::taxonomy::point3& bounds_max() const { return bounds_max_; }
|
||||
|
||||
private:
|
||||
|
||||
std::mutex caching_mutex_;
|
||||
|
||||
template <typename Fn>
|
||||
Element* decorate_with_cache_(GeometrySerializer::read_type rt, const std::string& product_guid, const std::string& representation_id, Fn f) {
|
||||
|
||||
|
||||
bool read_from_cache = false;
|
||||
Element* element = nullptr;
|
||||
|
||||
@@ -485,357 +183,35 @@ namespace IfcGeom {
|
||||
std::lock_guard<std::mutex> lk(caching_mutex_);
|
||||
|
||||
if (rt == GeometrySerializer::READ_TRIANGULATION) {
|
||||
cache_->write((IfcGeom::TriangulationElement*) element);
|
||||
cache_->write((IfcGeom::TriangulationElement*)element);
|
||||
} else {
|
||||
cache_->write((IfcGeom::BRepElement*)element);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
const IfcUtil::IfcBaseClass* create_shape_model_for_next_entity() {
|
||||
geometry_conversion_result* task = nullptr;
|
||||
for (; task_iterator_ < tasks_.end();) {
|
||||
task = &*task_iterator_++;
|
||||
create_element_(converter_, settings_, task);
|
||||
if (task->elements.empty()) {
|
||||
task = nullptr;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (task) {
|
||||
process_finished_rep(task);
|
||||
return task->item->instance->as<IfcUtil::IfcBaseClass>();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
const IfcUtil::IfcBaseClass* create_shape_model_for_next_entity();
|
||||
|
||||
void create_element_(
|
||||
ifcopenshell::geometry::Converter* kernel,
|
||||
ifcopenshell::geometry::Settings settings,
|
||||
geometry_conversion_result* rep)
|
||||
{
|
||||
if (!settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get()) {
|
||||
rep->item = kernel->mapping()->map(rep->representation);
|
||||
if (!rep->item) {
|
||||
return;
|
||||
}
|
||||
std::transform(rep->products_2->begin(), rep->products_2->end(), std::back_inserter(rep->products), [this, &rep, kernel](IfcUtil::IfcBaseClass* prod) {
|
||||
auto prod_item = kernel->mapping()->map(prod);
|
||||
return std::make_pair(prod->as<IfcUtil::IfcBaseEntity>(), ifcopenshell::geometry::taxonomy::cast<ifcopenshell::geometry::taxonomy::geom_item>(prod_item)->matrix);
|
||||
});
|
||||
} else {
|
||||
}
|
||||
|
||||
auto product_node = rep->products.front();
|
||||
const IfcUtil::IfcBaseEntity* product = product_node.first;
|
||||
const auto& place = product_node.second;
|
||||
|
||||
Logger::SetProduct(product);
|
||||
|
||||
IfcGeom::BRepElement* brep = static_cast<IfcGeom::BRepElement*>(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product->get("GlobalId"), std::to_string(rep->item->instance->as<IfcUtil::IfcBaseEntity>()->id()), [kernel, settings, product, place, rep]() {
|
||||
return kernel->create_brep_for_representation_and_product(rep->item, product, place);
|
||||
}));
|
||||
|
||||
if (!brep) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto elem = process_based_on_settings(settings, brep);
|
||||
if (!elem) {
|
||||
return;
|
||||
}
|
||||
|
||||
rep->breps = { brep };
|
||||
rep->elements = { elem };
|
||||
|
||||
for (auto it = rep->products.begin() + 1; it != rep->products.end(); ++it) {
|
||||
const auto& p = *it;
|
||||
const IfcUtil::IfcBaseEntity* product2 = p.first;
|
||||
const auto& place2 = p.second;
|
||||
|
||||
IfcGeom::BRepElement* brep2 = static_cast<IfcGeom::BRepElement*>(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product2->get("GlobalId"), std::to_string(rep->item->instance->as<IfcUtil::IfcBaseEntity>()->id()), [kernel, settings, product2, place2, brep]() {
|
||||
return kernel->create_brep_for_processed_representation(product2, place2, brep);
|
||||
}));
|
||||
if (brep2) {
|
||||
auto elem2 = process_based_on_settings(settings, brep2, dynamic_cast<IfcGeom::TriangulationElement*>(elem));
|
||||
if (elem2) {
|
||||
rep->breps.push_back(brep2);
|
||||
rep->elements.push_back(elem2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
geometry_conversion_result* rep);
|
||||
|
||||
IfcGeom::Element* process_based_on_settings(
|
||||
ifcopenshell::geometry::Settings settings,
|
||||
IfcGeom::BRepElement* elem,
|
||||
IfcGeom::TriangulationElement* previous = nullptr)
|
||||
{
|
||||
if (settings.get<ifcopenshell::geometry::settings::IteratorOutput>().get() == ifcopenshell::geometry::settings::SERIALIZED) {
|
||||
try {
|
||||
return new IfcGeom::SerializedElement(*elem);
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Getting a serialized element from model failed.");
|
||||
return nullptr;
|
||||
}
|
||||
} else if (settings.get<ifcopenshell::geometry::settings::IteratorOutput>().get() == ifcopenshell::geometry::settings::TRIANGULATED) {
|
||||
// the part before the hyphen is the representation id
|
||||
auto gid2 = elem->geometry().id();
|
||||
auto hyphen = gid2.find("-");
|
||||
if (hyphen != std::string::npos) {
|
||||
gid2 = gid2.substr(0, hyphen);
|
||||
}
|
||||
IfcGeom::TriangulationElement* previous = nullptr);
|
||||
|
||||
return decorate_with_cache_(GeometrySerializer::READ_TRIANGULATION, elem->guid(), gid2, [elem, previous]() {
|
||||
try {
|
||||
if (!previous) {
|
||||
return new TriangulationElement(*elem);
|
||||
} else {
|
||||
return new TriangulationElement(*elem, previous->geometry_pointer());
|
||||
}
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Getting a triangulation element from model failed.");
|
||||
}
|
||||
return (TriangulationElement*)nullptr;
|
||||
});
|
||||
} else {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
bool wait_for_element();
|
||||
|
||||
bool wait_for_element() {
|
||||
while (true) {
|
||||
size_t s;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(element_ready_mutex_);
|
||||
s = all_processed_elements_.size();
|
||||
}
|
||||
if (s > async_elements_returned_) {
|
||||
++async_elements_returned_;
|
||||
return true;
|
||||
} else if (finished_) {
|
||||
return false;
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void log_timepoints() const {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration;
|
||||
using namespace std::string_literals;
|
||||
|
||||
std::array<std::string, 3> labels = {
|
||||
"Initializing mapping"s,
|
||||
"Performing mapping"s,
|
||||
"Geometry interpretation"s
|
||||
};
|
||||
|
||||
for (auto it = time_points.begin() + 1; it != time_points.end(); ++it) {
|
||||
auto jt = it - 1;
|
||||
duration<double, std::milli> ms_double = (*it) - (*jt);
|
||||
Logger::Notice(labels[std::distance(time_points.begin(), jt)] + " took " + std::to_string(ms_double.count()) + "ms");
|
||||
}
|
||||
}
|
||||
void log_timepoints() const;
|
||||
void validate_iterator_state() const;
|
||||
|
||||
ifcopenshell::geometry::taxonomy::direction3::ptr remove_offset_();
|
||||
public:
|
||||
/// Returns what would be the product for the next shape representation
|
||||
/// @todo Double-check and test the impl.
|
||||
//IfcSchema::IfcProduct* peek_next() const
|
||||
//{
|
||||
// if (ifcproducts && ifcproduct_iterator + 1 != ifcproducts->end()){
|
||||
// return *(ifcproduct_iterator + 1);
|
||||
// } else {
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
||||
|
||||
/// @todo Would this be as simple as the following code?
|
||||
//void skip_next() { if (ifcproducts) { ++ifcproduct_iterator; } }
|
||||
|
||||
/// Moves to the next shape representation, create its geometry, and returns the associated product.
|
||||
/// Use get() to retrieve the created geometry.
|
||||
const IfcUtil::IfcBaseClass* next() {
|
||||
using std::chrono::high_resolution_clock;
|
||||
|
||||
if (*native_task_result_iterator_ != *task_result_iterator_) {
|
||||
delete* native_task_result_iterator_;
|
||||
}
|
||||
delete *task_result_iterator_;
|
||||
|
||||
if (num_threads_ != 1) {
|
||||
if (!wait_for_element()) {
|
||||
Logger::SetProduct(boost::none);
|
||||
time_points[3] = high_resolution_clock::now();
|
||||
log_timepoints();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
task_result_iterator_++;
|
||||
native_task_result_iterator_++;
|
||||
|
||||
return (*task_result_iterator_)->product();
|
||||
} else {
|
||||
// Increment the iterator over the list of products using the current
|
||||
// shape representation
|
||||
if (task_result_iterator_ == --all_processed_elements_.end()) {
|
||||
if (!create()) {
|
||||
Logger::SetProduct(boost::none);
|
||||
time_points[3] = high_resolution_clock::now();
|
||||
log_timepoints();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
task_result_iterator_++;
|
||||
native_task_result_iterator_++;
|
||||
|
||||
return (*task_result_iterator_)->product();
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the representation of the current geometrical entity.
|
||||
Element* get()
|
||||
{
|
||||
if (!initialization_outcome_) {
|
||||
throw std::runtime_error("Iterator not initialized");
|
||||
}
|
||||
|
||||
auto ret = *task_result_iterator_;
|
||||
|
||||
// If we want to organize the element considering their hierarchy
|
||||
if (settings_.get<ifcopenshell::geometry::settings::UseElementHierarchy>().get())
|
||||
{
|
||||
// We are going to build a vector with the element parents.
|
||||
// First, create the parent vector
|
||||
std::vector<const IfcGeom::Element*> parents;
|
||||
|
||||
// if the element has a parent
|
||||
if (ret->parent_id() != -1)
|
||||
{
|
||||
const IfcGeom::Element* parent_object = NULL;
|
||||
bool hasParent = true;
|
||||
|
||||
// get the parent
|
||||
try {
|
||||
parent_object = get_object(ret->parent_id());
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
hasParent = false;
|
||||
}
|
||||
|
||||
// Add the previously found parent to the vector
|
||||
if (hasParent) parents.insert(parents.begin(), parent_object);
|
||||
|
||||
// We need to find all the parents
|
||||
while (parent_object != NULL && hasParent && parent_object->parent_id() != -1)
|
||||
{
|
||||
// Find the next parent
|
||||
try {
|
||||
parent_object = get_object(parent_object->parent_id());
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
hasParent = false;
|
||||
}
|
||||
|
||||
// Add the previously found parent to the vector
|
||||
if (hasParent) parents.insert(parents.begin(), parent_object);
|
||||
|
||||
hasParent = hasParent && parent_object->parent_id() != -1;
|
||||
}
|
||||
|
||||
// when done push the parent list in the Element object
|
||||
ret->SetParents(parents);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Gets the native (Open Cascade or CGAL) representation of the current geometrical entity.
|
||||
BRepElement* get_native()
|
||||
{
|
||||
return *native_task_result_iterator_;
|
||||
}
|
||||
|
||||
const Element* get_object(int id) {
|
||||
ifcopenshell::geometry::taxonomy::matrix4::ptr m4;
|
||||
int parent_id = -1;
|
||||
std::string instance_type, product_name, product_guid;
|
||||
IfcUtil::IfcBaseEntity* ifc_product = 0;
|
||||
|
||||
try {
|
||||
ifc_product = ifc_file->instance_by_id(id)->as<IfcUtil::IfcBaseEntity>();
|
||||
instance_type = ifc_product->declaration().name();
|
||||
|
||||
if (ifc_product->declaration().is("IfcRoot")) {
|
||||
product_guid = (std::string) ifc_product->get("GlobalId");
|
||||
product_name = ifc_product->get_value<std::string>("Name", "");
|
||||
}
|
||||
|
||||
auto parent_object = converter_->mapping()->get_decomposing_entity(ifc_product);
|
||||
if (parent_object) {
|
||||
parent_id = parent_object->id();
|
||||
}
|
||||
|
||||
// fails in case of IfcProject
|
||||
auto mapped = converter_->mapping()->map(ifc_product);
|
||||
auto casted = mapped ? ifcopenshell::geometry::taxonomy::dcast<ifcopenshell::geometry::taxonomy::geom_item>(mapped) : nullptr;
|
||||
|
||||
if (casted) {
|
||||
m4 = casted->matrix;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
}
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
catch (const Standard_Failure& e) {
|
||||
if (e.GetMessageString() && strlen(e.GetMessageString())) {
|
||||
Logger::Error(e.GetMessageString());
|
||||
} else {
|
||||
Logger::Error("Unknown error returning product");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
catch (...) {
|
||||
Logger::Error("Unknown error returning product");
|
||||
}
|
||||
|
||||
Element* ifc_object = new Element(settings_, id, parent_id, product_name, instance_type, product_guid, "", m4, ifc_product);
|
||||
return ifc_object;
|
||||
}
|
||||
|
||||
const IfcUtil::IfcBaseClass* create() {
|
||||
const IfcUtil::IfcBaseClass* product = nullptr;
|
||||
try {
|
||||
product = create_shape_model_for_next_entity();
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
catch (const Standard_Failure& e) {
|
||||
if (e.GetMessageString() && strlen(e.GetMessageString())) {
|
||||
Logger::Error(e.GetMessageString());
|
||||
} else {
|
||||
Logger::Error("Unknown error creating geometry");
|
||||
}
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
#endif
|
||||
catch (...) {
|
||||
Logger::Error("Unknown error creating geometry");
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
Iterator(const std::string& geometry_library, const ifcopenshell::geometry::Settings& settings, IfcParse::IfcFile* file, const std::vector<IfcGeom::filter_t>& filters, int num_threads)
|
||||
: settings_(settings)
|
||||
, ifc_file(file)
|
||||
@@ -883,31 +259,95 @@ namespace IfcGeom {
|
||||
{
|
||||
}
|
||||
|
||||
~Iterator() {
|
||||
if (num_threads_ != 1) {
|
||||
terminating_ = true;
|
||||
~Iterator();
|
||||
|
||||
if (init_future_.valid()) {
|
||||
init_future_.wait();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& k : kernel_pool) {
|
||||
delete k;
|
||||
}
|
||||
void set_cache(GeometrySerializer* cache) { cache_ = cache; }
|
||||
|
||||
if (task_result_ptr_initialized) {
|
||||
while (task_result_iterator_ != --all_processed_elements_.end()) {
|
||||
if (*native_task_result_iterator_ != *task_result_iterator_) {
|
||||
delete* native_task_result_iterator_;
|
||||
}
|
||||
delete *task_result_iterator_++;
|
||||
native_task_result_iterator_++;
|
||||
}
|
||||
std::vector<ifcopenshell::geometry::taxonomy::item::ptr> get_task_items() const {
|
||||
std::vector<ifcopenshell::geometry::taxonomy::item::ptr> items;
|
||||
items.reserve(tasks_.size());
|
||||
for (const auto& task : tasks_) {
|
||||
items.push_back(task.item);
|
||||
}
|
||||
|
||||
delete converter_;
|
||||
return items;
|
||||
}
|
||||
|
||||
aggregate_of_aggregate_of_instance::ptr get_task_products() const {
|
||||
aggregate_of_aggregate_of_instance::ptr products = aggregate_of_aggregate_of_instance::ptr(new aggregate_of_aggregate_of_instance);
|
||||
for (const auto& task : tasks_) {
|
||||
if (task.products_2) {
|
||||
products->push(task.products_2);
|
||||
} else {
|
||||
for (auto& product : task.products) {
|
||||
aggregate_of_instance::ptr p(new aggregate_of_instance);
|
||||
p->push(product.first);
|
||||
products->push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
return products;
|
||||
}
|
||||
|
||||
const std::string& unit_name() const { return converter_->mapping()->get_length_unit_name(); }
|
||||
double unit_magnitude() const { return converter_->mapping()->get_length_unit(); }
|
||||
// Check if error occurred during iterator initialization or iteration over elements.
|
||||
bool had_error_processing_elements() const { return had_error_processing_elements_; }
|
||||
|
||||
boost::optional<bool> initialization_outcome_;
|
||||
|
||||
/**
|
||||
* @return Returns true if the iterator is initialized with any elements, false otherwise.
|
||||
*
|
||||
* @note
|
||||
* - A true return value does not guarantee successful initialization of all elements.
|
||||
* Some elements may have failed to initialize. Check had_error_processing_elements()
|
||||
* to see whether there were errors during the initialization.
|
||||
*
|
||||
* - For non-concurrent iterators, a false return may occur if initialization of the first
|
||||
* element fails, even if subsequent elements could be initialized successfully.
|
||||
*/
|
||||
bool initialize();
|
||||
|
||||
size_t processed_ = 0;
|
||||
|
||||
void process_finished_rep(geometry_conversion_result* rep);
|
||||
|
||||
void process_concurrently();
|
||||
|
||||
/// Computes model's bounding box (bounds_min and bounds_max).
|
||||
/// @note Can take several minutes for large files.
|
||||
void compute_bounds(bool with_geometry);
|
||||
|
||||
int progress() const {
|
||||
return progress_;
|
||||
}
|
||||
|
||||
std::string getLog() const { return Logger::GetLog(); }
|
||||
|
||||
IfcParse::IfcFile* file() const { return ifc_file; }
|
||||
|
||||
const std::vector<IfcGeom::filter_t>& filters() const { return filters_; }
|
||||
std::vector<IfcGeom::filter_t>& filters() { return filters_; }
|
||||
|
||||
const ifcopenshell::geometry::taxonomy::point3& bounds_min() const { return bounds_min_; }
|
||||
const ifcopenshell::geometry::taxonomy::point3& bounds_max() const { return bounds_max_; }
|
||||
|
||||
/// Moves to the next shape representation, create its geometry, and returns the associated product.
|
||||
/// Use get() to retrieve the created geometry.
|
||||
const IfcUtil::IfcBaseClass* next();
|
||||
|
||||
/// Gets the representation of the current geometrical entity.
|
||||
Element* get();
|
||||
|
||||
/// Gets the native (Open Cascade or CGAL) representation of the current geometrical entity.
|
||||
BRepElement* get_native()
|
||||
{
|
||||
return *native_task_result_iterator_;
|
||||
}
|
||||
|
||||
const Element* get_object(int id);
|
||||
|
||||
const IfcUtil::IfcBaseClass* create();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ struct gradient_fn_evaluator : public fn_evaluator {
|
||||
// Put curvature back into the solution matrix
|
||||
// curvature for vertical is in column 0, need it to be in column 1
|
||||
// so it doesn't add to curvature for horizontal
|
||||
std::swap(vertical_curvature(3, 0), vertical_curvature(3, 1));
|
||||
std::swap(vertical_curvature(0), vertical_curvature(1));
|
||||
m.row(3) = horizontal_curvature + vertical_curvature;
|
||||
|
||||
return m;
|
||||
|
||||
@@ -11,6 +11,16 @@ namespace ifcopenshell { namespace geometry {
|
||||
/// Returns (x,y,theta) at L/2. The results are in a vector so they can be returned to python
|
||||
std::vector<double> helmert_curve_point(double A0, double A1, double A2, double s);
|
||||
|
||||
/// @brief Converts a loop to a function item.
|
||||
/// This is intended to be used from python side. Polylines are mapped to a loop, but when
|
||||
/// representing an alignment they need to be a function_item so the can be evaluated by function_item_evaluator.
|
||||
/// On the C++ side, the dcast operator take care of this, but dcast is not accessible on the python side.
|
||||
/// @param loop
|
||||
/// @return
|
||||
static taxonomy::function_item::ptr convert_loop_to_function_item(taxonomy::loop::ptr loop) {
|
||||
return ifcopenshell::geometry::taxonomy::dcast<taxonomy::function_item>(loop);
|
||||
}
|
||||
|
||||
/// @brief Abstract class for evaluating a function_item. This class is specialized for each of the function_item types.
|
||||
struct fn_evaluator {
|
||||
fn_evaluator(const ifcopenshell::geometry::Settings& settings) : settings_(settings) {
|
||||
|
||||
@@ -47,8 +47,6 @@ namespace {
|
||||
};
|
||||
|
||||
bool are_facets_coplanar(const Facet_const_handle& f1, const Facet_const_handle& f2) {
|
||||
// Function to determine if two facets are coplanar
|
||||
// You can use the normal vectors and the equation of the planes to determine coplanarity
|
||||
auto normal_1 = CGAL::normal(f1->halfedge()->vertex()->point(),
|
||||
f1->halfedge()->next()->vertex()->point(),
|
||||
f1->halfedge()->next()->next()->vertex()->point());
|
||||
@@ -69,8 +67,8 @@ namespace {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a new component for coplanar facets
|
||||
std::set<Facet_const_handle> component;
|
||||
components.emplace_back();
|
||||
auto& component = components.back();
|
||||
std::queue<Facet_const_handle> queue;
|
||||
|
||||
queue.push(face);
|
||||
@@ -82,18 +80,15 @@ namespace {
|
||||
|
||||
component.insert(current);
|
||||
|
||||
// Iterate over neighboring facets
|
||||
Halfedge_around_facet_circulator he = current->facet_begin();
|
||||
do {
|
||||
Facet_const_handle neighbour = he->opposite()->face();
|
||||
if (neighbour != nullptr && visited.find(neighbour) == visited.end() && are_facets_coplanar(current, neighbour)) {
|
||||
if (visited.find(neighbour) == visited.end() && neighbour != nullptr && visited.find(neighbour) == visited.end() && are_facets_coplanar(current, neighbour)) {
|
||||
queue.push(neighbour);
|
||||
visited.insert(neighbour);
|
||||
}
|
||||
} while (++he != current->facet_begin());
|
||||
}
|
||||
|
||||
components.push_back(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,20 +179,30 @@ void ifcopenshell::geometry::CgalShape::to_nef() const {
|
||||
#endif
|
||||
|
||||
void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const {
|
||||
// Copy is made because triangulate_faces() obviously does not accept a const argument
|
||||
// ... also becuase of transforming the vertex positions, right?
|
||||
cgal_shape_t s = *this;
|
||||
const bool all_triangles = std::all_of(shape_->facets_begin(), shape_->facets_end(), [](auto f) { return f.is_triangle(); });
|
||||
const bool has_iden_transform = place.is_identity();
|
||||
|
||||
std::unique_ptr<cgal_shape_t> shape_copy_holder;
|
||||
cgal_shape_t* shape_to_use;
|
||||
|
||||
if (!all_triangles || !has_iden_transform) {
|
||||
// A copy is made when triangulate_faces() is required or when vertex positions need be transformed
|
||||
shape_copy_holder.reset(new cgal_shape_t(*this));
|
||||
shape_to_use = shape_copy_holder.get();
|
||||
} else {
|
||||
shape_to_use = &*shape_;
|
||||
}
|
||||
|
||||
const bool setting_use_original_edges = settings.get<ifcopenshell::geometry::settings::CgalEmitOriginalEdges>().get();
|
||||
|
||||
std::set<std::set<Kernel_::Point_3>> original_edges;
|
||||
if (setting_use_original_edges) {
|
||||
for (auto it = s.edges_begin(); it != s.edges_end(); ++it) {
|
||||
for (auto it = shape_to_use->edges_begin(); it != shape_to_use->edges_end(); ++it) {
|
||||
original_edges.insert({ it->vertex()->point(), it->prev()->vertex()->point() });
|
||||
}
|
||||
}
|
||||
|
||||
if (!place.is_identity()) {
|
||||
if (!has_iden_transform) {
|
||||
const auto& m = place.ccomponents();
|
||||
|
||||
// @todo check
|
||||
@@ -207,35 +212,43 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett
|
||||
m(2, 0), m(2, 1), m(2, 2), m(2, 3));
|
||||
|
||||
// Apply transformation
|
||||
for (auto &vertex : s.vertex_handles()) {
|
||||
for (auto &vertex : shape_to_use->vertex_handles()) {
|
||||
vertex->point() = vertex->point().transform(trsf);
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::all_of(s.facets_begin(), s.facets_end(), [](auto f) { return f.is_triangle(); })) {
|
||||
if (!s.is_valid()) {
|
||||
boost::optional<double> smooth_treshold;
|
||||
{
|
||||
auto setting_value = settings.get<ifcopenshell::geometry::settings::CgalSmoothAngleDegrees>().get();
|
||||
if (setting_value > 0.) {
|
||||
smooth_treshold = std::cos(setting_value * boost::math::constants::pi<double>() / 180.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!all_triangles) {
|
||||
if (!shape_to_use->is_valid()) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Invalid Polyhedron_3 in object (before triangulation)");
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
try {
|
||||
success = CGAL::Polygon_mesh_processing::triangulate_faces(s);
|
||||
success = CGAL::Polygon_mesh_processing::triangulate_faces(*shape_to_use);
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Triangulation crashed");
|
||||
return;
|
||||
}
|
||||
|
||||
CGAL::Polygon_mesh_processing::remove_degenerate_faces(s);
|
||||
CGAL::Polygon_mesh_processing::remove_degenerate_faces(*shape_to_use);
|
||||
|
||||
if (!success) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Triangulation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s.is_valid()) {
|
||||
if (!shape_to_use->is_valid()) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Invalid Polyhedron_3 in object (after triangulation)");
|
||||
// return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +257,7 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett
|
||||
std::vector<std::set<Facet_const_handle>> components;
|
||||
std::map<Facet_const_handle, typename decltype(components)::const_iterator> facet_to_component;
|
||||
if (!setting_use_original_edges) {
|
||||
partition_coplanar_components(s, components);
|
||||
partition_coplanar_components(*shape_to_use, components);
|
||||
for (auto it = components.begin(); it != components.end(); ++it) {
|
||||
for (auto& f : *it) {
|
||||
facet_to_component[f] = it;
|
||||
@@ -256,12 +269,12 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett
|
||||
// boost::associative_property_map<std::map<cgal_vertex_descriptor_t, Kernel_::Vector_3>> vertex_normals_map(vertex_normals);
|
||||
|
||||
// Triangulate the shape and compute the normals
|
||||
std::map<cgal_face_descriptor_t, Kernel_::Vector_3> face_normals;
|
||||
boost::associative_property_map<std::map<cgal_face_descriptor_t, Kernel_::Vector_3>> face_normals_map(face_normals);
|
||||
std::map<Facet_const_handle, Kernel_::Vector_3> face_normals;
|
||||
boost::associative_property_map<std::map<Facet_const_handle, Kernel_::Vector_3>> face_normals_map(face_normals);
|
||||
|
||||
// CGAL::Polygon_mesh_processing::compute_normals(s, vertex_normals_map, face_normals_map);
|
||||
try {
|
||||
CGAL::Polygon_mesh_processing::compute_face_normals(s, face_normals_map);
|
||||
CGAL::Polygon_mesh_processing::compute_face_normals(*shape_to_use, face_normals_map);
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Face normal calculation failed");
|
||||
return;
|
||||
@@ -275,23 +288,55 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett
|
||||
std::set<std::pair<int, int>> registered_edges;
|
||||
|
||||
int num_faces = 0, num_vertices = 0;
|
||||
for (auto &face : faces(s)) {
|
||||
for (auto &face : faces(*shape_to_use)) {
|
||||
if (!face->is_triangle()) {
|
||||
std::cout << "Warning: non-triangular face!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
CGAL::Polyhedron_3<Kernel_>::Halfedge_around_facet_const_circulator current_halfedge = face->facet_begin();
|
||||
|
||||
const Kernel_::Vector_3 facet_normal = face_normals_map[face];
|
||||
|
||||
int vertexidx[3];
|
||||
bool is_face_boundary[3];
|
||||
int i = 0;
|
||||
do {
|
||||
auto v = current_halfedge->vertex();
|
||||
|
||||
auto vertex_norm = facet_normal;
|
||||
|
||||
if (smooth_treshold) {
|
||||
Kernel_::Vector_3 normal_accum(0, 0, 0);
|
||||
{
|
||||
// circulator around the vertex
|
||||
auto vh_begin = v->vertex_begin();
|
||||
if (vh_begin != nullptr) {
|
||||
auto vh = vh_begin;
|
||||
do {
|
||||
if (!vh->is_border()) {
|
||||
Facet_const_handle adj_f = vh->facet();
|
||||
const auto fn2 = face_normals_map[adj_f];
|
||||
if ((fn2 * facet_normal) >= *smooth_treshold) {
|
||||
normal_accum = normal_accum + fn2;
|
||||
}
|
||||
++vh;
|
||||
}
|
||||
} while (vh != vh_begin);
|
||||
}
|
||||
}
|
||||
const double len = std::sqrt(CGAL::to_double(normal_accum.squared_length()));
|
||||
if (len > 0) {
|
||||
vertex_norm = normal_accum / len;
|
||||
}
|
||||
}
|
||||
|
||||
postion_normal pn = {
|
||||
current_halfedge->vertex()->point().cartesian(0),
|
||||
current_halfedge->vertex()->point().cartesian(1),
|
||||
current_halfedge->vertex()->point().cartesian(2),
|
||||
face_normals_map[face].cartesian(0),
|
||||
face_normals_map[face].cartesian(1),
|
||||
face_normals_map[face].cartesian(2)
|
||||
v->point().cartesian(0),
|
||||
v->point().cartesian(1),
|
||||
v->point().cartesian(2),
|
||||
vertex_norm.cartesian(0),
|
||||
vertex_norm.cartesian(1),
|
||||
vertex_norm.cartesian(2)
|
||||
};
|
||||
|
||||
// @todo normalzie based on largest component?
|
||||
|
||||
@@ -38,16 +38,6 @@ using namespace IfcGeom;
|
||||
using namespace ifcopenshell::geometry;
|
||||
using namespace ifcopenshell::geometry::kernels;
|
||||
|
||||
void CgalKernel::remove_duplicate_points_from_loop(cgal_wire_t& polygon) {
|
||||
std::set<cgal_point_t> points;
|
||||
for (int i = 0; i < polygon.size(); ++i) {
|
||||
if (points.count(polygon[i])) {
|
||||
polygon.erase(polygon.begin() + i);
|
||||
--i;
|
||||
} else points.insert(polygon[i]);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct PolyhedronBuilder : public CGAL::Modifier_base<CGAL::Polyhedron_3<Kernel_>::HalfedgeDS> {
|
||||
private:
|
||||
@@ -723,17 +713,31 @@ bool CgalKernel::convert(const taxonomy::loop::ptr loop, cgal_wire_t& result) {
|
||||
// A loop should consist of at least three vertices
|
||||
std::size_t original_count = polygon.size();
|
||||
if (original_count < 3) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Not enough edges for:", loop->instance);
|
||||
Logger::Warning("Not enough edges for:", loop->instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove points that are too close to one another
|
||||
remove_duplicate_points_from_loop(polygon);
|
||||
// this is done now in the mapping layer with Eigen
|
||||
// remove_duplicate_points_from_loop(polygon);
|
||||
|
||||
std::size_t count = polygon.size();
|
||||
if (original_count - count != 0) {
|
||||
std::stringstream ss; ss << (original_count - count) << " edges removed for:";
|
||||
Logger::Message(Logger::LOG_WARNING, ss.str(), loop->instance);
|
||||
Logger::Warning(ss.str(), loop->instance);
|
||||
}
|
||||
|
||||
{
|
||||
std::set<cgal_point_t> visited_points;
|
||||
for (auto& p : polygon) {
|
||||
if (visited_points.find(p) != visited_points.end()) {
|
||||
Logger::Error("Skipping self-intersecting loop", loop->instance);
|
||||
// @todo signal somehow that occt kernel might be able to solve this
|
||||
// @todo implement cycle detection using Arrangement_2, but that only works in exact kernel
|
||||
return false;
|
||||
}
|
||||
visited_points.insert(p);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Kernel_::Segment_3> segments;
|
||||
|
||||
@@ -94,8 +94,6 @@ namespace ifcopenshell {
|
||||
: AbstractKernel("cgal", settings)
|
||||
{}
|
||||
|
||||
void remove_duplicate_points_from_loop(cgal_wire_t& polygon);
|
||||
|
||||
bool convert(const taxonomy::extrusion::ptr, cgal_shape_t&);
|
||||
bool convert(const taxonomy::face::ptr, std::list<cgal_face_t>&);
|
||||
bool convert(const taxonomy::loop::ptr, cgal_wire_t&);
|
||||
|
||||
@@ -1365,8 +1365,10 @@ namespace IfcGeom {
|
||||
}
|
||||
|
||||
std::vector<T> select(const IfcGeom::BRepElement* elem, bool completely_within = false, double extend = -1.e-5) const {
|
||||
auto shp = elem->geometry().as_compound();
|
||||
auto compound = ((ifcopenshell::geometry::OpenCascadeShape*)shp)->shape();
|
||||
auto shp = (ifcopenshell::geometry::OpenCascadeShape*)elem->geometry().as_compound();
|
||||
TopoDS_Shape compound(std::move(((ifcopenshell::geometry::OpenCascadeShape*)shp)->shape()));
|
||||
delete shp;
|
||||
|
||||
const auto& m = elem->transformation().data()->ccomponents();
|
||||
gp_Trsf tr;
|
||||
tr.SetValues(
|
||||
@@ -1956,8 +1958,9 @@ namespace IfcGeom {
|
||||
return;
|
||||
}
|
||||
|
||||
auto compound_generic = elem->geometry().as_compound();
|
||||
auto compound = ((ifcopenshell::geometry::OpenCascadeShape*)compound_generic)->shape();
|
||||
auto compound_generic = (ifcopenshell::geometry::OpenCascadeShape*)elem->geometry().as_compound();
|
||||
TopoDS_Shape compound(std::move(compound_generic->shape()));
|
||||
delete compound_generic;
|
||||
|
||||
const auto& m = elem->transformation().data()->ccomponents();
|
||||
gp_Trsf tr;
|
||||
|
||||
@@ -66,12 +66,28 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr
|
||||
// to keep track of which edges were already emitted.
|
||||
std::set<std::pair<int, int>> emitted_edges;
|
||||
|
||||
// Triangulate the shape
|
||||
try {
|
||||
BRepMesh_IncrementalMesh(shape_, settings.get<settings::MesherLinearDeflection>().get(), false, settings.get<settings::MesherAngularDeflection>().get());
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape");
|
||||
return;
|
||||
// Do our own check if there are triangulations. Any will do. This is faster than the OCCT incremental check which compares the deflection tolerances and initialized a bunch of state
|
||||
bool has_triangulation = false;
|
||||
{
|
||||
TopExp_Explorer exp;
|
||||
for (exp.Init(shape_, TopAbs_FACE); exp.More(); exp.Next()) {
|
||||
TopLoc_Location loc;
|
||||
const Handle(Poly_Triangulation)& tri =
|
||||
BRep_Tool::Triangulation(TopoDS::Face(exp.Current()), loc);
|
||||
if (tri) {
|
||||
has_triangulation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_triangulation) {
|
||||
// Triangulate the shape
|
||||
try {
|
||||
BRepMesh_IncrementalMesh(shape_, settings.get<settings::MesherLinearDeflection>().get(), false, settings.get<settings::MesherAngularDeflection>().get());
|
||||
} catch (...) {
|
||||
Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterates over the faces of the shape
|
||||
@@ -328,7 +344,9 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr
|
||||
}
|
||||
}
|
||||
|
||||
BRepTools::Clean(shape_);
|
||||
if (!settings.get<settings::OcctNoCleanTriangulation>().get()) {
|
||||
BRepTools::Clean(shape_);
|
||||
}
|
||||
}
|
||||
|
||||
void ifcopenshell::geometry::OpenCascadeShape::Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string& r) const {
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace ifcopenshell {
|
||||
public:
|
||||
OpenCascadeShape(const TopoDS_Shape& shape)
|
||||
: shape_(shape) {}
|
||||
OpenCascadeShape(TopoDS_Shape&& shape)
|
||||
: shape_(std::move(shape)) {}
|
||||
|
||||
const TopoDS_Shape& shape() const { return shape_; }
|
||||
operator const TopoDS_Shape& () { return shape_; }
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace IfcGeom {
|
||||
|
||||
bool is_nested_compound_of_solid(const TopoDS_Shape& s, int depth = 0);
|
||||
|
||||
// Creates a solid from a compound of faces. When there are multiple connected components,
|
||||
// a compound of solids is returned.
|
||||
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);
|
||||
|
||||
@@ -1451,10 +1451,10 @@ TopoDS_Shape IfcGeom::util::ensure_fit_for_subtraction(const TopoDS_Shape& shape
|
||||
return shape;
|
||||
}
|
||||
|
||||
TopoDS_Solid solid;
|
||||
if (!create_solid_from_compound(shape, solid, tol)) {
|
||||
TopoDS_Shape solid_or_compound_of_solids;
|
||||
if (!create_solid_from_compound(shape, solid_or_compound_of_solids, tol)) {
|
||||
return shape;
|
||||
}
|
||||
|
||||
return solid;
|
||||
return solid_or_compound_of_solids;
|
||||
}
|
||||
|
||||
@@ -270,7 +270,8 @@ bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& re
|
||||
|
||||
face_definition fd;
|
||||
|
||||
if (face->basis) {
|
||||
// when the surface is planar we do not care about it
|
||||
if (face->basis && face->basis->kind() != taxonomy::PLANE) {
|
||||
fd.surface() = convert_surface(face->basis);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,13 @@ IfcGeom::OpenCascadeKernel::faceset_helper::faceset_helper(
|
||||
for (auto& l : f->children) {
|
||||
loops.push_back(l);
|
||||
for (auto& e : l->children) {
|
||||
// @todo make sure only cartesian points are provided here
|
||||
auto& p = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(e->orientation.get_value_or(true) ? e->start : e->end);
|
||||
if (point_identities_visited.find(p->identity()) == point_identities_visited.end()) {
|
||||
point_identities_visited.insert(p->identity());
|
||||
points.push_back(p);
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
// @todo make sure only cartesian points are provided here
|
||||
auto& p = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(i == 0 ? e->start : e->end);
|
||||
if (point_identities_visited.find(p->identity()) == point_identities_visited.end()) {
|
||||
point_identities_visited.insert(p->identity());
|
||||
points.push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,21 +208,19 @@ void IfcGeom::OpenCascadeKernel::faceset_helper::loop_(const ifcopenshell::geome
|
||||
return;
|
||||
}
|
||||
|
||||
auto a = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(ps->children.back()->orientation.get_value_or(true) ? ps->children.back()->start : ps->children.back()->end);
|
||||
auto A = a->identity();
|
||||
for (auto& b : ps->children) {
|
||||
auto B = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(b->orientation.get_value_or(true) ? b->start : b->end)->identity();
|
||||
for (auto& edge : ps->children) {
|
||||
auto A = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(edge->start)->identity();
|
||||
auto B = boost::get<ifcopenshell::geometry::taxonomy::point3::ptr>(edge->end)->identity();
|
||||
auto C = vertex_mapping_[A], D = vertex_mapping_[B];
|
||||
bool fwd = C < D;
|
||||
if (!b->orientation) {
|
||||
fwd = !fwd;
|
||||
}
|
||||
if (!fwd) {
|
||||
std::swap(C, D);
|
||||
}
|
||||
if (!edge->orientation.get_value_or(true)) {
|
||||
fwd = !fwd;
|
||||
}
|
||||
if (C != D) {
|
||||
callback(C, D, fwd);
|
||||
A = B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ bool OpenCascadeKernel::convert(const taxonomy::sweep_along_curve::ptr scs, Topo
|
||||
|
||||
Handle(Geom_Surface) surface;
|
||||
if (scs->surface) {
|
||||
convert_surface(scs->surface);
|
||||
surface = convert_surface(scs->surface);
|
||||
}
|
||||
|
||||
gp_Trsf directrix;
|
||||
@@ -231,6 +231,9 @@ bool OpenCascadeKernel::convert(const taxonomy::sweep_along_curve::ptr scs, Topo
|
||||
} else {
|
||||
f0 = BRepBuilderAPI_MakeFace(w0).Face();
|
||||
f1 = BRepBuilderAPI_MakeFace(w1).Face();
|
||||
if (f0.IsNull() || f1.IsNull()) {
|
||||
return false;
|
||||
}
|
||||
mf0.reset(new BRepBuilderAPI_MakeFace(f0));
|
||||
mf1.reset(new BRepBuilderAPI_MakeFace(f1));
|
||||
}
|
||||
|
||||
@@ -55,8 +55,10 @@ double translate_to_length_measure(const IfcSchema::IfcCurve* crv, double param_
|
||||
return fabs(clothoid->ClothoidConstant()*sqrt(PI))*param_value;
|
||||
} else if (auto circ = crv->as<IfcSchema::IfcCircle>()) {
|
||||
return circ->Radius() * param_value;
|
||||
#ifdef SCHEMA_HAS_IfcPolynomialCurve
|
||||
} else if (auto poly = crv->as<IfcSchema::IfcPolynomialCurve>()) {
|
||||
return param_value;
|
||||
#endif
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported curve measure type");
|
||||
}
|
||||
@@ -75,7 +77,9 @@ double translate_if_param_value(const IfcSchema::IfcCurve* crv, IfcSchema::IfcCu
|
||||
typedef boost::mpl::vector<
|
||||
IfcSchema::IfcLine
|
||||
, IfcSchema::IfcCircle
|
||||
#ifdef SCHEMA_HAS_IfcPolynomialCurve
|
||||
, IfcSchema::IfcPolynomialCurve
|
||||
#endif
|
||||
#ifdef SCHEMA_HAS_IfcClothoid
|
||||
, IfcSchema::IfcClothoid
|
||||
#endif
|
||||
@@ -198,8 +202,12 @@ class curve_segment_evaluator {
|
||||
inst_(inst),
|
||||
length_unit_(length_unit),
|
||||
parent_curve_(inst->ParentCurve()) {
|
||||
|
||||
#ifdef SCHEMA_IfcSegment_HAS_UsingCurves
|
||||
auto composite_curves = inst->UsingCurves();
|
||||
#else
|
||||
aggregate_of<IfcSchema::IfcCompositeCurve>::ptr composite_curves;
|
||||
throw std::runtime_error("Schema not supported");
|
||||
#endif
|
||||
|
||||
// Find the next segment after inst
|
||||
const IfcSchema::IfcCurveSegment* next_inst = nullptr;
|
||||
@@ -245,17 +253,23 @@ class curve_segment_evaluator {
|
||||
|
||||
segment_type_ = is_horizontal ? ST_HORIZONTAL : is_vertical ? ST_VERTICAL : is_cant ? ST_CANT : ST_HORIZONTAL;
|
||||
|
||||
|
||||
#ifdef SCHEMA_IfcCurveSegment_HAS_SegmentStart
|
||||
start_ = translate_if_param_value(inst->ParentCurve(), inst->SegmentStart()) * length_unit;
|
||||
#else
|
||||
throw std::runtime_error("Schema not supported");
|
||||
#endif
|
||||
length_ = translate_if_param_value(inst->ParentCurve(), inst->SegmentLength()) * length_unit;
|
||||
projected_length_ = length_; // initialize with something reasonable
|
||||
|
||||
if (inst) {
|
||||
#ifdef SCHEMA_IfcCurveSegment_HAS_Placement
|
||||
curve_segment_placement_ = taxonomy::cast<taxonomy::matrix4>(mapping_->map(inst->Placement()))->ccomponents();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (next_inst) {
|
||||
#ifdef SCHEMA_IfcCurveSegment_HAS_Placement
|
||||
next_segment_placement_ = taxonomy::cast<taxonomy::matrix4>(mapping_->map(next_inst->Placement()))->ccomponents();
|
||||
#endif
|
||||
} else {
|
||||
// there is not a next segment, however IfcGradientCurve and IfcSegmentReferenceCurve have an
|
||||
// optional EndPoint which services the same purpose as the zero-length last segment.
|
||||
@@ -264,7 +278,9 @@ class curve_segment_evaluator {
|
||||
auto& cc = *(composite_curves)->begin();
|
||||
if (segment_type_ == ST_VERTICAL) {
|
||||
auto gradient_curve = cc->as<IfcSchema::IfcGradientCurve>();
|
||||
#ifdef SCHEMA_IfcCurveSegment_HAS_Placement
|
||||
end_point = gradient_curve->EndPoint();
|
||||
#endif
|
||||
} else if (segment_type_ == ST_CANT) {
|
||||
auto segmented_reference_curve = cc->as<IfcSchema::IfcSegmentedReferenceCurve>();
|
||||
end_point = segmented_reference_curve->EndPoint();
|
||||
@@ -776,9 +792,13 @@ class curve_segment_evaluator {
|
||||
auto A0 = c->ConstantTerm();
|
||||
auto A1 = c->LinearTerm();
|
||||
auto A2 = c->QuadraticTerm();
|
||||
auto A3 = c->CubicTerm();
|
||||
boost::optional<double> A4, A5, A6, A7;
|
||||
|
||||
boost::optional<double> A3, A4, A5, A6, A7;
|
||||
#ifdef SCHEMA_IfcThirdOrderPolynomialSpiral_HAS_CubicTerm
|
||||
A3 = c->CubicTerm();
|
||||
#else
|
||||
A3 = c->QubicTerm();
|
||||
#endif
|
||||
|
||||
if (segment_type_ == ST_CANT) {
|
||||
polynomial_cant_spiral(A0, A1, A2, A3, A4, A5, A6, A7);
|
||||
} else {
|
||||
@@ -832,7 +852,10 @@ class curve_segment_evaluator {
|
||||
if (segment_type_ == ST_HORIZONTAL) {
|
||||
convert_u = [](double u) { return u; };
|
||||
} else {
|
||||
auto curve_segment_placement = taxonomy::cast<taxonomy::matrix4>(mapping_->map(inst_->Placement()))->ccomponents();
|
||||
Eigen::Matrix4d curve_segment_placement;
|
||||
#ifdef SCHEMA_IfcCurveSegment_HAS_Placement
|
||||
curve_segment_placement = taxonomy::cast<taxonomy::matrix4>(mapping_->map(inst_->Placement()))->ccomponents();
|
||||
#endif
|
||||
auto csStartX = curve_segment_placement(0, 3);
|
||||
auto csStartY = curve_segment_placement(1, 3);
|
||||
auto csStartDx = curve_segment_placement(0, 0);
|
||||
@@ -1015,6 +1038,7 @@ class curve_segment_evaluator {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SCHEMA_HAS_IfcPolynomialCurve
|
||||
void operator()(const IfcSchema::IfcPolynomialCurve* pc) {
|
||||
// see https://forums.buildingsmart.org/t/ifcpolynomialcurve-clarification/4716 for discussion on IfcPolynomialCurve
|
||||
auto coeffX = pc->CoefficientsX().get_value_or(std::vector<double>());
|
||||
@@ -1159,6 +1183,7 @@ class curve_segment_evaluator {
|
||||
[](double /*u*/) -> Eigen::Matrix4d { return Eigen::Matrix4d::Identity(); });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -53,7 +53,12 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcGradientCurve* inst) {
|
||||
// Get starting position of gradient curve, which is relative to the base curve
|
||||
// The gradient curve can start before or after the start of the base curve
|
||||
auto first_segment = *(segments->begin());
|
||||
auto p = taxonomy::cast<taxonomy::matrix4>(map(first_segment->as<IfcSchema::IfcCurveSegment>()->Placement()));
|
||||
taxonomy::matrix4::ptr p;
|
||||
#ifdef SCHEMA_IfcCurveSegment_HAS_Placement
|
||||
p = taxonomy::cast<taxonomy::matrix4>(map(first_segment->as<IfcSchema::IfcCurveSegment>()->Placement()));
|
||||
#else
|
||||
throw std::runtime_error("Unsupported schema");
|
||||
#endif
|
||||
const Eigen::Matrix4d& m = p->ccomponents();
|
||||
double gradient_start = m(0, 3); // start of vertical (row 0, col 3) - "Distance Along" horizontal curve
|
||||
|
||||
|
||||
@@ -39,15 +39,29 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst
|
||||
auto first_offset_value = *(offset_values->begin());
|
||||
|
||||
auto basis_curve = inst->BasisCurve();
|
||||
auto curve = taxonomy::dcast<taxonomy::function_item>(map(basis_curve));
|
||||
if (!curve) {
|
||||
|
||||
// // IfcOffsetCurveByDistances can be based on another IfcOffsetCurveByDistances, an IfcGradientCurve, or an IfcCompositeCurve
|
||||
// // When based on IfcOffsetCurveByDistances, it creates a chain of curves that we must navigate down to the base curve.
|
||||
// // The source curve is IfcGradientCurve or IfcCompositeCurve. This loop drills down to the base curve.
|
||||
// while (auto offset_curve = basis_curve->as<IfcSchema::IfcOffsetCurveByDistances>()) {
|
||||
// basis_curve = offset_curve;
|
||||
// }
|
||||
//
|
||||
//#if defined SCHEMA_HAS_IfcGradientCurve
|
||||
// if (auto gc = basis_curve->as<IfcSchema::IfcGradientCurve>()) {
|
||||
// basis_curve = gc->BaseCurve();
|
||||
// }
|
||||
//#endif
|
||||
|
||||
auto basis_curve_fn = taxonomy::dcast<taxonomy::function_item>(map(basis_curve));
|
||||
if (!basis_curve_fn) {
|
||||
// Only implement on alignment curves
|
||||
Logger::Warning("IfcOffsetCurveByDistances is only implemented for BasisCurves curves based on taxonomy::function_item", inst);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
double start = curve->start();
|
||||
double basis_curve_length = curve->length();
|
||||
double start = basis_curve_fn->start();
|
||||
double basis_curve_length = basis_curve_fn->length();
|
||||
|
||||
taxonomy::piecewise_function::spans_t offset_spans;
|
||||
|
||||
@@ -56,6 +70,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst
|
||||
#else
|
||||
double first_distance = *first_offset_value->DistanceAlong()->as<IfcSchema::IfcLengthMeasure>();
|
||||
#endif
|
||||
first_distance *= length_unit_;
|
||||
|
||||
if (first_distance < 0.0) {
|
||||
Logger::Warning("IfcOffsetCurveByDistance first offset value is before the start of the curve.");
|
||||
@@ -83,35 +98,54 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst
|
||||
auto prev = std::prev(next);
|
||||
auto end = offset_values->end();
|
||||
for (; next != end; prev++, next++) {
|
||||
#if defined SCHEMA_HAS_IfcPointByDistanceExpression
|
||||
if ((*prev)->BasisCurve() != basis_curve || (*next)->BasisCurve() != basis_curve) {
|
||||
Logger::Error("All offsets from a IfcOffsetCurveByDistances must refer to the same BasisCurve");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined SCHEMA_HAS_IfcDistanceExpression
|
||||
double dn = (*next)->DistanceAlong();
|
||||
double dp = (*prev)->DistanceAlong();
|
||||
double dn = (*next)->DistanceAlong();
|
||||
#else
|
||||
double dn = *(*next)->DistanceAlong()->as<IfcSchema::IfcLengthMeasure>();
|
||||
double dp = *(*prev)->DistanceAlong()->as<IfcSchema::IfcLengthMeasure>();
|
||||
double dn = *(*next)->DistanceAlong()->as<IfcSchema::IfcLengthMeasure>();
|
||||
#endif
|
||||
if ((dp < 0.0 || basis_curve_length < dp)
|
||||
or
|
||||
(dn < 0.0 || basis_curve_length < dn)
|
||||
or
|
||||
(dn < dp)
|
||||
)
|
||||
dp *= length_unit_;
|
||||
dn *= length_unit_;
|
||||
|
||||
if (dn < dp) // next is before previous
|
||||
{
|
||||
Logger::Warning("IfcOffsetCurveByDistance offset value is out of bounds.");
|
||||
continue;
|
||||
}
|
||||
|
||||
double l = (dn - dp)*length_unit_;
|
||||
double l = (dn - dp);
|
||||
double yn = (*next)->OffsetLateral().get_value_or(0.0) * length_unit_;
|
||||
double yp = (*prev)->OffsetLateral().get_value_or(0.0) * length_unit_;
|
||||
double zn = (*next)->OffsetVertical().get_value_or(0.0) * length_unit_;
|
||||
double zp = (*prev)->OffsetVertical().get_value_or(0.0) * length_unit_;
|
||||
|
||||
if ( (dp < 0.0 && dn < 0.0) || (basis_curve_length < dp && basis_curve_length < dn) ) {
|
||||
// both points are either before the start of the curve or after the end of the curve. ignore them.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dp < 0.0) {
|
||||
// previous is before the start of the curve
|
||||
// compute y and z offsets at the start of the curve
|
||||
auto yp_at_start = yp - (yn - yp) * dp / l;
|
||||
auto zp_at_start = zp - (zn - zp) * dp / l;
|
||||
|
||||
dp = 0.0;
|
||||
yp = yp_at_start;
|
||||
zp = zp_at_start;
|
||||
}
|
||||
|
||||
if (basis_curve_length < dn) {
|
||||
// next is after the end of the curve
|
||||
// compute y and z offsets at the end of the curve
|
||||
auto yn_at_end = yn - (yn - yp) * (dn - basis_curve_length) / l;
|
||||
auto zn_at_end = zn - (zn - zp) * (dn - basis_curve_length) / l;
|
||||
dn = basis_curve_length;
|
||||
yn = yn_at_end;
|
||||
zn = zn_at_end;
|
||||
}
|
||||
|
||||
|
||||
auto fn = [yp, yn, zp, zn, l](double u) -> Eigen::Matrix4d {
|
||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||
@@ -128,10 +162,6 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst
|
||||
#else
|
||||
double last_distance = *(*prev)->DistanceAlong()->as<IfcSchema::IfcLengthMeasure>() * length_unit_;
|
||||
#endif
|
||||
|
||||
if (basis_curve_length < last_distance) {
|
||||
Logger::Warning("IfcOffsetCurveByDistance last offset value is after the end of the curve.");
|
||||
}
|
||||
|
||||
if (last_distance < basis_curve_length) {
|
||||
// Last offset is defined before the end of the curve so the lateral and vertical offsets
|
||||
@@ -152,7 +182,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOffsetCurveByDistances* inst
|
||||
|
||||
auto offsets = taxonomy::make<taxonomy::piecewise_function>(start,offset_spans);
|
||||
|
||||
auto fn = taxonomy::make<taxonomy::offset_function>(curve, offsets);
|
||||
auto fn = taxonomy::make<taxonomy::offset_function>(basis_curve_fn, offsets);
|
||||
return fn;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSolidHorizontal* in
|
||||
for (auto& cs : *css) {
|
||||
faces.push_back(std::move(taxonomy::cast<taxonomy::face>(map(cs))));
|
||||
}
|
||||
#ifdef SCHEMA_HAS_IfcPointByDistanceExpression
|
||||
#if defined(SCHEMA_HAS_IfcPointByDistanceExpression) && !defined(SCHEMA_IfcSectionedSurface_HAS_FixedAxisVertical)
|
||||
for (auto& csp : *csps) {
|
||||
auto pbde = csp->Location()->as<IfcSchema::IfcPointByDistanceExpression>(true);
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSectionedSurface* inst) {
|
||||
for (auto& cs : *css) {
|
||||
faces.push_back(std::move(taxonomy::cast<taxonomy::geom_item>(map(cs))));
|
||||
}
|
||||
#ifdef SCHEMA_HAS_IfcPointByDistanceExpression
|
||||
// IfcSectionedSurface::FixedAxisVertical removed in rc4, where CrossSectionPositions was IfcPointByDistanceExpression instead of IfcAxis2PlacementLinear
|
||||
#if defined(SCHEMA_HAS_IfcPointByDistanceExpression) && !defined(SCHEMA_IfcSectionedSurface_HAS_FixedAxisVertical)
|
||||
for (auto& csp : *csps) {
|
||||
auto pbde = csp->Location()->as<IfcSchema::IfcPointByDistanceExpression>(true);
|
||||
|
||||
|
||||
@@ -53,8 +53,13 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSegmentedReferenceCurve* ins
|
||||
// Get starting position of cant curve, relative to the gradient curve.
|
||||
// The cant curve can start before or after the start of the gradient curve
|
||||
auto first_segment = *(segments->begin());
|
||||
auto p = taxonomy::cast<taxonomy::matrix4>(map(first_segment->as<IfcSchema::IfcCurveSegment>()->Placement()));
|
||||
const Eigen::Matrix4d& m = p->ccomponents();
|
||||
taxonomy::matrix4::ptr p;
|
||||
#ifdef SCHEMA_IfcCurveSegment_HAS_Placement
|
||||
p = taxonomy::cast<taxonomy::matrix4>(map(first_segment->as<IfcSchema::IfcCurveSegment>()->Placement()));
|
||||
#else
|
||||
throw std::runtime_error("Unsupported schema");
|
||||
#endif
|
||||
const Eigen::Matrix4d& m = p->ccomponents();
|
||||
double cant_start = m(0, 3); // start of cant curve
|
||||
|
||||
auto cant = taxonomy::make<taxonomy::piecewise_function>(cant_start,spans);
|
||||
|
||||
@@ -593,6 +593,24 @@ typedef item const* ptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct equal_functor {
|
||||
bool operator()(taxonomy::item::ptr const& a,
|
||||
taxonomy::item::ptr const& b) const
|
||||
{
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
return !less(a, b) && !less(b, a);
|
||||
}
|
||||
};
|
||||
|
||||
struct hash_functor {
|
||||
size_t operator()(taxonomy::item::ptr const& a) const
|
||||
{
|
||||
return a->hash();
|
||||
}
|
||||
};
|
||||
|
||||
// @todo make 4d for easier multiplication
|
||||
template <size_t N>
|
||||
struct cartesian_base : public item, public eigen_base<Eigen::Vector3d> {
|
||||
@@ -934,6 +952,21 @@ typedef item const* ptr;
|
||||
auto v = std::make_tuple(static_cast<size_t>(LOOP), hash_elements(), external ? *external ? 2 : 1 : 0, closed ? *closed ? 2 : 1 : 0);
|
||||
return boost::hash<decltype(v)>{}(v);
|
||||
}
|
||||
|
||||
// nb only takes into account explicit points
|
||||
taxonomy::point3::ptr centroid() const {
|
||||
Eigen::Vector3d c(0, 0, 0);
|
||||
for (auto& e : children) {
|
||||
if (e->start.which() == 1) {
|
||||
c += boost::get<point3::ptr>(e->start)->ccomponents();
|
||||
}
|
||||
if (e->end.which() == 1) {
|
||||
c += boost::get<point3::ptr>(e->end)->ccomponents();
|
||||
}
|
||||
}
|
||||
c /= static_cast<double>(children.size());
|
||||
return make<taxonomy::point3>(c);
|
||||
}
|
||||
};
|
||||
|
||||
struct face : public collection_base<loop> {
|
||||
@@ -974,6 +1007,25 @@ typedef item const* ptr;
|
||||
auto v = std::make_tuple(static_cast<size_t>(SHELL), hash_elements(), closed ? *closed ? 2 : 1 : 0);
|
||||
return boost::hash<decltype(v)>{}(v);
|
||||
}
|
||||
|
||||
// nb only takes into account explicit points
|
||||
taxonomy::point3::ptr centroid() const {
|
||||
Eigen::Vector3d c(0, 0, 0);
|
||||
for (auto& f : children) {
|
||||
for (auto& l : f->children) {
|
||||
for (auto& e : l->children) {
|
||||
if (e->start.which() == 1) {
|
||||
c += boost::get<point3::ptr>(e->start)->ccomponents();
|
||||
}
|
||||
if (e->end.which() == 1) {
|
||||
c += boost::get<point3::ptr>(e->end)->ccomponents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c /= static_cast<double>(children.size());
|
||||
return make<taxonomy::point3>(c);
|
||||
}
|
||||
};
|
||||
|
||||
struct solid : public collection_base<shell> {
|
||||
|
||||
Reference in New Issue
Block a user