mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 14:33:28 +00:00
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:
@@ -1688,6 +1688,13 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Esc also exits the area tool.
|
||||
if (area_tool_active_
|
||||
&& key == Qt::Key_Escape
|
||||
&& !event->isAutoRepeat()) {
|
||||
toggleAreaTool();
|
||||
return;
|
||||
}
|
||||
QWindow::keyPressEvent(event);
|
||||
}
|
||||
|
||||
@@ -3473,10 +3480,15 @@ void ViewportWindow::handleMouseRelease(QMouseEvent* e) {
|
||||
if (active_button_ == Qt::LeftButton
|
||||
&& !section_tool_active_
|
||||
&& (e->pos() - last_mouse_pos_).manhattanLength() < 5) {
|
||||
uint32_t id = pickObjectAt(e->pos().x(), e->pos().y());
|
||||
selected_object_id_ = id;
|
||||
emit objectPicked(id);
|
||||
requestUpdate(); // selection highlight changed
|
||||
if (area_tool_active_) {
|
||||
emit surfacePickedInTool(e->pos().x(), e->pos().y(),
|
||||
int(e->modifiers()));
|
||||
} else {
|
||||
uint32_t id = pickObjectAt(e->pos().x(), e->pos().y());
|
||||
selected_object_id_ = id;
|
||||
emit objectPicked(id);
|
||||
requestUpdate(); // selection highlight changed
|
||||
}
|
||||
}
|
||||
const bool was_navigating = (active_button_ == Qt::MiddleButton);
|
||||
active_button_ = Qt::NoButton;
|
||||
@@ -3809,3 +3821,40 @@ bool ViewportWindow::findInstance(uint32_t object_id, InstanceLookup& out) const
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewportWindow::pickMeshLocalAt(int x, int y, MeshLocalPick& out) {
|
||||
uint32_t obj_id = 0;
|
||||
QVector3D world_pos, world_normal;
|
||||
if (!pickSurfaceAt(x, y, obj_id, world_pos, world_normal)) return false;
|
||||
|
||||
for (const auto& kv : models_gpu_) {
|
||||
const ModelGpuData& m = kv.second;
|
||||
for (const InstanceCpu& inst : m.instances) {
|
||||
if (inst.object_id != obj_id) continue;
|
||||
using Mat4f = Eigen::Matrix<float, 4, 4, Eigen::ColMajor>;
|
||||
const Eigen::Matrix4f T = Eigen::Map<const Mat4f>(inst.transform);
|
||||
const Eigen::Matrix4f Ti = T.inverse();
|
||||
const Eigen::Vector4f wp(world_pos.x(), world_pos.y(), world_pos.z(), 1.0f);
|
||||
const Eigen::Vector4f mp = Ti * wp;
|
||||
out.object_id = obj_id;
|
||||
out.model_id = inst.model_id;
|
||||
out.mesh_id = inst.mesh_id;
|
||||
out.mesh_local[0] = mp.x();
|
||||
out.mesh_local[1] = mp.y();
|
||||
out.mesh_local[2] = mp.z();
|
||||
out.world_pos[0] = world_pos.x();
|
||||
out.world_pos[1] = world_pos.y();
|
||||
out.world_pos[2] = world_pos.z();
|
||||
out.world_normal[0] = world_normal.x();
|
||||
out.world_normal[1] = world_normal.y();
|
||||
out.world_normal[2] = world_normal.z();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ViewportWindow::toggleAreaTool() {
|
||||
area_tool_active_ = !area_tool_active_;
|
||||
emit areaToolToggled(area_tool_active_);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
Reference in New Issue
Block a user