SelectionState (new) owns the multi-set, the "active" id (last single-
clicked), and a per-object_id flags SSBO bound at binding=3. Main
shader reads sel_flags[v_object_id] for the in-set tint and a separate
u_active_id uniform for a stronger tint on the active.
Click semantics: plain replaces, Shift/Ctrl toggles. LMB-drag past 5px
boxes the rect through a pick-pass readback — plain replaces, Shift
adds, Ctrl removes; box-select preserves the active. Drag promotes
regardless of start point so a press on geometry doesn't disqualify it.
Sidecar fast-path bulk-loads instances, so noteObjectId is also called
from the apply path — without it the flags buffer was sized to 1 slot
while object_ids were in the 100k+ range and the in-set bit was lost.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add the federation/model settings dialog and related interface wiring for model coordinate configuration.
Generated with the assistance of an AI coding tool.
Move interface features from panels into modules, move AddModelDialog into the models module, and rename module Widget surfaces to Panel.
Generated with the assistance of an AI coding tool.
The streamer carved [0,100] evenly across N prioritised contexts (and
again across the net/gross passes). In practice nearly every element
yields from the first (Body) context, so smooth progress only ever
filled range/n of the bar — typically ~20% — before snapping forward.
Drive progress directly from yielded element count over total instead.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Area mode now drops a label at every connected coplanar patch — a
BFS sweep over selected_ restricted to each mesh's edge adjacency
identifies the components, then each component gets one label at
its area-weighted centroid in world space. Two clicks on
different walls now show two distinct numbers; a single BFS-grown
wall face stays one number across all its triangles.
The 2-pt length perpendicular line is now omitted when |perp|
matches any of ΔX/ΔY/ΔZ within 1mm — the surface-aligned-with-
axis case where the perpendicular is already shown by one of the
RGB legs. Avoids redundant double-readout on axis-aligned walls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Length tool's 1-pt laser is now hybrid:
- On any surface, a coplanar BFS finds the connected face patch
around the click and projects its vertices into the surface
tangent basis to get an exact bounding-box extent. Stops at
the face edge by construction — no overshoot into adjacent
geometry like the previous tangent-raycast did.
- On near-horizontal surfaces (|n.z| > 0.85, i.e. floors and
ceilings) it additionally fires one raycast in +n to the
opposing surface — so a single floor click reports X extent +
Y extent + ceiling height.
- Bars are labelled by their dominant world axis (X/Y/Z) instead
of "vertical/horizontal", which reads cleanly on either kind
of surface.
The 2-pt readout now draws the world-space XYZ stair-step (red ΔX,
green ΔY, blue ΔZ) with each leg labelled, and a dashed
perpendicular line whenever the two picks landed on near-parallel
surfaces — useful for measuring across walls.
To support multiple line styles per frame, OverlayRenderer's
setOverlayLines takes std::vector<LineGroup> instead of a single
inline style; each group has its own color/halo/width and an
optional dash period. The line shader gained v_along_px +
u_dash_period uniforms (screen-space dashes), and both line and
point shaders now use a sharp step() for the inner→stroke
transition with AA only on the outer halo edge — much crisper than
the previous soft band. Default visual style trimmed: 1.5px lines
(0.5px halo), 6px dots (1px halo), opaque black halo.
Also adds ViewportWindow::raycast(origin, dir, RaycastHit&) — CPU
ray traversal of each model's per-instance BVH followed by
Möller-Trumbore against the candidate meshes' triangles (lazily
read back, cached per call). Used by the floor/ceiling laser path
today and reusable for any future raycast-based feature.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Treat .ifcview sources as geometry-only cache inputs, stop guessing sibling data paths, and only start data-source loading for real model sources after a sidecar hit.
Generated with the assistance of an AI coding tool.
ViewportWindow trades the area_tool_active_ bool for an enum ToolMode
{None, Area, Length}; the existing surfacePickedInTool signal carries
both, the app dispatches on toolMode(). Esc exits any active tool;
Backspace/Delete in length mode emits toolBackspacePressed which the
length tool uses to remove the last point.
LengthMeasurement collects clicked world-space points and adapts the
readout: 2pt → distance + axis-aligned ΔX/ΔY/ΔZ, 3pt → angle at the
middle vertex + triangle area, 4+pt → best-fit-plane PCA + shoelace
when planar (RMS plane distance / bbox diag < 1e-3) else fan
triangulation, with the chosen method labelled in the readout. Per-
segment lengths float at each midpoint.
OverlayRenderer grows three new pipelines to support this:
- point sprite shader: gl_PointCoord-based outlined disc with
fwidth-smoothed inner/stroke bands, a single draw call.
- line shader: CPU-expand each segment to 6 verts carrying both
endpoints + (side, along) corner index; vertex shader computes
the screen-space perpendicular and offsets accordingly. Real
outlined lines independent of the driver's glLineWidth clamp.
- screen-space rect shader: HUD + label backgrounds drawn as raw
GL quads in NDC. QPainter::fillRect on QOpenGLPaintDevice was
silently dropping fills across drivers; bypassing it entirely
via this shader makes backgrounds reliable. Cull-face is also
explicitly disabled here — GL_TRIANGLES respects it but the
line/point primitives don't, so this was the one path needing
the fix.
setOverlayLines / setOverlayPoints take an inner color, an outline
color, and an extra-pixels-per-side stroke amount. Lines + points
draw with GL_ALWAYS so measurement annotations stay visible through
geometry; highlight tris stay depth-aware (GL_LEQUAL) so area
shading still tints the surface in place.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New OverlayRenderer module owns every client-supplied overlay primitive
drawn after the main pass: tinted, depth-aware highlight triangles via
its own GL shader, and top-left HUD text via QPainter on a
QOpenGLPaintDevice. Public surface on ViewportWindow is just two
forwarders (setHighlightTriangles, setHudText).
ViewportWindow's MeshLocalPick now exposes the instance's composed
transform so consumers can map mesh-local geometry back to world
space without re-querying. AreaMeasurement uses both: its selection
key is now (object_id, tri) so per-instance highlighting works for
two distinct walls sharing a mesh, and on every pick it rebuilds the
world-space tri list and the HUD readout.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a click-to-measure area mode triggered by Ctrl+Shift+A. Each LMB
click expands the picked triangle into its connected coplanar patch
(BFS over shared edges, dot(normal, seed) > 0.9999); re-clicking
removes that patch; Alt+LMB skips expansion for a single triangle.
Picks across different meshes accumulate as separate patches.
ViewportWindow gains pickMeshLocalAt (screen pick → mesh-local hit
via inverse composed transform) and a tool-mode pattern mirroring
the section tool (toggleAreaTool, surfacePickedInTool signal,
areaToolToggled signal, Esc to exit). Per-mesh adjacency is built
lazily on first pick of each mesh and dropped on tool toggle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds neutral primitives on ViewportWindow (readbackMeshTriangles,
findInstance) so consumers can compute per-object geometry queries
without the library retaining a CPU triangle copy. Measurement.cpp in
ifcviewer-full uses them to sum signed-tetrahedra in mesh-local space,
weighted by |det(placement_3x3)| per instance for mapped-item scaling.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Read-only handles reject Flush/CompactRange, so the destructor's status
assertion always fired on shutdown when the streamer's sidecar was
opened with read_only=true. Track the flag and skip the write path; also
guard against a null db when the initial open failed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>