Merge branch 'v0.8.0' into feat/IfcFixedReferenceSweptAreaSolid

This commit is contained in:
Kristoffer Andersen
2025-08-26 14:13:53 +02:00
committed by GitHub
1095 changed files with 34453 additions and 12496 deletions
+23 -3
View File
@@ -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) {
+11 -8
View File
@@ -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;
}
+62 -12
View File
@@ -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>
>
{};
}
+45 -10
View File
@@ -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
+852
View File
@@ -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
View File
@@ -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();
};
}
+1 -1
View File
@@ -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;
+10
View File
@@ -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?
+17 -13
View File
@@ -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;
-2
View File
@@ -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;
}
+2 -1
View File
@@ -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));
}
+32 -7
View File
@@ -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
+6 -1
View File
@@ -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);
+2 -1
View File
@@ -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);
+52
View File
@@ -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> {