fix(3d): 异常绘制提示中文乱码(改 QLabel 浮层) + 列表切到别对象清切片选中

1) 提示"乱码":vtkTextActor 用 VTK 内置字体不含中文字形 → 中文渲染不出(只剩 ASCII)。
   移除 VTK 文本提示,改 app 层右上角 QLabel 浮层:Qt 渲染中文 + QSS(深底/accent描边/圆角),
   绘制开始按形态显示结束方式、结束/取消隐藏;不挡画布鼠标。
2) 列表选中切片后切到别的对象(三维体/异常),VTK 切片仍高亮:datasetSelected 选非切片对象时
   未清切片选中。加 InteractionManager::deselectSlice();选异常/其它对象均清切片高亮(异常↔切片互斥)。

测试:439/439 通过
This commit is contained in:
gaozheng 2026-06-26 16:06:07 +08:00
parent 9782a2b93e
commit f230ca8dd1
5 changed files with 44 additions and 40 deletions

View File

@ -398,6 +398,17 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
viewToolbar->move(12, 12);
viewToolbar->raise();
viewToolbar->show();
// 异常绘制操作提示:右上角 QLabel 浮层VTK 内置字体不含中文字形,故用 Qt 渲染中文 + QSS 美化)。
// 深底 + accent 描边;不挡画布鼠标事件;绘制开始显示、结束/取消隐藏(见 onSliceContextMenuRequested
auto* anomalyHint = new QLabel(vtkWidget);
anomalyHint->setObjectName(QStringLiteral("anomalyHint"));
anomalyHint->setAttribute(Qt::WA_TransparentForMouseEvents);
geopro::app::applyTokenizedStyleSheet(
anomalyHint,
QStringLiteral("QLabel#anomalyHint{background:rgba(10,18,30,0.85);color:#E6ECF5;"
"border:1px solid {{accent/primary}};border-radius:8px;padding:8px 12px;}"));
anomalyHint->hide();
// 坐标轴设置抽屉面板:叠加 vtkWidget、工具条右侧滑出默认隐藏点设置 toggle
auto* axesPanel = new geopro::app::AxesSettingsPanel(vtkWidget);
axesPanel->hide();
@ -506,7 +517,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// 正视/翻转/关闭=接现有交互(关闭已保存切片→onSliceClosed 取消列表勾选);创建异常=占位(#4)。
interactionMgr->onSliceContextMenuRequested =
[&window, &cmdRepo, &nav, interactionMgr, sceneView, scene3dRepo, refreshAnalysis,
refreshAnomalies, drawer, anomalyDrawTool, renderWindowPtr]() {
refreshAnomalies, drawer, anomalyDrawTool, renderWindowPtr, anomalyHint, vtkWidget]() {
QMenu menu(&window);
QMenu* anomMenu = menu.addMenu(QStringLiteral("创建异常")); // → 点/线/面 子菜单
QAction* aAnoPoint = anomMenu->addAction(QStringLiteral(""));
@ -543,6 +554,16 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
const ri::Vec3 e1{{p1[0] - o[0], p1[1] - o[1], p1[2] - o[2]}};
const ri::Vec3 e2{{p2[0] - o[0], p2[1] - o[1], p2[2] - o[2]}};
const ri::Vec3 normal = ri::normalize(ri::cross(e1, e2));
// 操作提示浮层(右上角):按形态显示结束方式;绘制结束/取消隐藏。
anomalyHint->setText(
shape == 1 ? QStringLiteral("标注点\n左键单击落点即完成\nEsc 取消")
: shape == 2
? QStringLiteral("标注线\n左键逐点 · 双击结束\nBackspace 撤点 · Esc 取消")
: QStringLiteral("标注面\n左键逐点 · 点回起点闭合\nBackspace 撤点 · Esc 取消"));
anomalyHint->adjustSize();
anomalyHint->move(vtkWidget->width() - anomalyHint->width() - 12, 12); // 右上角
anomalyHint->show();
anomalyHint->raise();
// 多体并发:异常挂到"选中切片所属体"(非 currentVolume无选中切片回退当前体。
std::string volId = interactionMgr->selectedSliceVolumeDsId();
if (volId.empty()) volId = sceneView->currentVolumeDsId();
@ -551,8 +572,9 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
anomalyDrawTool->start(
mode, o, normal,
[&window, &cmdRepo, &nav, interactionMgr, sceneView, scene3dRepo, renderWindowPtr,
refreshAnomalies, refreshAnalysis, volId, savedSliceId, normal, o, p1, p2,
shape](const std::vector<ri::Vec3>& worldPts) {
refreshAnomalies, refreshAnalysis, volId, savedSliceId, normal, o, p1, p2, shape,
anomalyHint](const std::vector<ri::Vec3>& worldPts) {
anomalyHint->hide(); // 绘制结束 → 隐藏操作提示
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
geopro::core::Anomaly a;
a.markType = static_cast<geopro::core::AnomalyMarkType>(shape);
@ -632,7 +654,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
QString::fromStdString(m));
});
},
[]() { /* onCancel放弃无需处理 */ });
[anomalyHint]() { anomalyHint->hide(); }); // 取消(Esc)→隐藏提示
return;
}
if (aSave != nullptr && chosen == aSave) {
@ -991,12 +1013,16 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
[sceneView, interactionMgr, renderWindowPtr](const QString& dsId,
const QString& ddCode) {
const std::string id = dsId.toStdString();
// 选中项决定高亮:异常↔切片互斥,选其它对象两者都清(否则切到别的对象后切片/异常仍高亮)。
if (ddCode == QStringLiteral("dd_anomaly")) {
sceneView->setSelectedAnomaly(id);
} else {
sceneView->setSelectedAnomaly(std::string{}); // 选中非异常对象→清异常高亮
if (ddCode == QStringLiteral("dd_slice"))
interactionMgr->deselectSlice();
} else if (ddCode == QStringLiteral("dd_slice")) {
sceneView->setSelectedAnomaly(std::string{});
interactionMgr->selectSavedSlice(id); // 选中已渲染的该切片(高亮)
} else {
sceneView->setSelectedAnomaly(std::string{});
interactionMgr->deselectSlice();
}
renderWindowPtr->Render();
});

