ifcviewer-full: console-print accumulating coplanar-patch area tool

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>
This commit is contained in:
Dion Moult
2026-05-07 12:03:22 +10:00
parent f2b655fcf6
commit 016c278748
6 changed files with 391 additions and 4 deletions
+34
View File
@@ -208,6 +208,29 @@ public:
};
bool findInstance(uint32_t object_id, InstanceLookup& out) const;
// Pick + resolve to mesh-local space. Runs pickSurfaceAt to get the
// world-space hit, then inverts the instance's composed transform
// (FederatedFalseOrigin · ModelTransformation · CoordinateOperation
// · placement_transformation) to express the hit in the mesh's own
// coordinates — what readbackMeshTriangles returns. Returns false if
// the click missed geometry or its object_id has no live instance.
struct MeshLocalPick {
uint32_t object_id = 0;
uint32_t model_id = 0;
uint32_t mesh_id = 0;
float mesh_local[3] = {0, 0, 0};
float world_pos[3] = {0, 0, 0};
float world_normal[3]= {0, 0, 0};
};
bool pickMeshLocalAt(int x, int y, MeshLocalPick& out);
// Area tool: while active, LMB clicks emit surfacePickedInTool with
// the click coordinates instead of swapping object selection — the
// app interprets them (typically by calling pickMeshLocalAt and
// accumulating triangle area). Esc exits.
void toggleAreaTool();
bool areaToolActive() const { return area_tool_active_; }
// Federation pipeline: composed instance transform =
// FederatedFalseOrigin · ModelTransformation · CoordinateOperation
// · placement_transformation
@@ -302,6 +325,14 @@ signals:
void objectPicked(uint32_t object_id);
void initialized();
void frameStatsUpdated(const ViewportWindow::FrameStats& stats);
// Emitted instead of objectPicked when the area tool is active. The
// app is expected to call pickMeshLocalAt(x, y, ...) and accumulate.
// modifiers carries the Qt::KeyboardModifiers held at click time so
// the app can branch on Alt etc.
void surfacePickedInTool(int x, int y, int modifiers);
// Emitted whenever toggleAreaTool flips the mode. The app uses this
// to reset accumulator state on entry/exit.
void areaToolToggled(bool active);
protected:
void exposeEvent(QExposeEvent* event) override;
@@ -601,6 +632,9 @@ private:
// Selection
uint32_t selected_object_id_ = 0;
// Area-measurement tool: see toggleAreaTool / surfacePickedInTool.
bool area_tool_active_ = false;
// Active section planes. Uploaded as uniform array each frame to the
// main + pick programs; capped at MaxSectionPlanes.
std::vector<SectionPlane> section_planes_;