diff --git a/src/render/interact/AnomalyDrawTool.cpp b/src/render/interact/AnomalyDrawTool.cpp index 7ad9fb2..637dbd4 100644 --- a/src/render/interact/AnomalyDrawTool.cpp +++ b/src/render/interact/AnomalyDrawTool.cpp @@ -27,6 +27,7 @@ constexpr double kEps = 1e-9; constexpr double kObserverPriority = 5.0; // 高于切片 widget 与右键菜单(1.0),绘制期独占 constexpr double kDoubleClickMs = 350.0; // 左键双击闭合阈值 constexpr int kClickSlopPx = 6; // 双击位置相近阈值(px) +constexpr int kCloseSnapPx = 12; // 面:光标/点击邻近起点的吸附阈值(px) double nowMs() { return std::chrono::duration( @@ -60,10 +61,10 @@ void AnomalyDrawTool::start(DrawMode mode, const Vec3& planeOrigin, const Vec3& if (renderer_) { const char* tip = mode_ == DrawMode::Point - ? "标注点:左键落点 · 再点可重定位 · 双击/回车确认 · Esc 取消" + ? "标注点:左键单击落点即完成 · Esc 取消" : (mode_ == DrawMode::Line - ? "标注线:左键逐点 · Backspace 撤点 · 双击/回车完成 · Esc 取消" - : "标注面:左键逐点 · Backspace 撤点 · 双击/回车闭合 · Esc 取消"); + ? "标注线:左键逐点 · 双击完成 · Backspace 撤点 · Esc 取消" + : "标注面:左键逐点 · 点回起点闭合 · Backspace 撤点 · Esc 取消"); hint_ = vtkSmartPointer::New(); hint_->SetInput(tip); hint_->GetTextProperty()->SetFontSize(16); @@ -123,6 +124,15 @@ Vec3 AnomalyDrawTool::pickOnPlane() const { return Vec3{{nearP[0] + t * dir[0], nearP[1] + t * dir[1], nearP[2] + t * dir[2]}}; } +bool AnomalyDrawTool::nearFirstVertex(int sx, int sy) const { + if (pts_.empty() || !renderer_) return false; + renderer_->SetWorldPoint(pts_.front()[0], pts_.front()[1], pts_.front()[2], 1.0); + renderer_->WorldToDisplay(); + double d[3]; + renderer_->GetDisplayPoint(d); + return std::abs(d[0] - sx) <= kCloseSnapPx && std::abs(d[1] - sy) <= kCloseSnapPx; +} + void AnomalyDrawTool::addVertex() { // 点模式:单点,再次左键 = 重定位(微调),不累积;线/面模式:累积顶点。 if (mode_ == DrawMode::Point && !pts_.empty()) @@ -185,12 +195,13 @@ void AnomalyDrawTool::updateRubber() { if (interactor_) interactor_->Render(); return; } - // 末点 → 当前光标投影点 的虚线橡皮筋(跟手反馈)。 + // 末点 → 光标 的虚线橡皮筋(跟手反馈);面模式光标邻近起点 → 指向起点,预览闭合。 const Vec3& a = pts_.back(); + const Vec3 endP = cursorNearStart_ ? pts_.front() : cursorPt_; vtkNew points; points->SetNumberOfPoints(2); points->SetPoint(0, a[0], a[1], a[2]); - points->SetPoint(1, cursorPt_[0], cursorPt_[1], cursorPt_[2]); + points->SetPoint(1, endP[0], endP[1], endP[2]); vtkNew poly; poly->SetPoints(points); vtkNew line; @@ -237,6 +248,9 @@ void AnomalyDrawTool::installObservers() { // 鼠标移动:更新末点→光标的虚线橡皮筋(跟手反馈)。不 abort,不干扰其它悬停。 self->cursorPt_ = self->pickOnPlane(); self->hasCursor_ = true; + const int* mp = self->interactor_->GetEventPosition(); + self->cursorNearStart_ = self->mode_ == DrawMode::Face && self->pts_.size() >= 3 && + self->nearFirstVertex(mp[0], mp[1]); self->updateRubber(); return; } @@ -245,9 +259,21 @@ void AnomalyDrawTool::installObservers() { if (self->cmd_) self->cmd_->SetAbortFlag(1); switch (eid) { case vtkCommand::LeftButtonPressEvent: { - // 左键双连击 = 闭合(标准多边形交互);否则加顶点。 + // 点:单击即落点并完成(业界通用,无需双击/回车)。 + if (self->mode_ == DrawMode::Point) { + self->addVertex(); + self->finish(); + break; + } const double now = nowMs(); const int* p = self->interactor_->GetEventPosition(); + // 面:点回起点(屏幕邻近)闭合(≥3点),不加点。 + if (self->mode_ == DrawMode::Face && self->pts_.size() >= 3 && + self->nearFirstVertex(p[0], p[1])) { + self->finish(); + break; + } + // 线:左键双连击 = 完成;否则加顶点。 const bool dbl = self->lastClickMs_ >= 0.0 && (now - self->lastClickMs_) < kDoubleClickMs && std::abs(p[0] - self->lastClickX_) <= kClickSlopPx && @@ -256,7 +282,7 @@ void AnomalyDrawTool::installObservers() { self->lastClickX_ = p[0]; self->lastClickY_ = p[1]; if (dbl) { - // 隔离单/双击:回滚"双击第一下"那次加点/移点(否则点会移、线多一段、面多一条边),再完成。 + // 隔离单/双击:回滚"双击第一下"那次加点(否则线多一段、面多一条边),再完成。 self->pts_ = self->ptsBeforeClick_; self->updatePreview(); self->finish(); diff --git a/src/render/interact/AnomalyDrawTool.hpp b/src/render/interact/AnomalyDrawTool.hpp index d89ce8e..7f1cefa 100644 --- a/src/render/interact/AnomalyDrawTool.hpp +++ b/src/render/interact/AnomalyDrawTool.hpp @@ -41,8 +41,9 @@ private: void addVertex(); // 左键:加顶点 void updatePreview(); // 重建已点几何(顶点圆点 + 实线折线;单点也可见) void updateRubber(); // 鼠标移动:末点→光标的虚线橡皮筋 - void finish(); // 右键/双击/回车:闭合 + void finish(); // 双击/回车/(面)点起点:完成 Vec3 pickOnPlane() const; // 当前鼠标屏幕点 → 射线与平面交点 + bool nearFirstVertex(int sx, int sy) const; // 屏幕点是否邻近起点(面闭合判定/提示) void installObservers(); void removeObservers(); @@ -64,6 +65,7 @@ private: vtkSmartPointer hint_; // 屏幕操作提示 Vec3 cursorPt_{{0, 0, 0}}; // 当前鼠标在切面上的投影点 bool hasCursor_ = false; + bool cursorNearStart_ = false; // 面模式光标邻近起点 → 橡皮筋指向起点预览闭合 vtkSmartPointer cmd_; unsigned long tagLeft_ = 0, tagMove_ = 0, tagRight_ = 0, tagKey_ = 0, tagDbl_ = 0; // 双击闭合检测(左键两连击):记上次左键时刻 + 屏幕位置。