mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
ifcviewer: auto-guess FederatedFalseOrigin on first model added
When the user adds a model into a fresh, untitled federation that still
has the default (0,0,0, no rotation) FederatedFalseOrigin, derive an
origin from the first instance's placement_transformation (lifted
through CoordinateOperation when enabled) and the helmert grid-north
baked into ModelGeoref::coordinate_operation_meters. Multi-file batches
naturally settle: whichever load finishes first anchors the federation,
the rest see a non-default origin and skip. Saved .ifcfeds keep their
authoritative origin.
Adds Placement.{h,cpp} (port of util/placement.py — a2p,
get_axis2placement, get_local_placement) so Geolocation no longer needs
its own anonymous getAxis2Placement, and xaxis2angleDeg in Geolocation
mirroring util/geolocation.xaxis2angle.
SceneLoader captures the first instance's placement_transformation from
either the sidecar's InstanceCpu[0] or the streamer's first
InstanceChunk, so the guess works on both load paths without re-reading
the IFC.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -594,6 +594,30 @@ void MainWindow::applyFederatedFalseOriginToViewport() {
|
|||||||
viewport_->setFederatedFalseOrigin(M);
|
viewport_->setFederatedFalseOrigin(M);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::maybeGuessFederatedFalseOrigin(uint32_t mid) {
|
||||||
|
// Only auto-guess for untitled federations. A saved .ifcfed carries its
|
||||||
|
// authoritative origin (even if that happens to be the default), so we
|
||||||
|
// never silently overwrite it when the user re-adds a model.
|
||||||
|
if (!federation_->filePath().isEmpty()) return;
|
||||||
|
|
||||||
|
// Skip once the origin is non-default — either the user edited it, a
|
||||||
|
// previous batch already guessed, or a load-from-file populated it.
|
||||||
|
// For a multi-file batch this means whichever model finishes first
|
||||||
|
// anchors the federation; the rest see a non-default origin and skip.
|
||||||
|
const FederatedFalseOrigin& cur = federation_->federatedFalseOrigin();
|
||||||
|
const FederatedFalseOrigin def;
|
||||||
|
if (cur.xyz != def.xyz || cur.rz_deg != def.rz_deg) return;
|
||||||
|
|
||||||
|
const Eigen::Matrix4d* placement = loader_->firstPlacement(mid);
|
||||||
|
const ModelGeoref* gr = loader_->modelGeoref(mid);
|
||||||
|
if (placement == nullptr || gr == nullptr) return;
|
||||||
|
|
||||||
|
const FederatedFalseOrigin guess = guessFederatedFalseOrigin(
|
||||||
|
*placement, *gr, federation_->config(),
|
||||||
|
AppSettings::instance().applyCoordinateOperation());
|
||||||
|
federation_->setFederatedFalseOrigin(guess);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) {
|
void MainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) {
|
||||||
progress_bar_->setVisible(false);
|
progress_bar_->setVisible(false);
|
||||||
status_label_->setText(QString("%1 elements across %2 model(s) — loaded from cache in %3")
|
status_label_->setText(QString("%1 elements across %2 model(s) — loaded from cache in %3")
|
||||||
@@ -606,6 +630,7 @@ void MainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) {
|
|||||||
// ModelTransformation immediately rather than waiting for the
|
// ModelTransformation immediately rather than waiting for the
|
||||||
// (possibly never-arriving) data-source load.
|
// (possibly never-arriving) data-source load.
|
||||||
applyCoordinateOperationToViewport(mid);
|
applyCoordinateOperationToViewport(mid);
|
||||||
|
maybeGuessFederatedFalseOrigin(mid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onStreamedElementsReady(uint32_t /*mid*/, std::vector<ElementInfo> elements) {
|
void MainWindow::onStreamedElementsReady(uint32_t /*mid*/, std::vector<ElementInfo> elements) {
|
||||||
@@ -714,9 +739,8 @@ void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
|
|||||||
.arg(loader_->modelCount())
|
.arg(loader_->modelCount())
|
||||||
.arg(formatElapsed(elapsed_ms)));
|
.arg(formatElapsed(elapsed_ms)));
|
||||||
|
|
||||||
// Stream path: the IFC is owned by the streamer, so georef is
|
|
||||||
// computable now. (Sidecar-hit models defer to onDataSourceReady.)
|
|
||||||
applyCoordinateOperationToViewport(mid);
|
applyCoordinateOperationToViewport(mid);
|
||||||
|
maybeGuessFederatedFalseOrigin(mid);
|
||||||
|
|
||||||
writeSidecarForModel(mid);
|
writeSidecarForModel(mid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,15 @@ private:
|
|||||||
// Push the federation-wide FederatedFalseOrigin (stage 3) matrix to
|
// Push the federation-wide FederatedFalseOrigin (stage 3) matrix to
|
||||||
// the viewport. Affects every loaded model.
|
// the viewport. Affects every loaded model.
|
||||||
void applyFederatedFalseOriginToViewport();
|
void applyFederatedFalseOriginToViewport();
|
||||||
|
|
||||||
|
// For an untitled federation whose FederatedFalseOrigin is still at
|
||||||
|
// its default, derive a sensible origin from `mid`'s first instance
|
||||||
|
// placement + georef and push it via Federation. Idempotent: a
|
||||||
|
// non-default origin (user-edited, already guessed by a sibling load
|
||||||
|
// in the same batch, or loaded from a saved .ifcfed) is left
|
||||||
|
// untouched, so multi-file batches naturally anchor on whichever
|
||||||
|
// model finishes first.
|
||||||
|
void maybeGuessFederatedFalseOrigin(uint32_t mid);
|
||||||
QString formatElapsed(qint64 ms) const;
|
QString formatElapsed(qint64 ms) const;
|
||||||
|
|
||||||
ViewportWindow* viewport_ = nullptr;
|
ViewportWindow* viewport_ = nullptr;
|
||||||
|
|||||||
@@ -135,6 +135,36 @@ ModelGeoref computeModelGeoref(ifcopenshell::file* ifc_file) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FederatedFalseOrigin
|
||||||
|
guessFederatedFalseOrigin(const Eigen::Matrix4d& first_placement_meters,
|
||||||
|
const ModelGeoref& georef,
|
||||||
|
const FederationConfig& fed_cfg,
|
||||||
|
bool apply_coordinate_operation) {
|
||||||
|
Eigen::Vector3d t_m = first_placement_meters.block<3, 1>(0, 3);
|
||||||
|
|
||||||
|
const bool use_coord_op =
|
||||||
|
apply_coordinate_operation && georef.has_coordinate_operation;
|
||||||
|
if (use_coord_op) {
|
||||||
|
const Eigen::Vector4d th(t_m.x(), t_m.y(), t_m.z(), 1.0);
|
||||||
|
t_m = (georef.coordinate_operation_meters * th).head<3>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const double u_fed = federationUnitToMeters(fed_cfg);
|
||||||
|
const double u_fed_inv = (u_fed != 0.0) ? (1.0 / u_fed) : 1.0;
|
||||||
|
|
||||||
|
FederatedFalseOrigin out;
|
||||||
|
out.xyz = t_m * u_fed_inv;
|
||||||
|
|
||||||
|
// Rotation: helmert grid-north baked into coordinate_operation_meters.
|
||||||
|
// helmertMetersFromParameters built that block as R_z(theta)·diag(fx,fy,fz)
|
||||||
|
// with theta = atan2(xao, xaa); xaxis2angle is `-theta` in degrees.
|
||||||
|
if (use_coord_op) {
|
||||||
|
const Eigen::Matrix4d& M = georef.coordinate_operation_meters;
|
||||||
|
out.rz_deg = xaxis2angleDeg(M(0, 0), M(1, 0));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
Eigen::Matrix4d composeModelTransformation(const ModelTransformation& xf,
|
Eigen::Matrix4d composeModelTransformation(const ModelTransformation& xf,
|
||||||
const FederationConfig& fed_cfg,
|
const FederationConfig& fed_cfg,
|
||||||
const ModelUnits& model_units,
|
const ModelUnits& model_units,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -125,6 +126,27 @@ struct ModelGeoref {
|
|||||||
// caller doesn't want to cache.
|
// caller doesn't want to cache.
|
||||||
ModelGeoref computeModelGeoref(ifcopenshell::file* ifc_file);
|
ModelGeoref computeModelGeoref(ifcopenshell::file* ifc_file);
|
||||||
|
|
||||||
|
// Build a FederatedFalseOrigin guess so that a model lands near the
|
||||||
|
// federation origin instead of out at its surveyor coordinates. Designed
|
||||||
|
// to work without an open IFC file so it's usable from sidecar-only loads
|
||||||
|
// (the inputs are all derivable from the InstanceCpu cache + ModelGeoref).
|
||||||
|
//
|
||||||
|
// Position: `first_placement_meters` is the model's "anchor" placement —
|
||||||
|
// typically the first instance's `placement_transformation`, which the
|
||||||
|
// iterator already produces in metres (its `convert-back-units` default
|
||||||
|
// is false). The translation is optionally lifted through
|
||||||
|
// `georef.coordinate_operation_meters` (controlled by
|
||||||
|
// `apply_coordinate_operation`), then expressed in the federation unit.
|
||||||
|
//
|
||||||
|
// Rotation: read directly from `georef.coordinate_operation_meters` when
|
||||||
|
// `apply_coordinate_operation && has_coordinate_operation` (this is the
|
||||||
|
// helmert grid-north angle). Otherwise zero. Anticlockwise positive.
|
||||||
|
FederatedFalseOrigin
|
||||||
|
guessFederatedFalseOrigin(const Eigen::Matrix4d& first_placement_meters,
|
||||||
|
const ModelGeoref& georef,
|
||||||
|
const FederationConfig& fed_cfg,
|
||||||
|
bool apply_coordinate_operation);
|
||||||
|
|
||||||
// 1 federation_unit -> N metres.
|
// 1 federation_unit -> N metres.
|
||||||
double federationUnitToMeters(const FederationConfig&);
|
double federationUnitToMeters(const FederationConfig&);
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
#include "Geolocation.h"
|
#include "Geolocation.h"
|
||||||
|
#include "Placement.h"
|
||||||
|
|
||||||
#include "../ifcparse/express.h"
|
#include "../ifcparse/express.h"
|
||||||
#include "../ifcparse/file.h"
|
#include "../ifcparse/file.h"
|
||||||
@@ -30,59 +31,6 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// IfcAxis2Placement3D / IfcAxis2PlacementLinear -> column-major 4x4 matrix.
|
|
||||||
// Mirrors ifcopenshell.util.placement.a2p + get_axis2placement, but only the
|
|
||||||
// branches needed for IfcGeometricRepresentationContext.WorldCoordinateSystem.
|
|
||||||
std::optional<Eigen::Matrix4d> getAxis2Placement(express::Base placement) {
|
|
||||||
if (!placement) return std::nullopt;
|
|
||||||
const auto& decl = placement.declaration();
|
|
||||||
if (!(decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear"))) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto entity = placement.as<express::Entity>();
|
|
||||||
|
|
||||||
Eigen::Vector3d z(0.0, 0.0, 1.0);
|
|
||||||
Eigen::Vector3d x(1.0, 0.0, 0.0);
|
|
||||||
|
|
||||||
auto axis_attr = entity.get("Axis");
|
|
||||||
if (!axis_attr.isNull()) {
|
|
||||||
express::Base axis = axis_attr;
|
|
||||||
std::vector<double> dr =
|
|
||||||
axis.as<express::Entity>().get("DirectionRatios");
|
|
||||||
if (dr.size() >= 3) z = Eigen::Vector3d(dr[0], dr[1], dr[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto refdir_attr = entity.get("RefDirection");
|
|
||||||
if (!refdir_attr.isNull()) {
|
|
||||||
express::Base refdir = refdir_attr;
|
|
||||||
std::vector<double> dr =
|
|
||||||
refdir.as<express::Entity>().get("DirectionRatios");
|
|
||||||
if (dr.size() >= 3) x = Eigen::Vector3d(dr[0], dr[1], dr[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto loc_attr = entity.get("Location");
|
|
||||||
if (loc_attr.isNull()) return std::nullopt;
|
|
||||||
express::Base location = loc_attr;
|
|
||||||
auto coords_attr = location.as<express::Entity>().get("Coordinates");
|
|
||||||
if (coords_attr.isNull()) return std::nullopt;
|
|
||||||
std::vector<double> coords = coords_attr;
|
|
||||||
if (coords.size() < 3) return std::nullopt;
|
|
||||||
|
|
||||||
Eigen::Vector3d xn = x.normalized();
|
|
||||||
Eigen::Vector3d zn = z.normalized();
|
|
||||||
Eigen::Vector3d yn = zn.cross(xn).normalized();
|
|
||||||
|
|
||||||
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
|
||||||
m.block<3, 1>(0, 0) = xn;
|
|
||||||
m.block<3, 1>(0, 1) = yn;
|
|
||||||
m.block<3, 1>(0, 2) = zn;
|
|
||||||
m(0, 3) = coords[0];
|
|
||||||
m(1, 3) = coords[1];
|
|
||||||
m(2, 3) = coords[2];
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read a numeric NominalValue out of an IfcPropertySingleValue. IFC2X3
|
// Read a numeric NominalValue out of an IfcPropertySingleValue. IFC2X3
|
||||||
// ePSet_MapConversion stores eastings/northings/scale as IfcLengthMeasure or
|
// ePSet_MapConversion stores eastings/northings/scale as IfcLengthMeasure or
|
||||||
// IfcReal wrapped inside IfcValue (a SELECT) — get_attribute_value(0) peels
|
// IfcReal wrapped inside IfcValue (a SELECT) — get_attribute_value(0) peels
|
||||||
@@ -223,6 +171,10 @@ std::optional<Eigen::Matrix4d> getWcs(ifcopenshell::file* ifc_file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) return std::nullopt;
|
if (!found) return std::nullopt;
|
||||||
|
const auto& decl = wcs.declaration();
|
||||||
|
if (!(decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear"))) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
return getAxis2Placement(wcs);
|
return getAxis2Placement(wcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,3 +263,8 @@ std::optional<express::Base> getMapUnit(ifcopenshell::file* ifc_file) {
|
|||||||
if (mu_attr.isNull()) return std::nullopt;
|
if (mu_attr.isNull()) return std::nullopt;
|
||||||
return (express::Base) mu_attr;
|
return (express::Base) mu_attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double xaxis2angleDeg(double xaa, double xao) {
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
return -std::atan2(xao, xaa) * (180.0 / kPi);
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,4 +100,9 @@ Eigen::Matrix4d helmertMetersFromParameters(const HelmertTransformation& params,
|
|||||||
// calculateUnitScale(file, "LENGTHUNIT") in that case.
|
// calculateUnitScale(file, "LENGTHUNIT") in that case.
|
||||||
std::optional<express::Base> getMapUnit(ifcopenshell::file* ifc_file);
|
std::optional<express::Base> getMapUnit(ifcopenshell::file* ifc_file);
|
||||||
|
|
||||||
|
// "How do I rotate project east to get to grid east?" — i.e. -atan2(xao, xaa)
|
||||||
|
// converted to degrees, anticlockwise positive. Mirrors
|
||||||
|
// ifcopenshell.util.geolocation.xaxis2angle.
|
||||||
|
double xaxis2angleDeg(double xaa, double xao);
|
||||||
|
|
||||||
#endif // GEOLOCATION_H
|
#endif // GEOLOCATION_H
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
/********************************************************************************
|
||||||
|
* *
|
||||||
|
* This file is part of IfcOpenShell. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the Lesser GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* Lesser GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the Lesser GNU General Public License *
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#include "Placement.h"
|
||||||
|
|
||||||
|
#include "../ifcparse/instance_data.h"
|
||||||
|
#include "../ifcparse/schema.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Eigen::Vector3d safeNormalize(const Eigen::Vector3d& v,
|
||||||
|
const Eigen::Vector3d& fallback) {
|
||||||
|
const double n = v.norm();
|
||||||
|
return (n > 0.0) ? Eigen::Vector3d(v / n) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> readDirectionRatios(const express::Base& dir) {
|
||||||
|
if (!dir) return {};
|
||||||
|
auto attr = dir.as<express::Entity>().get("DirectionRatios");
|
||||||
|
if (attr.isNull()) return {};
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Eigen::Matrix4d a2p(const Eigen::Vector3d& origin,
|
||||||
|
const Eigen::Vector3d& z,
|
||||||
|
const Eigen::Vector3d& x) {
|
||||||
|
const Eigen::Vector3d xn = safeNormalize(x, Eigen::Vector3d::UnitX());
|
||||||
|
const Eigen::Vector3d zn = safeNormalize(z, Eigen::Vector3d::UnitZ());
|
||||||
|
const Eigen::Vector3d yn = safeNormalize(zn.cross(xn), Eigen::Vector3d::UnitY());
|
||||||
|
|
||||||
|
Eigen::Matrix4d m = Eigen::Matrix4d::Identity();
|
||||||
|
m.block<3, 1>(0, 0) = xn;
|
||||||
|
m.block<3, 1>(0, 1) = yn;
|
||||||
|
m.block<3, 1>(0, 2) = zn;
|
||||||
|
m.block<3, 1>(0, 3) = origin;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Matrix4d getAxis2Placement(const express::Base& placement) {
|
||||||
|
if (!placement) return Eigen::Matrix4d::Identity();
|
||||||
|
const auto& decl = placement.declaration();
|
||||||
|
auto entity = placement.as<express::Entity>();
|
||||||
|
|
||||||
|
Eigen::Vector3d z(0.0, 0.0, 1.0);
|
||||||
|
Eigen::Vector3d x(1.0, 0.0, 0.0);
|
||||||
|
Eigen::Vector3d o(0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
if (decl.is("IfcAxis2Placement3D") || decl.is("IfcAxis2PlacementLinear")) {
|
||||||
|
auto axis_attr = entity.get("Axis");
|
||||||
|
if (!axis_attr.isNull()) {
|
||||||
|
auto dr = readDirectionRatios((express::Base) axis_attr);
|
||||||
|
if (dr.size() >= 3) z = Eigen::Vector3d(dr[0], dr[1], dr[2]);
|
||||||
|
}
|
||||||
|
auto refdir_attr = entity.get("RefDirection");
|
||||||
|
if (!refdir_attr.isNull()) {
|
||||||
|
auto dr = readDirectionRatios((express::Base) refdir_attr);
|
||||||
|
if (dr.size() >= 3) x = Eigen::Vector3d(dr[0], dr[1], dr[2]);
|
||||||
|
}
|
||||||
|
auto loc_attr = entity.get("Location");
|
||||||
|
if (loc_attr.isNull()) return Eigen::Matrix4d::Identity();
|
||||||
|
express::Base location = loc_attr;
|
||||||
|
auto coords_attr = location.as<express::Entity>().get("Coordinates");
|
||||||
|
if (coords_attr.isNull()) return Eigen::Matrix4d::Identity();
|
||||||
|
std::vector<double> coords = coords_attr;
|
||||||
|
if (coords.size() >= 3) o = Eigen::Vector3d(coords[0], coords[1], coords[2]);
|
||||||
|
} else if (decl.is("IfcAxis2Placement2D")) {
|
||||||
|
auto refdir_attr = entity.get("RefDirection");
|
||||||
|
if (!refdir_attr.isNull()) {
|
||||||
|
auto dr = readDirectionRatios((express::Base) refdir_attr);
|
||||||
|
if (dr.size() >= 1) {
|
||||||
|
x = Eigen::Vector3d(dr.size() > 0 ? dr[0] : 1.0,
|
||||||
|
dr.size() > 1 ? dr[1] : 0.0,
|
||||||
|
0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto loc_attr = entity.get("Location");
|
||||||
|
if (loc_attr.isNull()) return Eigen::Matrix4d::Identity();
|
||||||
|
express::Base location = loc_attr;
|
||||||
|
auto coords_attr = location.as<express::Entity>().get("Coordinates");
|
||||||
|
if (coords_attr.isNull()) return Eigen::Matrix4d::Identity();
|
||||||
|
std::vector<double> coords = coords_attr;
|
||||||
|
if (coords.size() >= 2) {
|
||||||
|
o = Eigen::Vector3d(coords[0], coords[1],
|
||||||
|
coords.size() >= 3 ? coords[2] : 0.0);
|
||||||
|
}
|
||||||
|
} else if (decl.is("IfcAxis1Placement")) {
|
||||||
|
auto axis_attr = entity.get("Axis");
|
||||||
|
if (!axis_attr.isNull()) {
|
||||||
|
auto dr = readDirectionRatios((express::Base) axis_attr);
|
||||||
|
if (dr.size() >= 3) z = Eigen::Vector3d(dr[0], dr[1], dr[2]);
|
||||||
|
}
|
||||||
|
auto loc_attr = entity.get("Location");
|
||||||
|
if (loc_attr.isNull()) return Eigen::Matrix4d::Identity();
|
||||||
|
express::Base location = loc_attr;
|
||||||
|
auto coords_attr = location.as<express::Entity>().get("Coordinates");
|
||||||
|
if (coords_attr.isNull()) return Eigen::Matrix4d::Identity();
|
||||||
|
std::vector<double> coords = coords_attr;
|
||||||
|
if (coords.size() >= 3) o = Eigen::Vector3d(coords[0], coords[1], coords[2]);
|
||||||
|
} else {
|
||||||
|
return Eigen::Matrix4d::Identity();
|
||||||
|
}
|
||||||
|
|
||||||
|
return a2p(o, z, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Matrix4d getLocalPlacement(const express::Base& placement) {
|
||||||
|
if (!placement) return Eigen::Matrix4d::Identity();
|
||||||
|
const auto& decl = placement.declaration();
|
||||||
|
|
||||||
|
if (decl.is("IfcLocalPlacement")) {
|
||||||
|
auto entity = placement.as<express::Entity>();
|
||||||
|
Eigen::Matrix4d parent = Eigen::Matrix4d::Identity();
|
||||||
|
auto rel_attr = entity.get("PlacementRelTo");
|
||||||
|
if (!rel_attr.isNull()) {
|
||||||
|
parent = getLocalPlacement((express::Base) rel_attr);
|
||||||
|
}
|
||||||
|
auto rp_attr = entity.get("RelativePlacement");
|
||||||
|
if (rp_attr.isNull()) return parent;
|
||||||
|
return parent * getAxis2Placement((express::Base) rp_attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfcAxis2Placement* / IfcAxis1Placement passed in directly.
|
||||||
|
return getAxis2Placement(placement);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/********************************************************************************
|
||||||
|
* *
|
||||||
|
* This file is part of IfcOpenShell. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the Lesser GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* Lesser GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the Lesser GNU General Public License *
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
// Port of selected helpers from
|
||||||
|
// src/ifcopenshell-python/ifcopenshell/util/placement.py — entity-instance
|
||||||
|
// placement chains -> 4x4 matrices, used by callers that need to read
|
||||||
|
// IfcLocalPlacement / IfcAxis2Placement* outside of the geometry kernel.
|
||||||
|
|
||||||
|
#ifndef PLACEMENT_H
|
||||||
|
#define PLACEMENT_H
|
||||||
|
|
||||||
|
#include "../ifcparse/express.h"
|
||||||
|
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
|
||||||
|
// Build a 4x4 placement matrix from an origin + Z + X axis triple, mirroring
|
||||||
|
// ifcopenshell.util.placement.a2p. The Y axis is derived as Z × X. Inputs
|
||||||
|
// don't need to be unit; vectors are renormalised internally.
|
||||||
|
Eigen::Matrix4d a2p(const Eigen::Vector3d& origin,
|
||||||
|
const Eigen::Vector3d& z,
|
||||||
|
const Eigen::Vector3d& x);
|
||||||
|
|
||||||
|
// IfcAxis2Placement{2D,3D,Linear} / IfcAxis1Placement -> 4x4 matrix. Mirrors
|
||||||
|
// ifcopenshell.util.placement.get_axis2placement. Returns identity for null
|
||||||
|
// or unparseable inputs. Translation is in the IFC's project length unit.
|
||||||
|
Eigen::Matrix4d getAxis2Placement(const express::Base& placement);
|
||||||
|
|
||||||
|
// Resolve an IfcLocalPlacement (or an IfcAxis2Placement* directly) into a
|
||||||
|
// 4x4 matrix in the IFC project's length unit, walking the PlacementRelTo
|
||||||
|
// chain. Mirrors ifcopenshell.util.placement.get_local_placement. Returns
|
||||||
|
// identity for a null input.
|
||||||
|
Eigen::Matrix4d getLocalPlacement(const express::Base& placement);
|
||||||
|
|
||||||
|
#endif // PLACEMENT_H
|
||||||
@@ -81,6 +81,12 @@ const ModelGeoref* SceneLoader::modelGeoref(uint32_t mid) {
|
|||||||
return &m.georef;
|
return &m.georef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Eigen::Matrix4d* SceneLoader::firstPlacement(uint32_t mid) const {
|
||||||
|
auto it = models_.find(mid);
|
||||||
|
if (it == models_.end() || !it->second.has_first_placement) return nullptr;
|
||||||
|
return &it->second.first_placement;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint32_t> SceneLoader::addFiles(const QStringList& paths) {
|
std::vector<uint32_t> SceneLoader::addFiles(const QStringList& paths) {
|
||||||
std::vector<uint32_t> assigned;
|
std::vector<uint32_t> assigned;
|
||||||
assigned.reserve(paths.size());
|
assigned.reserve(paths.size());
|
||||||
@@ -214,6 +220,14 @@ void SceneLoader::applySidecarData(uint32_t mid, SidecarData data) {
|
|||||||
model.has_georef = true;
|
model.has_georef = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.instances.empty() && !model.has_first_placement) {
|
||||||
|
using Mat4fCol = Eigen::Matrix<float, 4, 4, Eigen::ColMajor>;
|
||||||
|
model.first_placement =
|
||||||
|
Eigen::Map<const Mat4fCol>(data.instances[0].placement_transformation)
|
||||||
|
.cast<double>();
|
||||||
|
model.has_first_placement = true;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<PackedElementInfo> elements = std::move(data.elements);
|
std::vector<PackedElementInfo> elements = std::move(data.elements);
|
||||||
std::string stbl = std::move(data.string_table);
|
std::string stbl = std::move(data.string_table);
|
||||||
|
|
||||||
@@ -302,6 +316,15 @@ void SceneLoader::onStreamerMeshReady(MeshChunk chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SceneLoader::onStreamerInstanceReady(InstanceChunk chunk) {
|
void SceneLoader::onStreamerInstanceReady(InstanceChunk chunk) {
|
||||||
|
if (loading_model_id_ != 0) {
|
||||||
|
auto it = models_.find(loading_model_id_);
|
||||||
|
if (it != models_.end() && !it->second.has_first_placement) {
|
||||||
|
using Mat4fCol = Eigen::Matrix<float, 4, 4, Eigen::ColMajor>;
|
||||||
|
it->second.first_placement =
|
||||||
|
Eigen::Map<const Mat4fCol>(chunk.transform).cast<double>();
|
||||||
|
it->second.has_first_placement = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
viewport_->uploadInstanceChunk(chunk);
|
viewport_->uploadInstanceChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,14 @@ public:
|
|||||||
// sidecar-hit path before the data-source thread populates the streamer).
|
// sidecar-hit path before the data-source thread populates the streamer).
|
||||||
const ModelGeoref* modelGeoref(uint32_t mid);
|
const ModelGeoref* modelGeoref(uint32_t mid);
|
||||||
|
|
||||||
|
// The placement_transformation (in metres, column-major 4x4) of the
|
||||||
|
// first instance the loader saw for `mid` — captured from the streamer's
|
||||||
|
// first InstanceChunk during a stream load, or from the cached
|
||||||
|
// InstanceCpu[0] on a sidecar hit. Returns nullptr until at least one
|
||||||
|
// instance has been observed. Used by the federation false-origin
|
||||||
|
// auto-guess to anchor the model without re-parsing the IFC.
|
||||||
|
const Eigen::Matrix4d* firstPlacement(uint32_t mid) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void progressChanged(int percent);
|
void progressChanged(int percent);
|
||||||
void loadStarted(uint32_t mid, QString display_name);
|
void loadStarted(uint32_t mid, QString display_name);
|
||||||
@@ -125,6 +133,12 @@ private:
|
|||||||
// streamer has its IFC file loaded.
|
// streamer has its IFC file loaded.
|
||||||
ModelGeoref georef;
|
ModelGeoref georef;
|
||||||
bool has_georef = false;
|
bool has_georef = false;
|
||||||
|
|
||||||
|
// The first instance's placement_transformation (in metres) — set
|
||||||
|
// once per model from either the sidecar's InstanceCpu[0] or the
|
||||||
|
// streamer's first InstanceChunk.
|
||||||
|
Eigen::Matrix4d first_placement = Eigen::Matrix4d::Identity();
|
||||||
|
bool has_first_placement = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void startNextLoad();
|
void startNextLoad();
|
||||||
|
|||||||
Reference in New Issue
Block a user