feat(3d): 创建异常支持点/线/面三态(子菜单+统一双击·回车提交)
按原型「创建异常→点/线/面」改造:
- 右键菜单单项「创建异常」→ 点/线/面 子菜单;形态(1/2/3)同时驱动绘制 mode、a.markType、
对话框查平台类型的 remarkSourceType(core::AnomalyMarkType 与 remarkSourceType 同值,一个 shape 贯通)
- AnomalyDrawTool 泛化 DrawMode{Point,Line,Face}:点≥1(再点重定位微调)/线≥2(开放)/面≥3(闭合);
最少点数按模式;分形态屏幕提示
- 交互按锁定规范:**双击/回车 提交**(去掉右键提交,右键绘制中消费不响应保留菜单语义);
**Backspace/Delete 撤上一点**;Esc 取消
- AnomalyActor 已支持点(verts)/折线/闭合多边形三态渲染,无需改
测试:439/439 通过
This commit is contained in:
parent
c6756aafc5
commit
58544ffb3c
|
|
@ -506,7 +506,10 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
[&window, &cmdRepo, &nav, interactionMgr, sceneView, scene3dRepo, refreshAnalysis,
|
||||
refreshAnomalies, drawer, anomalyDrawTool, renderWindowPtr]() {
|
||||
QMenu menu(&window);
|
||||
QAction* aAnomaly = menu.addAction(QStringLiteral("创建异常"));
|
||||
QMenu* anomMenu = menu.addMenu(QStringLiteral("创建异常")); // → 点/线/面 子菜单
|
||||
QAction* aAnoPoint = anomMenu->addAction(QStringLiteral("点"));
|
||||
QAction* aAnoLine = anomMenu->addAction(QStringLiteral("线"));
|
||||
QAction* aAnoFace = anomMenu->addAction(QStringLiteral("面"));
|
||||
QAction* aSave = menu.addAction(QStringLiteral("保存"));
|
||||
QMenu* expMenu = menu.addMenu(QStringLiteral("导出"));
|
||||
QAction* aImg = expMenu->addAction(QStringLiteral("图片"));
|
||||
|
|
@ -521,9 +524,14 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
if (chosen == aFace) { interactionMgr->faceSelected(); return; }
|
||||
if (chosen == aFlip) { interactionMgr->flipView(); return; }
|
||||
if (chosen == aClose) { interactionMgr->closeSelected(); return; } // →onSliceClosed→取消列表勾选
|
||||
if (chosen == aAnomaly) {
|
||||
// 在选中切片平面上启动圈定(左键逐点、右键/回车闭合、Esc 取消)。
|
||||
if (chosen == aAnoPoint || chosen == aAnoLine || chosen == aAnoFace) {
|
||||
// 形态(1点/2线/3面):同时决定绘制工具 mode、a.markType、对话框查平台类型的 remarkSourceType。
|
||||
// core::AnomalyMarkType 与 remarkSourceType 同值(Point=1/Polyline=2/Polygon=3),用一个 shape 贯通。
|
||||
namespace ri = geopro::render::interact;
|
||||
using DM = ri::AnomalyDrawTool::DrawMode;
|
||||
const int shape = (chosen == aAnoPoint) ? 1 : (chosen == aAnoLine) ? 2 : 3;
|
||||
const DM mode =
|
||||
(chosen == aAnoPoint) ? DM::Point : (chosen == aAnoLine) ? DM::Line : DM::Face;
|
||||
int axis = 3;
|
||||
ri::Vec3 o{}, p1{}, p2{};
|
||||
if (!interactionMgr->selectedSlicePlane(axis, o, p1, p2)) return;
|
||||
|
|
@ -536,13 +544,13 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
// 异常归属(spec §8):当前选中切片已保存(selectedSliceDsId 非空)→挂该切片;临时切片→挂体。
|
||||
const std::string savedSliceId = interactionMgr->selectedSliceDsId();
|
||||
anomalyDrawTool->start(
|
||||
o, normal,
|
||||
mode, o, normal,
|
||||
[&window, &cmdRepo, &nav, sceneView, scene3dRepo, renderWindowPtr, refreshAnomalies,
|
||||
refreshAnalysis, volId, savedSliceId, normal, o, p1,
|
||||
p2](const std::vector<ri::Vec3>& worldPts) {
|
||||
refreshAnalysis, volId, savedSliceId, normal, o, p1, p2,
|
||||
shape](const std::vector<ri::Vec3>& worldPts) {
|
||||
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
|
||||
geopro::core::Anomaly a;
|
||||
a.markType = geopro::core::AnomalyMarkType::Polygon;
|
||||
a.markType = static_cast<geopro::core::AnomalyMarkType>(shape);
|
||||
a.remarkSourceId =
|
||||
geopro::core::resolveAnomalyMount(!savedSliceId.empty(), savedSliceId, volId);
|
||||
a.lineColor = "#ff3030";
|
||||
|
|
@ -576,10 +584,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
const double minExt = 0.25 * std::min(e1, e2); // 点/线退化框景半径
|
||||
geopro::app::captureFramedRegionPng(renderWindowPtr, rb, 1.4, minExt,
|
||||
shot.toStdString(), sw, sh);
|
||||
// 异常类型按标注形态拉平台类型(与平台一致)。当前仅面(Polygon)→remarkSourceType=3;
|
||||
// P2 接入点/线后由菜单形态决定 1/2/3。
|
||||
// 异常类型按标注形态(shape=1点/2线/3面)拉对应平台类型,与平台一致。
|
||||
geopro::app::AnomalySaveDialog dlg(shot, sw, sh, &cmdRepo,
|
||||
nav.currentProjectId(), 3, &window);
|
||||
nav.currentProjectId(), shape, &window);
|
||||
if (dlg.exec() != QDialog::Accepted) {
|
||||
sceneView->removeAnomaly(draftId);
|
||||
renderWindowPtr->Render();
|
||||
|
|
|
|||
|
|
@ -40,10 +40,11 @@ AnomalyDrawTool::AnomalyDrawTool(vtkRenderWindowInteractor* interactor, vtkRende
|
|||
|
||||
AnomalyDrawTool::~AnomalyDrawTool() { removeObservers(); }
|
||||
|
||||
void AnomalyDrawTool::start(const Vec3& planeOrigin, const Vec3& planeNormal,
|
||||
void AnomalyDrawTool::start(DrawMode mode, const Vec3& planeOrigin, const Vec3& planeNormal,
|
||||
std::function<void(const std::vector<Vec3>&)> onFinish,
|
||||
std::function<void()> onCancel) {
|
||||
if (active_) cancel();
|
||||
mode_ = mode;
|
||||
origin_ = planeOrigin;
|
||||
normal_ = normalize(planeNormal);
|
||||
onFinish_ = std::move(onFinish);
|
||||
|
|
@ -54,10 +55,16 @@ void AnomalyDrawTool::start(const Vec3& planeOrigin, const Vec3& planeNormal,
|
|||
active_ = true;
|
||||
installObservers();
|
||||
|
||||
// 屏幕操作提示(左上角),解决"不知如何闭合"。
|
||||
// 屏幕操作提示(左上角),按形态给不同指引。
|
||||
if (renderer_) {
|
||||
const char* tip =
|
||||
mode_ == DrawMode::Point
|
||||
? "标注点:左键落点 · 再点可重定位 · 双击/回车确认 · Esc 取消"
|
||||
: (mode_ == DrawMode::Line
|
||||
? "标注线:左键逐点 · Backspace 撤点 · 双击/回车完成 · Esc 取消"
|
||||
: "标注面:左键逐点 · Backspace 撤点 · 双击/回车闭合 · Esc 取消");
|
||||
hint_ = vtkSmartPointer<vtkTextActor>::New();
|
||||
hint_->SetInput("圈定异常:左键逐点 · 双击或右键完成 · Esc 取消");
|
||||
hint_->SetInput(tip);
|
||||
hint_->GetTextProperty()->SetFontSize(16);
|
||||
hint_->GetTextProperty()->SetColor(1.0, 0.9, 0.0);
|
||||
hint_->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
|
||||
|
|
@ -116,6 +123,10 @@ Vec3 AnomalyDrawTool::pickOnPlane() const {
|
|||
}
|
||||
|
||||
void AnomalyDrawTool::addVertex() {
|
||||
// 点模式:单点,再次左键 = 重定位(微调),不累积;线/面模式:累积顶点。
|
||||
if (mode_ == DrawMode::Point && !pts_.empty())
|
||||
pts_[0] = pickOnPlane();
|
||||
else
|
||||
pts_.push_back(pickOnPlane());
|
||||
updatePreview();
|
||||
}
|
||||
|
|
@ -200,7 +211,9 @@ void AnomalyDrawTool::updateRubber() {
|
|||
}
|
||||
|
||||
void AnomalyDrawTool::finish() {
|
||||
if (pts_.size() < 3) { // 不足以成面 → 取消
|
||||
const std::size_t minPts =
|
||||
mode_ == DrawMode::Point ? 1 : (mode_ == DrawMode::Line ? 2 : 3); // 点1/线2/面3
|
||||
if (pts_.size() < minPts) { // 不足以成形 → 取消
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
|
@ -245,11 +258,21 @@ void AnomalyDrawTool::installObservers() {
|
|||
self->addVertex();
|
||||
break;
|
||||
}
|
||||
case vtkCommand::RightButtonPressEvent: self->finish(); break;
|
||||
case vtkCommand::RightButtonPressEvent:
|
||||
// 绘制中右键不提交(保留给「创建异常」菜单语义);已 abort 消费,不打开菜单。
|
||||
break;
|
||||
case vtkCommand::KeyPressEvent: {
|
||||
const char* key = self->interactor_->GetKeySym();
|
||||
if (key && (std::string(key) == "Escape")) self->cancel();
|
||||
else if (key && (std::string(key) == "Return")) self->finish();
|
||||
const std::string k = key ? std::string(key) : std::string();
|
||||
if (k == "Escape")
|
||||
self->cancel();
|
||||
else if (k == "Return" || k == "KP_Enter")
|
||||
self->finish();
|
||||
else if ((k == "BackSpace" || k == "Delete") && !self->pts_.empty()) {
|
||||
self->pts_.pop_back(); // 撤上一点
|
||||
self->updatePreview();
|
||||
self->updateRubber();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
|
|
|
|||
|
|
@ -14,21 +14,24 @@ class vtkCallbackCommand;
|
|||
|
||||
namespace geopro::render::interact {
|
||||
|
||||
// 异常圈定工具(#4b):在给定切片平面上交互式画多边形。
|
||||
// 左键逐点加顶点(屏幕射线与平面求交,落在平面上);右键 / 双击 / 回车 闭合 → onFinish(worldPts);
|
||||
// Esc / 不足 3 点闭合 → onCancel。绘制中实时预览折线。
|
||||
// 高优先级(2.0)交互器观察者抢输入:先于切片 widget 与 InteractionManager 右键菜单,绘制期独占左右键。
|
||||
// 异常圈定工具(#4b):在给定切片平面上交互式画 点 / 线 / 面。
|
||||
// 左键逐点加顶点(屏幕射线与平面求交,落在平面上);**双击 / 回车 提交** → onFinish(worldPts);
|
||||
// Esc 取消;Backspace 撤上一点;点模式再次左键=重定位单点(微调)。右键绘制中不响应(保留给菜单语义)。
|
||||
// 点≥1 / 线≥2(开放) / 面≥3(闭合);闭合与否由上层据 markType 渲染,本工具只产顶点。
|
||||
// 高优先级(5.0)交互器观察者抢输入:先于切片 widget 与 InteractionManager 右键菜单,绘制期独占左右键。
|
||||
// render 层:只碰 VTK,不认业务;产物(平面上的世界点)经回调交上层组装 core::Anomaly。
|
||||
class AnomalyDrawTool {
|
||||
public:
|
||||
enum class DrawMode { Point, Line, Face }; // 点(1)/线(2,开放)/面(3,闭合)
|
||||
|
||||
AnomalyDrawTool(vtkRenderWindowInteractor* interactor, vtkRenderer* renderer);
|
||||
~AnomalyDrawTool();
|
||||
|
||||
AnomalyDrawTool(const AnomalyDrawTool&) = delete;
|
||||
AnomalyDrawTool& operator=(const AnomalyDrawTool&) = delete;
|
||||
|
||||
// 开始在平面(origin/normal)上圈定。onFinish 收闭合多边形顶点(世界系);onCancel 取消。
|
||||
void start(const Vec3& planeOrigin, const Vec3& planeNormal,
|
||||
// 开始在平面(origin/normal)上按 mode 圈定。onFinish 收顶点(世界系);onCancel 取消。
|
||||
void start(DrawMode mode, const Vec3& planeOrigin, const Vec3& planeNormal,
|
||||
std::function<void(const std::vector<Vec3>&)> onFinish,
|
||||
std::function<void()> onCancel);
|
||||
bool active() const { return active_; }
|
||||
|
|
@ -49,6 +52,7 @@ private:
|
|||
vtkRenderer* renderer_;
|
||||
|
||||
bool active_ = false;
|
||||
DrawMode mode_ = DrawMode::Face;
|
||||
Vec3 origin_{{0, 0, 0}}, normal_{{0, 0, 1}};
|
||||
std::vector<Vec3> pts_;
|
||||
std::function<void(const std::vector<Vec3>&)> onFinish_;
|
||||
|
|
|
|||
Loading…
Reference in New Issue