mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-30 12:09:51 +00:00
ifcviewer: stream geometry per prioritised context
Port get_prioritised_contexts from ifcopenshell.util.representation to C++ and have GeometryStreamer iterate one context at a time, mirroring bonsai's create_generic_element loop. Each pass sets context-ids to a single context id; elements that yield geometry are dropped from the include set so lower-priority contexts only pick up leftovers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -246,6 +246,104 @@ static MeshChunk buildMeshChunk(uint32_t model_id,
|
|||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Port of ifcopenshell.util.representation.get_prioritised_contexts: rank every
|
||||||
|
// IfcGeometricRepresentationContext (and SubContext) by (ContextType,
|
||||||
|
// ContextIdentifier, TargetView, TargetScale) — tuple comparison, descending —
|
||||||
|
// and return the resulting context ids high-priority first. Used to drive a
|
||||||
|
// pass-per-context iteration in the streamer (mirrors bonsai's
|
||||||
|
// create_generic_element loop), so each element is rendered from its
|
||||||
|
// preferred representation if available, falling back to lower-priority
|
||||||
|
// contexts only when the preferred one is missing.
|
||||||
|
static std::vector<int> prioritisedContextIds(ifcopenshell::file* ifc_file) {
|
||||||
|
static const std::vector<std::string> type_order = {
|
||||||
|
// "Annotation" accommodates broken Revit files that put 3D bodies
|
||||||
|
// under a context typed Annotation. See revit-ifc#187.
|
||||||
|
"Model", "Plan", "Annotation",
|
||||||
|
};
|
||||||
|
static const std::vector<std::string> identifier_order = {
|
||||||
|
"Body", "Body-FallBack", "Facetation", "FootPrint", "Profile",
|
||||||
|
"Surface", "Reference", "Axis", "Clearance", "Box", "Lighting",
|
||||||
|
"Annotation", "CoG",
|
||||||
|
};
|
||||||
|
static const std::vector<std::string> target_view_order = {
|
||||||
|
"MODEL_VIEW", "PLAN_VIEW", "REFLECTED_PLAN_VIEW", "ELEVATION_VIEW",
|
||||||
|
"SECTION_VIEW", "GRAPH_VIEW", "SKETCH_VIEW", "USERDEFINED",
|
||||||
|
"NOTDEFINED",
|
||||||
|
};
|
||||||
|
|
||||||
|
auto rank = [](const std::vector<std::string>& order,
|
||||||
|
const std::string& value) -> int {
|
||||||
|
if (value.empty()) return 0;
|
||||||
|
auto it = std::find(order.begin(), order.end(), value);
|
||||||
|
if (it == order.end()) return 0;
|
||||||
|
return static_cast<int>(order.size() - (it - order.begin()));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContextInfo {
|
||||||
|
int id;
|
||||||
|
int type_priority;
|
||||||
|
int identifier_priority;
|
||||||
|
int target_view_priority;
|
||||||
|
double target_scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ContextInfo> infos;
|
||||||
|
auto contexts =
|
||||||
|
ifc_file->instances_by_type("IfcGeometricRepresentationContext");
|
||||||
|
infos.reserve(contexts.size());
|
||||||
|
|
||||||
|
for (const auto& ctx : contexts) {
|
||||||
|
ContextInfo info{};
|
||||||
|
info.id = ctx.id();
|
||||||
|
|
||||||
|
const auto entity = ctx.as<express::Entity>();
|
||||||
|
const std::string ctype =
|
||||||
|
entity.get_value<std::string>("ContextType", "");
|
||||||
|
const std::string cident =
|
||||||
|
entity.get_value<std::string>("ContextIdentifier", "");
|
||||||
|
info.type_priority = rank(type_order, ctype);
|
||||||
|
info.identifier_priority = rank(identifier_order, cident);
|
||||||
|
|
||||||
|
// TargetView and TargetScale only exist on
|
||||||
|
// IfcGeometricRepresentationSubContext; get() throws on the parent
|
||||||
|
// type, so gate by declaration before reading.
|
||||||
|
if (ctx.declaration().is("IfcGeometricRepresentationSubContext")) {
|
||||||
|
try {
|
||||||
|
auto tv = entity.get("TargetView");
|
||||||
|
if (!tv.isNull()) {
|
||||||
|
enumeration_reference er = tv;
|
||||||
|
info.target_view_priority =
|
||||||
|
rank(target_view_order, er.value());
|
||||||
|
}
|
||||||
|
} catch (...) {}
|
||||||
|
try {
|
||||||
|
auto ts = entity.get("TargetScale");
|
||||||
|
if (!ts.isNull()) {
|
||||||
|
info.target_scale = static_cast<double>(ts);
|
||||||
|
}
|
||||||
|
} catch (...) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
infos.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(infos.begin(), infos.end(),
|
||||||
|
[](const ContextInfo& a, const ContextInfo& b) {
|
||||||
|
if (a.type_priority != b.type_priority)
|
||||||
|
return a.type_priority > b.type_priority;
|
||||||
|
if (a.identifier_priority != b.identifier_priority)
|
||||||
|
return a.identifier_priority > b.identifier_priority;
|
||||||
|
if (a.target_view_priority != b.target_view_priority)
|
||||||
|
return a.target_view_priority > b.target_view_priority;
|
||||||
|
return a.target_scale > b.target_scale;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<int> result;
|
||||||
|
result.reserve(infos.size());
|
||||||
|
for (const auto& i : infos) result.push_back(i.id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the world-space AABB by transforming the 8 corners of the local
|
// Compute the world-space AABB by transforming the 8 corners of the local
|
||||||
// AABB through the column-major 4x4 transform.
|
// AABB through the column-major 4x4 transform.
|
||||||
static void worldAabbFromLocal(const float local_min[3],
|
static void worldAabbFromLocal(const float local_min[3],
|
||||||
@@ -375,130 +473,178 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
|
|||||||
? 100
|
? 100
|
||||||
: static_cast<int>(100.0 * net_ids.size() / total_count + 0.5);
|
: static_cast<int>(100.0 * net_ids.size() / total_count + 0.5);
|
||||||
|
|
||||||
|
// High-priority context first, so each element gets its preferred
|
||||||
|
// representation; lower-priority contexts only pick up elements the
|
||||||
|
// earlier passes didn't yield geometry for. Mirrors bonsai's
|
||||||
|
// create_generic_element loop over context_settings.
|
||||||
|
const std::vector<int> prioritised_contexts =
|
||||||
|
prioritisedContextIds(ifc_file_.get());
|
||||||
|
|
||||||
auto run_pass = [&](const std::set<int>& include_ids,
|
auto run_pass = [&](const std::set<int>& include_ids,
|
||||||
bool is_gross,
|
bool is_gross,
|
||||||
int progress_lo,
|
int progress_lo,
|
||||||
int progress_hi) -> bool {
|
int progress_hi) -> bool {
|
||||||
if (include_ids.empty()) return true;
|
if (include_ids.empty()) return true;
|
||||||
|
|
||||||
ifcopenshell::geometry::Settings pass_settings = settings;
|
ifcopenshell::geometry::Settings base_settings = settings;
|
||||||
if (is_gross) {
|
if (is_gross) {
|
||||||
pass_settings.set("disable-opening-subtractions", true);
|
base_settings.set("disable-opening-subtractions", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ifcopenshell::geometry::filter_t> filters;
|
// Elements that haven't yet produced geometry from any context.
|
||||||
IfcGeom::instance_id_filter idf{
|
std::set<int> remaining = include_ids;
|
||||||
/*include=*/true, /*traverse=*/false, include_ids};
|
|
||||||
filters.push_back(idf);
|
|
||||||
|
|
||||||
std::unique_ptr<IfcGeom::Iterator> iterator;
|
auto run_iterator = [&](ifcopenshell::geometry::Settings& iter_settings,
|
||||||
try {
|
int sub_lo, int sub_hi) -> bool {
|
||||||
const std::string geometry_library =
|
if (remaining.empty()) return true;
|
||||||
AppSettings::instance().geometryLibrary().toStdString();
|
|
||||||
auto kernel = ifcopenshell::geometry::kernels::construct(
|
|
||||||
ifc_file_.get(), geometry_library, pass_settings);
|
|
||||||
iterator = std::make_unique<IfcGeom::Iterator>(
|
|
||||||
std::move(kernel), pass_settings, ifc_file_.get(),
|
|
||||||
filters, effective_threads);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
emit errorOccurred(QString("Failed to create geometry iterator: %1").arg(e.what()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!iterator->initialize()) {
|
std::vector<ifcopenshell::geometry::filter_t> filters;
|
||||||
// Empty pass — no geometry survived for these ids. Still
|
IfcGeom::instance_id_filter idf{
|
||||||
// advance progress to the upper bound so the bar doesn't stall.
|
/*include=*/true, /*traverse=*/false, remaining};
|
||||||
progress_ = progress_hi;
|
filters.push_back(idf);
|
||||||
emit progressChanged(progress_hi);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int last_progress = progress_lo;
|
std::unique_ptr<IfcGeom::Iterator> iterator;
|
||||||
|
try {
|
||||||
do {
|
const std::string geometry_library =
|
||||||
if (cancel_requested_.load()) break;
|
AppSettings::instance().geometryLibrary().toStdString();
|
||||||
|
auto kernel = ifcopenshell::geometry::kernels::construct(
|
||||||
const IfcGeom::Element* elem = iterator->get();
|
ifc_file_.get(), geometry_library, iter_settings);
|
||||||
if (!elem) continue;
|
iterator = std::make_unique<IfcGeom::Iterator>(
|
||||||
|
std::move(kernel), iter_settings, ifc_file_.get(),
|
||||||
const auto* tri_elem = dynamic_cast<const IfcGeom::TriangulationElement*>(elem);
|
filters, effective_threads);
|
||||||
if (!tri_elem) continue;
|
} catch (const std::exception& e) {
|
||||||
|
emit errorOccurred(QString("Failed to create geometry iterator: %1").arg(e.what()));
|
||||||
const auto& geom = tri_elem->geometry();
|
return false;
|
||||||
if (geom.verts().empty() || geom.faces().empty()) continue;
|
|
||||||
|
|
||||||
uint32_t object_id = next_object_id_++;
|
|
||||||
|
|
||||||
ElementInfo info;
|
|
||||||
info.object_id = object_id;
|
|
||||||
info.model_id = model_id_;
|
|
||||||
info.ifc_id = tri_elem->id();
|
|
||||||
info.guid = tri_elem->guid();
|
|
||||||
info.name = tri_elem->name();
|
|
||||||
info.type = tri_elem->type();
|
|
||||||
info.parent_id = tri_elem->parent_id();
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(elements_mutex_);
|
|
||||||
pending_elements_.push_back(std::move(info));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& geom_id = geom.id();
|
if (!iterator->initialize()) {
|
||||||
uint32_t local_mesh_id;
|
// No geometry survived this context for the remaining ids.
|
||||||
bool first_sight = false;
|
// Still advance progress so the bar doesn't stall.
|
||||||
if (geom_id.empty()) {
|
progress_ = sub_hi;
|
||||||
local_mesh_id = total_meshes++;
|
emit progressChanged(sub_hi);
|
||||||
first_sight = true;
|
return true;
|
||||||
} else {
|
}
|
||||||
auto it = geom_to_local_mesh_id.find(geom_id);
|
|
||||||
if (it == geom_to_local_mesh_id.end()) {
|
int last_progress = sub_lo;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (cancel_requested_.load()) break;
|
||||||
|
|
||||||
|
const IfcGeom::Element* elem = iterator->get();
|
||||||
|
if (!elem) continue;
|
||||||
|
|
||||||
|
const auto* tri_elem = dynamic_cast<const IfcGeom::TriangulationElement*>(elem);
|
||||||
|
if (!tri_elem) continue;
|
||||||
|
|
||||||
|
const auto& geom = tri_elem->geometry();
|
||||||
|
if (geom.verts().empty() || geom.faces().empty()) continue;
|
||||||
|
|
||||||
|
// Once an element yields geometry from this context, drop it
|
||||||
|
// from the remaining set so lower-priority contexts don't
|
||||||
|
// re-render it.
|
||||||
|
remaining.erase(tri_elem->id());
|
||||||
|
|
||||||
|
uint32_t object_id = next_object_id_++;
|
||||||
|
|
||||||
|
ElementInfo info;
|
||||||
|
info.object_id = object_id;
|
||||||
|
info.model_id = model_id_;
|
||||||
|
info.ifc_id = tri_elem->id();
|
||||||
|
info.guid = tri_elem->guid();
|
||||||
|
info.name = tri_elem->name();
|
||||||
|
info.type = tri_elem->type();
|
||||||
|
info.parent_id = tri_elem->parent_id();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(elements_mutex_);
|
||||||
|
pending_elements_.push_back(std::move(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& geom_id = geom.id();
|
||||||
|
uint32_t local_mesh_id;
|
||||||
|
bool first_sight = false;
|
||||||
|
if (geom_id.empty()) {
|
||||||
local_mesh_id = total_meshes++;
|
local_mesh_id = total_meshes++;
|
||||||
geom_to_local_mesh_id.emplace(geom_id, local_mesh_id);
|
|
||||||
first_sight = true;
|
first_sight = true;
|
||||||
} else {
|
} else {
|
||||||
local_mesh_id = it->second;
|
auto it = geom_to_local_mesh_id.find(geom_id);
|
||||||
|
if (it == geom_to_local_mesh_id.end()) {
|
||||||
|
local_mesh_id = total_meshes++;
|
||||||
|
geom_to_local_mesh_id.emplace(geom_id, local_mesh_id);
|
||||||
|
first_sight = true;
|
||||||
|
} else {
|
||||||
|
local_mesh_id = it->second;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (first_sight) {
|
if (first_sight) {
|
||||||
MeshChunk mesh_chunk = buildMeshChunk(model_id_, local_mesh_id, tri_elem);
|
MeshChunk mesh_chunk = buildMeshChunk(model_id_, local_mesh_id, tri_elem);
|
||||||
MeshAabb ma;
|
MeshAabb ma;
|
||||||
for (int a = 0; a < 3; ++a) {
|
for (int a = 0; a < 3; ++a) {
|
||||||
ma.lmin[a] = mesh_chunk.local_aabb_min[a];
|
ma.lmin[a] = mesh_chunk.local_aabb_min[a];
|
||||||
ma.lmax[a] = mesh_chunk.local_aabb_max[a];
|
ma.lmax[a] = mesh_chunk.local_aabb_max[a];
|
||||||
|
}
|
||||||
|
if (mesh_aabbs.size() <= local_mesh_id) mesh_aabbs.resize(local_mesh_id + 1);
|
||||||
|
mesh_aabbs[local_mesh_id] = ma;
|
||||||
|
if (!mesh_chunk.indices.empty()) {
|
||||||
|
emit meshReady(std::move(mesh_chunk));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (mesh_aabbs.size() <= local_mesh_id) mesh_aabbs.resize(local_mesh_id + 1);
|
|
||||||
mesh_aabbs[local_mesh_id] = ma;
|
const Eigen::Matrix4d& mat_d = tri_elem->transformation().data()->ccomponents();
|
||||||
if (!mesh_chunk.indices.empty()) {
|
InstanceChunk inst;
|
||||||
emit meshReady(std::move(mesh_chunk));
|
inst.model_id = model_id_;
|
||||||
|
inst.local_mesh_id = local_mesh_id;
|
||||||
|
inst.object_id = object_id;
|
||||||
|
inst.color_override_rgba8 = 0;
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
inst.transform[i] = static_cast<float>(mat_d.data()[i]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const Eigen::Matrix4d& mat_d = tri_elem->transformation().data()->ccomponents();
|
const MeshAabb& ma = mesh_aabbs[local_mesh_id];
|
||||||
InstanceChunk inst;
|
worldAabbFromLocal(ma.lmin, ma.lmax, inst.transform,
|
||||||
inst.model_id = model_id_;
|
inst.world_aabb_min, inst.world_aabb_max);
|
||||||
inst.local_mesh_id = local_mesh_id;
|
|
||||||
inst.object_id = object_id;
|
|
||||||
inst.color_override_rgba8 = 0;
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
|
||||||
inst.transform[i] = static_cast<float>(mat_d.data()[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const MeshAabb& ma = mesh_aabbs[local_mesh_id];
|
emit instanceReady(std::move(inst));
|
||||||
worldAabbFromLocal(ma.lmin, ma.lmax, inst.transform,
|
total_shapes++;
|
||||||
inst.world_aabb_min, inst.world_aabb_max);
|
|
||||||
|
|
||||||
emit instanceReady(std::move(inst));
|
const int p = sub_lo +
|
||||||
total_shapes++;
|
(iterator->progress() * (sub_hi - sub_lo)) / 100;
|
||||||
|
if (p != last_progress) {
|
||||||
|
last_progress = p;
|
||||||
|
progress_ = p;
|
||||||
|
emit progressChanged(p);
|
||||||
|
}
|
||||||
|
} while (iterator->next());
|
||||||
|
|
||||||
const int p = progress_lo +
|
return true;
|
||||||
(iterator->progress() * (progress_hi - progress_lo)) / 100;
|
};
|
||||||
if (p != last_progress) {
|
|
||||||
last_progress = p;
|
|
||||||
progress_ = p;
|
|
||||||
emit progressChanged(p);
|
|
||||||
}
|
|
||||||
} while (iterator->next());
|
|
||||||
|
|
||||||
|
if (prioritised_contexts.empty()) {
|
||||||
|
// No IfcGeometricRepresentationContext entities — fall back to
|
||||||
|
// a single iterator pass without context-id filtering.
|
||||||
|
return run_iterator(base_settings, progress_lo, progress_hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int range = progress_hi - progress_lo;
|
||||||
|
const int n = static_cast<int>(prioritised_contexts.size());
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
if (cancel_requested_.load()) break;
|
||||||
|
if (remaining.empty()) break;
|
||||||
|
|
||||||
|
ifcopenshell::geometry::Settings iter_settings = base_settings;
|
||||||
|
iter_settings.set("context-ids",
|
||||||
|
std::set<int>{ prioritised_contexts[i] });
|
||||||
|
|
||||||
|
const int sub_lo = progress_lo + (range * i) / n;
|
||||||
|
const int sub_hi = (i + 1 == n)
|
||||||
|
? progress_hi
|
||||||
|
: progress_lo + (range * (i + 1)) / n;
|
||||||
|
|
||||||
|
if (!run_iterator(iter_settings, sub_lo, sub_hi)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress_ = progress_hi;
|
||||||
|
emit progressChanged(progress_hi);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user