settings: DeferProcessingFirstElement, MaxOffset, MaxOffsetDeviation, ApplyOffset; taxonomy: centroid funcs; iterator get_tasks + items() funcs; geom.map_shape()

This commit is contained in:
Thomas Krijnen
2025-07-01 21:10:24 +02:00
parent 6c4e4e5f91
commit bb329affb8
6 changed files with 306 additions and 8 deletions
+22 -1
View File
@@ -441,6 +441,27 @@ namespace ifcopenshell {
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 {
@@ -616,7 +637,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, PermissiveShapeReuse, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges, OcctNoCleanTriangulation, CacheShapes>
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, PermissiveShapeReuse, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges, OcctNoCleanTriangulation, CacheShapes, DeferProcessingFirstElement, MaxOffset, MaxOffsetDeviation, ApplyOffset>
>
{};
}
+184 -2
View File
@@ -53,7 +53,7 @@ bool IfcGeom::Iterator::initialize() {
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<const IfcUtil::IfcBaseEntity*, ifcopenshell::geometry::taxonomy::matrix4::ptr>>> folded;
std::vector<std::pair<IfcUtil::IfcBaseEntity*, ifcopenshell::geometry::taxonomy::matrix4::ptr>>> folded;
for (auto& r : tasks_) {
auto i = r.item;
@@ -94,6 +94,10 @@ bool IfcGeom::Iterator::initialize() {
}
}
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();
@@ -136,7 +140,7 @@ bool IfcGeom::Iterator::initialize() {
if (tasks_.size() == 0) {
Logger::Warning("No representations encountered, aborting");
initialization_outcome_.reset(false);
} else {
} else if (!settings_.get<ifcopenshell::geometry::settings::DeferProcessingFirstElement>().get()) {
task_iterator_ = tasks_.begin();
@@ -153,6 +157,8 @@ bool IfcGeom::Iterator::initialize() {
} else {
initialization_outcome_ = create();
}
} else {
initialization_outcome_.reset(true);
}
return *initialization_outcome_;
@@ -505,6 +511,10 @@ IfcGeom::Element* IfcGeom::Iterator::get()
throw std::runtime_error("Iterator not initialized");
}
if (settings_.get<ifcopenshell::geometry::settings::DeferProcessingFirstElement>().get() && !task_result_ptr_initialized) {
throw std::runtime_error("No elements processed");
}
auto ret = *task_result_iterator_;
// If we want to organize the element considering their hierarchy
@@ -625,6 +635,178 @@ const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create() {
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;
+27 -2
View File
@@ -91,7 +91,7 @@ namespace IfcGeom {
// 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;
@@ -209,7 +209,7 @@ namespace IfcGeom {
void log_timepoints() const;
/// @todo public/private sections all over the place: move all public to the beginning of the class
ifcopenshell::geometry::taxonomy::direction3::ptr remove_offset_();
public:
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)
@@ -262,6 +262,31 @@ namespace IfcGeom {
void set_cache(GeometrySerializer* cache) { cache_ = cache; }
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);
}
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.
+34
View File
@@ -952,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> {
@@ -992,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> {
@@ -299,8 +299,11 @@ class iterator(ifcopenshell_wrapper.Iterator):
):
self.settings = settings
if isinstance(file_or_filename, file):
self.file = file
file_or_filename = file_or_filename.wrapped_data
else:
# @todo?
self.file = None
# Makes sure people are able to use python's platform agnostic paths
file_or_filename = os.path.abspath(file_or_filename)
@@ -346,6 +349,9 @@ class iterator(ifcopenshell_wrapper.Iterator):
if not self.next():
break
def get_task_products(self):
return entity_instance.wrap_value(ifcopenshell_wrapper.Iterator.get_task_products(self), self.file)
ClashType = Literal["protrusion", "pierce", "collision", "clearance"]
CLASH_TYPE_ITEMS = ("protrusion", "pierce", "collision", "clearance")
@@ -453,9 +459,7 @@ def create_shape(
geometry_library: GEOMETRY_LIBRARY = "opencascade",
) -> Union[ShapeType, ShapeElementType, ifcopenshell_wrapper.Transformation, utils.shape_tuple, TopoDS.TopoDS_Shape]:
"""
Return a geometric representation from STEP-based IFCREPRESENTATIONSHAPE
or
Return an OpenCASCADE BRep if 'use-python-opencascade' is True
Returns a geometric interpretation of the IFC entity instance
Note that in Python, you must store a reference to the element returned by this function to prevent garbage
collection when you access its children. See #1124.
@@ -504,6 +508,20 @@ def create_shape(
)
def map_shape(settings: settings, inst: entity_instance) -> ifcopenshell_wrapper.item:
"""
Returns an interpretation of the geometry encoded as per IfcOpenShell's taxonomy layer.
In many cases this is somewhat equivalent to the raw IFC data (but schema-agnostic in C++), but
in other cases such as IfcParameterizedProfileDef the returned item is the equivalent
of an explicit composite curve.
>>> point = ifc_file.by_type('IfcCartesianPoint')[0]
>>> ifcopenshell.geom.map_shape(ifcopenshell.geom.settings(), point).components
(0.0, 0.0, 0.0)
"""
return ifcopenshell_wrapper.map_shape(settings, inst.wrapped_data)
@overload
def consume_iterator(it: iterator, with_progress: Literal[False] = False) -> Generator[IteratorOutput, None, None]: ...
@overload
+18
View File
@@ -6,6 +6,16 @@
}
}
%typemap(out) aggregate_of_aggregate_of_instance::ptr {
const unsigned size = $1 ? $1->size() : 0;
$result = PyTuple_New(size);
for (unsigned i = 0; i < size; ++i) {
const auto& r_i = *(result->begin() + i);
PyTuple_SetItem($result, i, pythonize_vector(r_i));
}
}
%typemap(out) IfcUtil::ArgumentType {
$result = SWIG_Python_str_FromChar(IfcUtil::ArgumentTypeToString($1));
}
@@ -122,6 +132,14 @@ CREATE_VECTOR_TYPEMAP_OUT(IfcGeom::ConversionResultShape *)
}
};
%typemap(out) std::vector<item_name::ptr> {
const auto& v = (std::vector<item_name::ptr>) $1;
$result = PyTuple_New(v.size());
for (int i = 0; i < v.size(); ++i) {
PyTuple_SetItem($result, i, item_to_pyobject(v[i]));
}
};
%typemap(out) const item_name::ptr& {
$result = item_to_pyobject(*$1);
};