ContextPriorities

This commit is contained in:
Thomas Krijnen
2026-07-10 16:31:35 +02:00
parent 542856fb30
commit 4f69855dc0
3 changed files with 403 additions and 11 deletions
+241 -11
View File
@@ -18,7 +18,13 @@
********************************************************************************/
#define _USE_MATH_DEFINES
#include <algorithm>
#include <cctype>
#include <cmath>
#include <optional>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include "mapping.h"
@@ -26,6 +32,9 @@
#include "../../ifcparse/file.h"
#include "../../ifcparse/si_prefix.h"
#include <boost/algorithm/string.hpp>
#include <boost/spirit/home/x3.hpp>
using namespace ifcopenshell;
using namespace ifcopenshell::geometry;
using namespace IfcGeom;
@@ -222,16 +231,22 @@ std::vector<express::Base> mapping::find_openings(const express::Base& inst) {
void mapping::get_representations(std::vector<geometry_conversion_task>& tasks, std::vector<filter_t>& filters) {
std::vector<IfcSchema::IfcRepresentation> representations;
const bool has_context_ids = settings_.get<settings::ContextIds>().has();
const bool uses_priorities = !has_context_ids && settings_.get<settings::ContextPriorities>().has();
if (!settings_.get<settings::ContextIds>().has()) {
addRepresentationsFromDefaultContexts(representations);
} else {
if (has_context_ids) {
addRepresentationsFromContextIds(representations);
} else if (uses_priorities) {
addRepresentationsFromPriorities(representations);
} else {
addRepresentationsFromDefaultContexts(representations);
}
std::vector<IfcSchema::IfcRepresentation> ok_mapped_representations;
int task_index = 0;
std::set<IfcSchema::IfcProduct> products_seen;
for (auto representation : representations) {
IfcSchema::IfcRepresentationMap rmap;
@@ -279,6 +294,12 @@ void mapping::get_representations(std::vector<geometry_conversion_task>& tasks,
// reuse_ok is taken into account in products_represented_by(), but not when
// the same IfcRepresentation is directly assigned to multiple products.
for (auto& p : ifcproducts) {
if (uses_priorities) {
if (products_seen.find(p) != products_seen.end()) {
continue;
}
products_seen.insert(p);
}
geometry_conversion_task task;
task.index = task_index++;
task.representation = representation;
@@ -286,6 +307,18 @@ void mapping::get_representations(std::vector<geometry_conversion_task>& tasks,
tasks.emplace_back(task);
}
} else {
if (uses_priorities) {
ifcproducts.erase(std::remove_if(ifcproducts.begin(), ifcproducts.end(), [&](const IfcSchema::IfcProduct& p) {
return products_seen.find(p) != products_seen.end();
}),
ifcproducts.end());
for (auto& p : ifcproducts) {
products_seen.insert(p);
}
if (ifcproducts.empty()) {
continue;
}
}
geometry_conversion_task task;
task.index = task_index++;
task.representation = representation;
@@ -1300,39 +1333,225 @@ void mapping::addRepresentationsFromDefaultContexts(std::vector<IfcSchema::IfcRe
}
}
namespace {
std::string normalized_context_filter_key(std::string attribute) {
boost::trim(attribute);
auto normalized = attribute;
boost::to_lower(normalized);
normalized.erase(std::remove_if(normalized.begin(), normalized.end(), [](char c) {
return c == '-' || c == '_' || std::isspace(static_cast<unsigned char>(c));
}), normalized.end());
return normalized;
}
const std::unordered_map<std::string, std::string>& context_filter_attribute_names() {
static const auto names = [] {
std::unordered_map<std::string, std::string> result;
const auto& attributes = IfcSchema::get_schema()
.declaration_by_name("IfcGeometricRepresentationSubContext")
->as_entity()
->all_attributes();
for (const auto* attribute : attributes) {
result.emplace(normalized_context_filter_key(attribute->name()), attribute->name());
}
return result;
}();
return names;
}
std::string canonical_context_filter_attribute(std::string attribute) {
boost::trim(attribute);
const auto& names = context_filter_attribute_names();
const auto it = names.find(normalized_context_filter_key(attribute));
return it == names.end() ? attribute : it->second;
}
template <typename T, typename = void>
struct has_std_to_string : std::false_type {};
template <typename T>
struct has_std_to_string<T, std::void_t<decltype(std::to_string(std::declval<T>()))>> : std::true_type {};
struct compiled_context_filter {
std::optional<std::string> type;
std::vector<std::pair<std::string, std::string>> args;
bool matches(const std::optional<std::string>& actual, const std::string& expected) const;
bool matches(const IfcSchema::IfcGeometricRepresentationContext& context) const;
bool matches(
const IfcSchema::IfcGeometricRepresentationContext& context,
const std::string& key,
const std::string& value) const;
template <typename T>
bool matches(const T& actual, const std::string& expected) const;
};
template <typename T>
bool compiled_context_filter::matches(const T& actual, const std::string& expected) const {
using value_t = std::decay_t<T>;
if constexpr (std::is_same_v<value_t, std::string>) {
return boost::iequals(actual, expected);
} else if constexpr (std::is_same_v<value_t, enumeration_reference>) {
return boost::iequals(actual.value(), expected);
} else if constexpr (has_std_to_string<value_t>::value) {
return std::to_string(actual) == expected;
} else {
return false;
}
}
bool compiled_context_filter::matches(const std::optional<std::string>& actual, const std::string& expected) const {
return actual && matches(*actual, expected);
}
bool compiled_context_filter::matches(
const IfcSchema::IfcGeometricRepresentationContext& context,
const std::string& key,
const std::string& value) const
{
try {
attribute_value val = context.get(key);
return val.apply_visitor([&](const auto& v) {
return matches(v, value);
});
} catch (const ifcopenshell::exception&) {
return false;
}
}
bool compiled_context_filter::matches(const IfcSchema::IfcGeometricRepresentationContext& context) const {
if (type.has_value() && !matches(context.ContextIdentifier(), *type)) {
return false;
}
for (auto& [key, value] : args) {
if (!matches(context, key, value)) {
return false;
}
}
return true;
}
std::optional<compiled_context_filter> parse_context_filter(const std::string& statement) {
namespace x3 = boost::spirit::x3;
std::string type;
std::vector<std::pair<std::string, std::string>> args;
const auto text = x3::lexeme[+(x3::char_ - '[' - ']' - '=' - ',')];
std::string arg_key;
const auto key = text[([&](auto& ctx) { arg_key = x3::_attr(ctx); })];
const auto value = text[([&](auto& ctx) { args.emplace_back(arg_key, x3::_attr(ctx)); })];
const auto assignment = key >> '=' >> value;
const auto assignments = assignment % ',';
const auto wildcard = x3::lit('*')[([&](auto&) { type.clear(); })];
const auto typed = text[([&](auto& ctx) { type = x3::_attr(ctx); })];
const auto query =
(wildcard | typed) >>
-('[' >> assignments >> ']');
auto first = statement.begin();
auto last = statement.end();
if (!x3::phrase_parse(first, last, query, x3::space) || first != last) {
return std::nullopt;
}
boost::trim(type);
compiled_context_filter filter;
if (!type.empty()) {
filter.type = type;
}
for (auto& arg : args) {
boost::trim(arg.first);
boost::trim(arg.second);
filter.args.emplace_back(canonical_context_filter_attribute(arg.first), arg.second);
}
return filter;
}
} // namespace
void mapping::addRepresentationsFromPriorities(std::vector<IfcSchema::IfcRepresentation>& representations) {
std::vector<IfcSchema::IfcGeometricRepresentationContext> filtered_contexts;
std::vector<compiled_context_filter> filters;
for (auto& p : settings_.get<settings::ContextPriorities>().get()) {
// Parse statement below into a compiled_context_filter:
// - 'body' -> {'body', {}}
// - 'tesselation[targetscale=10]' -> {'tesselation', {'targetscale', '10'}}
// - '*' -> {{},{}}
if (auto filter = parse_context_filter(p)) {
filters.push_back(*filter);
} else {
logger_.warning("GEO", 325, "Ignoring invalid context filter '" + p + "'");
}
}
auto contexts = file_->instances_by_type<IfcSchema::IfcGeometricRepresentationContext>();
for (const auto& filter : filters) {
for (auto& context : contexts) {
if (filter.matches(context)) {
// Filtered contexts is in order of the priorities, so that the first context in the list is the highest priority.
filtered_contexts.push_back(context);
}
}
}
for (auto& context : filtered_contexts) {
auto reps_in_context = context.RepresentationsInContext();
representations.insert(representations.end(), reps_in_context.begin(), reps_in_context.end());
}
}
void mapping::ensureRepresentationContextCache_() {
const auto has_context_ids = settings_.get<settings::ContextIds>().has();
const auto has_context_priorities = !has_context_ids && settings_.get<settings::ContextPriorities>().has();
const auto dimensionality = settings_.get<settings::OutputDimensionality>().get();
const auto context_ids = has_context_ids ? settings_.get<settings::ContextIds>().get() : std::set<int>{};
const auto context_priorities =
has_context_priorities ? settings_.get<settings::ContextPriorities>().get() : std::vector<std::string>{};
std::lock_guard<std::mutex> guard(representation_context_cache_guard_);
if (representation_context_cache_valid_ &&
representation_context_cache_has_context_ids_ == has_context_ids &&
representation_context_cache_has_context_priorities_ == has_context_priorities &&
representation_context_cache_dimensionality_ == dimensionality &&
representation_context_cache_ids_ == context_ids) {
representation_context_cache_ids_ == context_ids &&
representation_context_cache_priorities_ == context_priorities) {
return;
}
std::vector<IfcSchema::IfcRepresentation> representations;
if (!has_context_ids) {
addRepresentationsFromDefaultContexts(representations);
} else {
if (has_context_ids) {
addRepresentationsFromContextIds(representations);
} else if (has_context_priorities) {
addRepresentationsFromPriorities(representations);
} else {
addRepresentationsFromDefaultContexts(representations);
}
std::unordered_set<uint32_t> representation_ids;
representation_ids.reserve(representations.size());
std::vector<uint32_t> representation_priority_ids;
representation_priority_ids.reserve(representations.size());
for (auto& representation : representations) {
if (representation) {
representation_ids.insert((uint32_t)representation.id());
const auto representation_id = (uint32_t)representation.id();
if (representation_ids.insert(representation_id).second) {
representation_priority_ids.push_back(representation_id);
}
}
}
representation_context_cache_ = std::move(representation_ids);
representation_context_priority_cache_ = std::move(representation_priority_ids);
representation_context_cache_ids_ = std::move(context_ids);
representation_context_cache_priorities_ = std::move(context_priorities);
representation_context_cache_dimensionality_ = dimensionality;
representation_context_cache_has_context_ids_ = has_context_ids;
representation_context_cache_has_context_priorities_ = has_context_priorities;
representation_context_cache_valid_ = true;
}
@@ -1349,9 +1568,20 @@ express::Base mapping::representation_of(const express::Base& product) {
{
std::lock_guard<std::mutex> guard(representation_context_cache_guard_);
for (auto& r : of_product) {
if (representation_context_cache_.find((uint32_t)r.id()) != representation_context_cache_.end()) {
intersection.push_back(r);
if (representation_context_cache_has_context_priorities_) {
for (auto representation_id : representation_context_priority_cache_) {
auto it = std::find_if(of_product.begin(), of_product.end(), [&](const auto& r) {
return (uint32_t)r.id() == representation_id;
});
if (it != of_product.end()) {
intersection.push_back(*it);
}
}
} else {
for (auto& r : of_product) {
if (representation_context_cache_.find((uint32_t)r.id()) != representation_context_cache_.end()) {
intersection.push_back(r);
}
}
}
}
+4
View File
@@ -33,6 +33,7 @@ namespace geometry {
void initialize_units_();
void addRepresentationsFromContextIds(std::vector<IfcSchema::IfcRepresentation>&);
void addRepresentationsFromPriorities(std::vector<IfcSchema::IfcRepresentation>&);
void addRepresentationsFromDefaultContexts(std::vector<IfcSchema::IfcRepresentation>&);
void ensureRepresentationContextCache_();
@@ -41,9 +42,12 @@ namespace geometry {
std::set<express::Base> failed_on_purpose_;
std::set<IfcSchema::IfcRepresentationMap> not_reusable_maps_;
std::unordered_set<uint32_t> representation_context_cache_;
std::vector<uint32_t> representation_context_priority_cache_;
std::set<int> representation_context_cache_ids_;
std::vector<std::string> representation_context_cache_priorities_;
settings::OutputDimensionalityTypes representation_context_cache_dimensionality_ = settings::SURFACES_AND_SOLIDS;
bool representation_context_cache_has_context_ids_ = false;
bool representation_context_cache_has_context_priorities_ = false;
bool representation_context_cache_valid_ = false;
std::mutex representation_context_cache_guard_;
@@ -1,5 +1,6 @@
#include <algorithm>
#include <string>
#include <vector>
#include <catch2/catch_test_macros.hpp>
@@ -68,6 +69,86 @@ std::size_t count_geo403_for_wall(hierarchy_helper<IfcSchema>& file, const IfcSc
return log.count("GEO403");
}
IfcSchema::IfcShapeRepresentation add_box_representation(
hierarchy_helper<IfcSchema>& file,
IfcSchema::IfcRepresentationContext context,
const std::string& identifier,
double size)
{
auto representation = file.create<IfcSchema::IfcShapeRepresentation>();
representation.setContextOfItems(context);
representation.setRepresentationIdentifier(identifier);
representation.setRepresentationType("SweptSolid");
representation.setItems(std::vector<IfcSchema::IfcRepresentationItem>{});
file.addBox(representation, size, size, size);
return representation;
}
IfcSchema::IfcRepresentation select_representation(
hierarchy_helper<IfcSchema>& file,
const IfcSchema::IfcWallStandardCase& wall,
const std::vector<std::string>& context_priorities)
{
ifcopenshell::geometry::Settings settings;
settings.set("context-priorities", std::vector<std::string>(context_priorities));
logger log;
log.output_format(logger::FMT_INMEMORY);
ifcopenshell::geometry::Converter converter(
ifcopenshell::geometry::kernels::construct(&file, "passthrough", settings), &file, settings, log);
auto selected = converter.mapping()->representation_of(wall).as<IfcSchema::IfcRepresentation>();
REQUIRE(selected);
return selected;
}
IfcSchema::IfcWallStandardCase add_wall_with_representations(
hierarchy_helper<IfcSchema>& file,
const std::vector<IfcSchema::IfcRepresentation>& representations)
{
auto wall = file.create<IfcSchema::IfcWallStandardCase>();
wall.setGlobalId(ifcopenshell::global_id());
file.addBuildingProduct(wall);
auto shape = file.create<IfcSchema::IfcProductDefinitionShape>();
shape.setRepresentations(representations);
wall.setRepresentation(shape);
return wall;
}
std::vector<ifcopenshell::geometry::geometry_conversion_task> representation_tasks(
hierarchy_helper<IfcSchema>& file,
const std::vector<std::string>& context_priorities)
{
ifcopenshell::geometry::Settings settings;
settings.set("context-priorities", std::vector<std::string>(context_priorities));
logger log;
log.output_format(logger::FMT_INMEMORY);
ifcopenshell::geometry::Converter converter(
ifcopenshell::geometry::kernels::construct(&file, "passthrough", settings), &file, settings, log);
std::vector<ifcopenshell::geometry::geometry_conversion_task> tasks;
std::vector<ifcopenshell::geometry::filter_t> filters;
converter.mapping()->get_representations(tasks, filters);
return tasks;
}
const ifcopenshell::geometry::geometry_conversion_task* task_for_product(
const std::vector<ifcopenshell::geometry::geometry_conversion_task>& tasks,
const express::Base& product)
{
for (const auto& task : tasks) {
if (std::any_of(task.products.begin(), task.products.end(), [&](const express::Base& task_product) {
return task_product.id() == product.id();
})) {
return &task;
}
}
return nullptr;
}
} // namespace
TEST_CASE("IfcGeom C++ fixture creates walls below and above the void limit", "[ifcgeom][voids]") {
@@ -79,3 +160,80 @@ TEST_CASE("IfcGeom C++ fixture creates walls below and above the void limit", "[
const auto above_limit_wall = create_wall_with_voids(above_limit_file, MAX_VOIDS + 1);
REQUIRE(count_geo403_for_wall(above_limit_file, above_limit_wall) == 1);
}
TEST_CASE("IfcGeom context priorities select representations by subcontext filter", "[ifcgeom][context]") {
hierarchy_helper<IfcSchema> file;
auto body_context = file.getRepresentationSubContext("Body", "Model");
auto tesselation_context = file.getRepresentationSubContext("Tesselation", "Model");
auto wall = file.create<IfcSchema::IfcWallStandardCase>();
wall.setGlobalId(ifcopenshell::global_id());
file.addBuildingProduct(wall);
const auto body_representation = add_box_representation(file, body_context, "Body", 1000.0);
const auto tesselation_representation = add_box_representation(file, tesselation_context, "Tesselation", 500.0);
REQUIRE(body_context.RepresentationsInContext().size() == 1);
REQUIRE(tesselation_context.RepresentationsInContext().size() == 1);
auto shape = file.create<IfcSchema::IfcProductDefinitionShape>();
shape.setRepresentations(
std::vector<IfcSchema::IfcRepresentation>{tesselation_representation, body_representation});
wall.setRepresentation(shape);
REQUIRE(select_representation(file, wall, {"body"}).id() == body_representation.id());
REQUIRE(select_representation(file, wall, {"tesselation"}).id() == tesselation_representation.id());
REQUIRE(
select_representation(file, wall, {"tesselation[context-identifier=Tesselation]"}).id() ==
tesselation_representation.id());
REQUIRE(
select_representation(file, wall, {"tesselation[targetview=MODEL_VIEW]"}).id() ==
tesselation_representation.id());
REQUIRE(
select_representation(file, wall, {"tesselation[target_view=MODEL_VIEW]"}).id() ==
tesselation_representation.id());
REQUIRE(
select_representation(file, wall, {"body", "tesselation[targetview=MODEL_VIEW]"}).id() ==
body_representation.id());
REQUIRE(
select_representation(file, wall, {"tesselation[targetview=MODEL_VIEW]", "body"}).id() ==
tesselation_representation.id());
}
TEST_CASE("IfcGeom context priorities create tasks from highest priority representation", "[ifcgeom][context]") {
hierarchy_helper<IfcSchema> file;
auto body_context = file.getRepresentationSubContext("Body", "Model");
auto tesselation_context = file.getRepresentationSubContext("Tesselation", "Model");
const auto body_representation = add_box_representation(file, body_context, "Body", 1000.0);
const auto tesselation_representation = add_box_representation(file, tesselation_context, "Tesselation", 500.0);
const auto body_only_representation = add_box_representation(file, body_context, "Body", 750.0);
const auto wall_with_both = add_wall_with_representations(
file,
std::vector<IfcSchema::IfcRepresentation>{body_representation, tesselation_representation});
const auto wall_with_body_only = add_wall_with_representations(
file,
std::vector<IfcSchema::IfcRepresentation>{body_only_representation});
auto tesselation_first_tasks = representation_tasks(file, {"tesselation[target_view=MODEL_VIEW]", "body"});
REQUIRE(tesselation_first_tasks.size() == 2);
REQUIRE(tesselation_first_tasks[0].representation.id() == tesselation_representation.id());
REQUIRE(tesselation_first_tasks[0].products.size() == 1);
REQUIRE(tesselation_first_tasks[0].products.front().id() == wall_with_both.id());
REQUIRE(tesselation_first_tasks[1].representation.id() == body_only_representation.id());
REQUIRE(tesselation_first_tasks[1].products.size() == 1);
REQUIRE(tesselation_first_tasks[1].products.front().id() == wall_with_body_only.id());
auto body_first_tasks = representation_tasks(file, {"body", "tesselation[targetview=MODEL_VIEW]"});
REQUIRE(body_first_tasks.size() == 2);
const auto* wall_with_both_task = task_for_product(body_first_tasks, wall_with_both);
REQUIRE(wall_with_both_task != nullptr);
REQUIRE(wall_with_both_task->representation.id() == body_representation.id());
const auto* wall_with_body_only_task = task_for_product(body_first_tasks, wall_with_body_only);
REQUIRE(wall_with_body_only_task != nullptr);
REQUIRE(wall_with_body_only_task->representation.id() == body_only_representation.id());
}