View File

@ -17,8 +17,6 @@
#include <vtkProperty.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkTextActor.h>
#include <vtkTextProperty.h>
namespace geopro::render::interact {
@ -55,34 +53,9 @@ void AnomalyDrawTool::start(DrawMode mode, const Vec3& planeOrigin, const Vec3&
hasCursor_ = false;
active_ = true;
installObservers();
// 屏幕操作提示(右上角,避开左侧工具条):标题 + 当前形态的结束方式 + 取消,分行排版。
if (renderer_) {
const char* tip =
mode_ == DrawMode::Point
? "标注点\n左键单击落点即完成\nEsc 取消"
: (mode_ == DrawMode::Line
? "标注线\n左键逐点 · 双击结束\nBackspace 撤点 · Esc 取消"
: "标注面\n左键逐点 · 点回起点闭合\nBackspace 撤点 · Esc 取消");
hint_ = vtkSmartPointer<vtkTextActor>::New();
hint_->SetInput(tip);
auto* tp = hint_->GetTextProperty();
tp->SetFontSize(15);
tp->SetLineSpacing(1.3);
tp->SetColor(0.90, 0.94, 1.0); // 近白(canvas-text)
tp->SetJustificationToRight(); // 右对齐,贴右上
tp->SetVerticalJustificationToTop();
tp->SetBackgroundColor(0.04, 0.07, 0.12); // 深底
tp->SetBackgroundOpacity(0.66);
tp->SetFrame(1);
tp->SetFrameColor(0.37, 0.55, 0.96); // accent 描边
tp->SetFrameWidth(1);
hint_->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
hint_->GetPositionCoordinate()->SetValue(0.985, 0.975); // 右上角
renderer_->AddViewProp(hint_);
// 操作提示由 app 层 QLabel 浮层承担(VTK 内置字体不含中文字形 → vtkTextActor 渲染不出中文)。
if (interactor_) interactor_->Render();
}
}
void AnomalyDrawTool::cancel() {
if (!active_) return;
@ -97,11 +70,9 @@ void AnomalyDrawTool::teardownActive() {
if (renderer_) {
if (preview_) renderer_->RemoveViewProp(preview_);
if (rubber_) renderer_->RemoveViewProp(rubber_);
if (hint_) renderer_->RemoveViewProp(hint_);
}
preview_ = nullptr;
rubber_ = nullptr;
hint_ = nullptr;
active_ = false;
hasCursor_ = false;
pts_.clear();

View File

@ -9,7 +9,6 @@
class vtkRenderWindowInteractor;
class vtkRenderer;
class vtkActor;
class vtkTextActor;
class vtkCallbackCommand;
namespace geopro::render::interact {
@ -61,7 +60,6 @@ private:
vtkSmartPointer<vtkActor> preview_; // 已点几何(顶点圆点 + 实线折线)
vtkSmartPointer<vtkActor> rubber_; // 末点→光标 虚线橡皮筋
vtkSmartPointer<vtkTextActor> hint_; // 屏幕操作提示
Vec3 cursorPt_{{0, 0, 0}}; // 当前鼠标在切面上的投影点
bool hasCursor_ = false;
bool cursorNearStart_ = false; // 面模式光标邻近起点 → 橡皮筋指向起点预览闭合

View File

@ -206,6 +206,13 @@ bool InteractionManager::selectSavedSlice(const std::string& dsId) {
return false;
}
void InteractionManager::deselectSlice() {
if (selected_ < 0) return;
selected_ = -1;
updateSelectionVisual(); // 清高亮(无选中切片)
safeRender();
}
void InteractionManager::selectByTool(const SliceTool* tool) {
int idx = -1;
for (std::size_t i = 0; i < slices_.size(); ++i)

View File

@ -60,6 +60,8 @@ public:
std::vector<std::string> shownSavedSliceIds() const; // 当前已显示的已保存切片 dsId 列表
// 选中已显示的某 dsId 切片(列表操作定位到对应渲染切片);找到返回 true。
bool selectSavedSlice(const std::string& dsId);
// 清除切片选中(列表选中切到别的对象/异常时调用,否则 VTK 切片仍高亮,用户反馈)。
void deselectSlice();
// 关闭选中切片E56。无选中则忽略。
void closeSelected();