diff --git a/src/app/main.cpp b/src/app/main.cpp index 0d557bc..22925f8 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -508,6 +508,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re detailPanel, [detailPanel](const QString& dsId) { detailPanel->focusDataset(dsId); }); + QObject::connect(&detailCtrl, &geopro::controller::DatasetDetailController::loadFailed, &window, + [&window](const QString& dsId, const QString& msg) { + window.statusBar()->showMessage( + QStringLiteral("数据集 %1 加载失败:%2").arg(dsId, msg), 5000); + }); // ── 详情面板切 Tab → 反向高亮数据集列表对应行 ── QObject::connect(detailPanel, &geopro::app::DatasetDetailPanel::activeDatasetChanged, diff --git a/src/app/panels/DatasetDetailPanel.cpp b/src/app/panels/DatasetDetailPanel.cpp index 4f3e59f..a5267a3 100644 --- a/src/app/panels/DatasetDetailPanel.cpp +++ b/src/app/panels/DatasetDetailPanel.cpp @@ -4,7 +4,7 @@ namespace geopro::app { DatasetDetailPanel::DatasetDetailPanel(QWidget* parent) : QTabWidget(parent) { setTabsClosable(true); - connect(this, &QTabWidget::tabCloseRequested, this, [this](int i) { delete widget(i); }); + connect(this, &QTabWidget::tabCloseRequested, this, [this](int i) { widget(i)->deleteLater(); }); connect(this, &QTabWidget::currentChanged, this, [this](int i) { if (auto* p = qobject_cast(widget(i))) emit activeDatasetChanged(p->dsId()); diff --git a/src/app/panels/chart/DatasetChartView.cpp b/src/app/panels/chart/DatasetChartView.cpp index 60ce1ed..d1dff50 100644 --- a/src/app/panels/chart/DatasetChartView.cpp +++ b/src/app/panels/chart/DatasetChartView.cpp @@ -1,4 +1,5 @@ #include "panels/chart/DatasetChartView.hpp" +#include #include #include #include @@ -24,13 +25,18 @@ DatasetChartView::DatasetChartView(QWidget* parent) } void DatasetChartView::clearChart() { + // scene_->clear() 删除场景中的所有 item(含 anomalyItems_ 所指向的对象); + // anomalyItems_.clear() 随后清空向量中的悬空指针。 + // 二者必须成对、顺序不可颠倒:若先 clear() 后仍持有指针,rebuildAnomalyItems + // 中的 delete 会对已释放对象 double-free;若先清向量、后继续操作旧指针同样 UB。 scene_->clear(); anomalyItems_.clear(); } void DatasetChartView::showScatter(const geopro::core::ScatterField& f, const geopro::core::ColorScale& cs) { clearChart(); const double sz = 0.6; // 方块边长(数据单位,近似 web 方点) - for (size_t i = 0; i < f.v.size(); ++i) { + const size_t n = std::min({f.x.size(), f.y.size(), f.v.size()}); + for (size_t i = 0; i < n; ++i) { auto* r = scene_->addRect(f.x[i] - sz / 2, f.y[i] - sz / 2, sz, sz, QPen(Qt::white, 0), QBrush(toQ(cs.colorAt(f.v[i])))); r->setPen(QPen(Qt::white, 0)); // 白描边 @@ -81,6 +87,16 @@ void DatasetChartView::rebuildAnomalyItems() { for (int i = 0; i < static_cast(anomalies_.size()); ++i) { if (hidden_.count(i)) continue; const auto& a = anomalies_[i]; + if (a.localPts.empty()) continue; + if (static_cast(a.markType) == 1) { // 点:小方块标记 + const double s = 0.8; + auto* dot = scene_->addRect(a.localPts[0].x - s/2, a.localPts[0].y - s/2, s, s, + QPen(QColor(QString::fromStdString(a.lineColor)), 0), + QBrush(QColor(QString::fromStdString(a.lineColor)))); + dot->setZValue(2); + anomalyItems_.push_back(dot); + continue; + } if (a.localPts.size() < 2) continue; QPainterPath path; path.moveTo(a.localPts[0].x, a.localPts[0].y); for (size_t k = 1; k < a.localPts.size(); ++k) path.lineTo(a.localPts[k].x, a.localPts[k].y); diff --git a/src/controller/DatasetDetailController.cpp b/src/controller/DatasetDetailController.cpp index fb03556..d3e4a48 100644 --- a/src/controller/DatasetDetailController.cpp +++ b/src/controller/DatasetDetailController.cpp @@ -7,6 +7,13 @@ DatasetDetailController::DatasetDetailController(data::IDatasetRepository& repo, : QObject(parent), repo_(repo) {} void DatasetDetailController::openDataset(const QString& dsId, const QString& ddCode) { + if (busy_) return; // 防重入(同步网络期间 QEventLoop 可重入) + busy_ = true; + if (ddCode != QLatin1String("dd_inversion_data")) { // 首版仅支持 ERT 反演 + busy_ = false; + emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览")); + return; + } const std::string id = dsId.toStdString(); try { ChartData d; @@ -17,8 +24,10 @@ void DatasetDetailController::openDataset(const QString& dsId, const QString& dd d.grid = repo_.loadGrid(id); d.gridScale = repo_.loadColorScale(id); d.anomalies = repo_.loadAnomalies(id); + busy_ = false; emit chartReady(d); } catch (const std::exception& e) { + busy_ = false; emit loadFailed(dsId, QString::fromStdString(e.what())); } } diff --git a/src/controller/DatasetDetailController.hpp b/src/controller/DatasetDetailController.hpp index 43e5993..31a753b 100644 --- a/src/controller/DatasetDetailController.hpp +++ b/src/controller/DatasetDetailController.hpp @@ -31,5 +31,6 @@ signals: void loadFailed(const QString& dsId, const QString& message); private: data::IDatasetRepository& repo_; + bool busy_ = false; }; } // namespace geopro::controller diff --git a/src/render/ContourBands.cpp b/src/render/ContourBands.cpp index 894ed34..3f6a53c 100644 --- a/src/render/ContourBands.cpp +++ b/src/render/ContourBands.cpp @@ -130,7 +130,7 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const banded->SetInputConnection(surf->GetOutputPort()); banded->SetNumberOfContours(static_cast(stops.size())); for (int i = 0; i < static_cast(stops.size()); ++i) banded->SetValue(i, stops[i]); - banded->GenerateContourEdgesOn(); + if (opt.makeLines) banded->GenerateContourEdgesOn(); banded->SetScalarModeToValue(); banded->Update(); @@ -152,21 +152,23 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const out.bands.push_back(std::move(bp)); } - // port1:等值线(polylines)+ DP 简化。 - vtkPolyData* edges = banded->GetContourEdgesOutput(); - if (edges) { - edges->BuildCells(); - const vtkIdType nLines = edges->GetNumberOfCells(); - for (vtkIdType c = 0; c < nLines; ++c) { - vtkCell* cell = edges->GetCell(c); - vtkPoints* cp = cell->GetPoints(); - ContourLine cl; cl.level = 0.0; - for (vtkIdType p = 0; p < cp->GetNumberOfPoints(); ++p) { - double xyz[3]; cp->GetPoint(p, xyz); - cl.pts.push_back(Vec2{xyz[0], xyz[1]}); + // port1:等值线(polylines)+ DP 简化。仅 makeLines=true 时生成(否则不提取)。 + if (opt.makeLines) { + vtkPolyData* edges = banded->GetContourEdgesOutput(); + if (edges) { + edges->BuildCells(); + const vtkIdType nLines = edges->GetNumberOfCells(); + for (vtkIdType c = 0; c < nLines; ++c) { + vtkCell* cell = edges->GetCell(c); + vtkPoints* cp = cell->GetPoints(); + ContourLine cl; cl.level = 0.0; + for (vtkIdType p = 0; p < cp->GetNumberOfPoints(); ++p) { + double xyz[3]; cp->GetPoint(p, xyz); + cl.pts.push_back(Vec2{xyz[0], xyz[1]}); + } + simplifyInPlace(cl.pts, opt.simplifyTol); + if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl)); } - simplifyInPlace(cl.pts, opt.simplifyTol); - if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl)); } } return out;