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>
Previously, applyCoordinateOperationToViewport — which pushes both
CoordinateOperation and ModelTransformation — was only called on
paths that required the IFC source to be loaded
(onLoadedFromStream and onDataSourceReady). Sidecar-only loads
(loadDataSource off, or no .ifc/.rdb sibling) silently lost both
stages.
Cache the per-model georef + unit scales in the sidecar itself so
the IFC source isn't needed to apply them:
SidecarData gains
coordinate_operation_meters[16] // column-major
project_length_to_meters
map_unit_to_meters
has_coordinate_operation
148 B fixed block written/read between instances and elements.
SIDECAR_VERSION 10 -> 11; existing sidecars rebuild on next load.
MainWindow::writeSidecarForModel populates the block from
loader_->modelGeoref(mid) before writeSidecar.
SceneLoader::applySidecarData restores it into the model's
ModelGeoref + sets has_georef = true, so subsequent
loader_->modelGeoref(mid) calls return the cached data without
needing the IFC.
MainWindow::onLoadedFromSidecar now calls
applyCoordinateOperationToViewport(mid) directly — both
CoordinateOperation and ModelTransformation land at sidecar-load
time, no longer waiting on a possibly-never-arriving data source.
Edits to the IFC's IfcMapConversion don't invalidate the cache —
delete the .ifcview manually if the source's georef changes. This
matches the existing cache-invalidation contract.
Tests: round-trip the new fields through the existing sidecar
fixture; assert SIDECAR_VERSION == 11.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds ModelGeoref { ModelUnits units; Eigen::Matrix4d stage2_meters; bool
has_stage2; } and computeModelGeoref(file*) in Federation.{h,cpp}. The
helper reads the project length unit, IfcProjectedCRS.MapUnit, helmert
parameters and WCS, and reduces them to a metres-in/metres-out stage 2
matrix using the existing Geolocation + Unit primitives. When the model
has no IfcMapConversion it returns an identity stage_2 with has_stage2
== false, so the upload pipeline can branch cheaply.
SceneLoader::Model gains a cached ModelGeoref; SceneLoader::modelGeoref
(uint32_t mid) computes lazily on first call (returns nullptr when the
IFC file isn't available yet — happens on the sidecar-hit path before
the data-source thread populates the streamer) and serves from cache
afterwards.
Not yet consumed by the upload pipeline; that's the next commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The struct holds per-model bookkeeping; Model describes its contents
rather than its container relationship.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sidecar hits skipped opening the underlying .rdb/.ifc, so ifcFile() was
null and the property panel only showed cached name/type/guid. Now, after
a sidecar hit, a background thread opens <stem>.rdb (preferred) or
<stem>.ifc and hands the file to GeometryStreamer via setIfcFile(), with
a dataSourceReady signal so the UI refreshes the current selection.
Gated behind a new AppSettings::loadDataSource toggle (default on) so
users can opt into geometry-only viewing; when off, the sidecar-hit
thread is skipped and the stream-path ifc_file_ is released after
the sidecar write completes.
Also adds *.ifcview to the Add Files dialog filter so a cache can be
opened directly without its source file present.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously readSidecar/writeSidecar were keyed on (path, file_size) with
staleness rejected at read time. Switch to pure path-stem keying: foo.ifc
and foo.ifcdb/ both resolve to foo.ifcview, so the same cache serves either
source format. Staleness is user-managed (delete the sidecar to force a
rebuild), which also lets sidecars be copied or moved independently of the
source.
v8 header drops the source_file_size field.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Handle streamer success, failure, and cancellation as distinct terminal states so failed or cancelled loads do not finalize as successful models. Clean up partial model/UI state in the full and minimal viewer apps when a load is cancelled or fails.
Generated with the assistance of an AI coding tool.
MainWindow and MinimalWindow each carried ~150 lines of mirrored
load-queue, sidecar-thread, streamer-wiring, and ID-rebase code. Lift
all of it into a SceneLoader QObject in the library; both apps now
consume it via signals. Sidecar writes stay on the full-app side since
they need the consumer's element metadata strings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>