Skip to content

Commit 6117ae5

Browse files
Tom O'ReillyTom O'Reilly
authored andcommitted
hides/shows points as they are marked bad/good in EditDataItem window
1 parent cf173bc commit 6117ae5

2 files changed

Lines changed: 195 additions & 46 deletions

File tree

src/qt-guilib/SurfaceDataItem.cpp

Lines changed: 139 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,170 @@
11
#include "SurfaceDataItem.h"
2+
#include <vtkIdList.h>
3+
#include <vtkIntArray.h>
4+
#include <vtkCellArray.h>
25
#include <QDebug>
3-
#include <QMetaObject>
46

57
using namespace mb_system;
68

9+
// ─────────────────────────────────────────────────────────────────────────────
10+
// Constructor
711
// ─────────────────────────────────────────────────────────────────────────────
812

913
SurfaceDataItem::SurfaceDataItem()
1014
: TopoDataItem()
1115
{
12-
// Surface view defaults: polygon surface, elevation coloring
1316
surfaceRenderType_ = SurfaceRenderType::Polys;
1417
coloredScalar_ = ColoredScalar::Elevation;
1518
}
1619

1720
// ─────────────────────────────────────────────────────────────────────────────
18-
// assemblePipeline override — apply surface-specific defaults
21+
// assemblePipeline surface defaults, then base-class assembly
1922
// ─────────────────────────────────────────────────────────────────────────────
23+
2024
void SurfaceDataItem::assemblePipeline(Pipeline *pipeline) {
21-
// Ensure surface defaults are set before the base class builds the pipeline
2225
surfaceRenderType_ = SurfaceRenderType::Polys;
2326
coloredScalar_ = ColoredScalar::Elevation;
27+
// Base class calls connectDataset() → applyColoredScalar() → applyRenderType()
28+
// etc., in that order. connectDataset() is overridden below and will
29+
// initialise qualitySource_ before applyColoredScalar() calls
30+
// dataOutputPort() (also overridden below).
2431
TopoDataItem::assemblePipeline(pipeline);
2532
}
2633

2734
// ─────────────────────────────────────────────────────────────────────────────
28-
// onRegionSelected — extend selection to full Z range, emit signal
35+
// connectDataset — wire quality source AFTER base wires the shared polyData
36+
//
37+
// IMPORTANT: does NOT change pipeline->source_ or pipeline->polyData_.
38+
// The rubber-band area picker uses pipeline_->polyData_ directly; keeping it
39+
// pointed at dataset_->polyData() ensures extraction of all points (good and
40+
// bad) for the EditDataItem to edit.
2941
// ─────────────────────────────────────────────────────────────────────────────
30-
void SurfaceDataItem::onRegionSelected(double worldBounds[6]) {
3142

32-
qDebug() << "SurfaceDataItem::onRegionSelected(): dataset_=" << dataset_;
43+
bool SurfaceDataItem::connectDataset(Pipeline *pipeline) {
44+
// Base: sets pipeline->source_->SetOutput(dataset_->polyData())
45+
// and pipeline->polyData_ = dataset_->polyData().
46+
// Returns false if dataset is not yet loaded.
47+
if (!TopoDataItem::connectDataset(pipeline)) return false;
48+
49+
// Build the initial good-only geometry (at load time every point is GOOD,
50+
// so this is a full copy) and wire qualitySource_ to it.
51+
rebuildSurfacePolyData();
52+
qualitySource_->SetOutput(surfacePolyData_);
53+
54+
qDebug() << "SurfaceDataItem::connectDataset(): qualitySource_ wired,"
55+
<< surfacePolyData_->GetNumberOfCells() << "cells in render copy";
56+
57+
return true;
58+
}
59+
60+
// ─────────────────────────────────────────────────────────────────────────────
61+
// dataOutputPort — serve the good-only copy to the rendering stages
62+
// ─────────────────────────────────────────────────────────────────────────────
63+
64+
vtkAlgorithmOutput *SurfaceDataItem::dataOutputPort(Pipeline *pipeline) {
65+
(void)pipeline;
66+
return qualitySource_->GetOutputPort();
67+
}
68+
69+
// ─────────────────────────────────────────────────────────────────────────────
70+
// onQualityChanged — rebuild the good-only surface and re-render
71+
// ─────────────────────────────────────────────────────────────────────────────
72+
73+
void SurfaceDataItem::onQualityChanged() {
74+
// qualityChanged() is emitted on the render thread; this slot is connected
75+
// with Qt::QueuedConnection so it runs on the main thread first, then we
76+
// dispatch VTK work back to the render thread.
77+
dispatch_async([this](vtkRenderWindow *rw, vtkUserData) {
78+
if (!dataset_ || !dataset_->isLoaded()) {
79+
rw->Render();
80+
return;
81+
}
82+
rebuildSurfacePolyData();
83+
rw->Render();
84+
});
85+
}
86+
87+
// ─────────────────────────────────────────────────────────────────────────────
88+
// rebuildSurfacePolyData — core quality-hiding logic (render thread only)
89+
//
90+
// Shallow-copies the shared polyData so that points, point data, and cell
91+
// data arrays are shared (no data duplication). Then replaces the polys
92+
// array with a new vtkCellArray that omits every cell whose vertices include
93+
// at least one BAD_DATA point. The result is a geometrically consistent
94+
// surface with holes where bad soundings were removed.
95+
// ─────────────────────────────────────────────────────────────────────────────
96+
97+
void SurfaceDataItem::rebuildSurfacePolyData() {
98+
vtkPolyData *src = dataset_->polyData();
99+
100+
// ShallowCopy: new container object, shared underlying array references.
101+
// We immediately replace the polys array, so the source polys are not
102+
// aliased after this function returns.
103+
surfacePolyData_->ShallowCopy(src);
104+
105+
vtkIntArray *qa = vtkIntArray::SafeDownCast(
106+
src->GetPointData()->GetArray(DATA_QUALITY_NAME));
107+
108+
if (!qa) {
109+
// No quality array yet — nothing to hide, leave the full surface.
110+
surfacePolyData_->Modified();
111+
return;
112+
}
113+
114+
// Walk source cells; include only those with ALL good vertices.
115+
vtkNew<vtkCellArray> goodPolys;
116+
vtkNew<vtkIdList> ptIds;
117+
118+
const vtkIdType nCells = src->GetNumberOfCells();
119+
vtkIdType nRemoved = 0;
33120

34-
if (!dataset_ || !dataset_->isLoaded()) return;
35-
const double xMin = worldBounds[0];
36-
const double xMax = worldBounds[1];
37-
const double yMin = worldBounds[2];
38-
const double yMax = worldBounds[3];
121+
for (vtkIdType ci = 0; ci < nCells; ++ci) {
122+
src->GetCellPoints(ci, ptIds);
39123

40-
// Extend Z to cover the full dataset elevation range, scaled by the
41-
// current vertical exaggeration so the clip volume matches what the user
42-
// sees. A small outward margin avoids clipping surface-level points.
43-
const double zCenter = 0.5 * (dataset_->elevMin() + dataset_->elevMax());
44-
const double zHalf = 0.5 * (dataset_->elevMax() - dataset_->elevMin())
45-
* verticalExagg_;
46-
const double margin = zHalf * 0.05; // 5% margin
124+
bool anyBad = false;
125+
for (vtkIdType pi = 0; pi < ptIds->GetNumberOfIds(); ++pi) {
126+
if (qa->GetValue(ptIds->GetId(pi)) == BAD_DATA) {
127+
anyBad = true;
128+
break;
129+
}
130+
}
47131

48-
const double zMin = zCenter - zHalf - margin;
49-
const double zMax = zCenter + zHalf + margin;
132+
if (!anyBad) {
133+
goodPolys->InsertNextCell(ptIds);
134+
} else {
135+
++nRemoved;
136+
}
137+
}
138+
139+
surfacePolyData_->SetPolys(goodPolys);
140+
surfacePolyData_->Modified();
141+
142+
qDebug() << "SurfaceDataItem::rebuildSurfacePolyData():"
143+
<< nCells - nRemoved << "/" << nCells << "cells kept,"
144+
<< nRemoved << "removed (bad vertices)";
145+
}
146+
147+
// ─────────────────────────────────────────────────────────────────────────────
148+
// onRegionSelected — emit editBoundsChanged with the rubber-band XY extent
149+
// ─────────────────────────────────────────────────────────────────────────────
150+
151+
void SurfaceDataItem::onRegionSelected(double worldBounds[6]) {
152+
qDebug() << "SurfaceDataItem::onRegionSelected() CALLED"
153+
<< "x[" << worldBounds[0] << "," << worldBounds[1] << "]"
154+
<< "y[" << worldBounds[2] << "," << worldBounds[3] << "]"
155+
<< "z[" << worldBounds[4] << "," << worldBounds[5] << "]";
50156

51-
qDebug() << "SurfaceDataItem::onRegionSelected():"
52-
<< "x[" << xMin << "," << xMax << "]"
53-
<< "y[" << yMin << "," << yMax << "]"
54-
<< "z[" << zMin << "," << zMax << "]";
157+
if (!dataset_) {
158+
qWarning() << "SurfaceDataItem::onRegionSelected(): dataset_ is null";
159+
return;
160+
}
161+
if (!dataset_->isLoaded()) {
162+
qWarning() << "SurfaceDataItem::onRegionSelected(): dataset not loaded";
163+
return;
164+
}
55165

56-
qDebug() << "emit editBoundsChanged()";
57-
emit editBoundsChanged(xMin, xMax, yMin, yMax, zMin, zMax);
166+
// Emit directly; Qt AutoConnection queues the call cross-thread if needed.
167+
emit editBoundsChanged(worldBounds[0], worldBounds[1],
168+
worldBounds[2], worldBounds[3],
169+
worldBounds[4], worldBounds[5]);
58170
}

src/qt-guilib/SurfaceDataItem.h

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,81 @@
66
namespace mb_system {
77

88
/**
9-
SurfaceDataItem is the main-window view. It renders the full dataset as a
10-
shaded polygon surface (default SurfaceRenderType::Polys) and lets the user
11-
draw a rubber-band rectangle to define the volume that will appear in the
12-
companion EditDataItem.
9+
SurfaceDataItem — main-window view of the full bathymetric dataset.
10+
11+
Rendering: polygon surface (SurfaceRenderType::Polys), elevation coloring.
12+
13+
Bad-point hiding
14+
────────────────
15+
The surface view maintains its own render-only copy of the dataset geometry
16+
(surfacePolyData_) that contains only cells whose vertices are all GOOD.
17+
A companion vtkTrivialProducer (qualitySource_) wraps that copy and is
18+
returned by dataOutputPort() so the mapper, normals filter, contour filter,
19+
etc. see the filtered geometry.
20+
21+
connectDataset() is overridden ONLY to initialise surfacePolyData_ and
22+
qualitySource_; it does NOT change pipeline_->source_ or pipeline_->polyData_,
23+
so the rubber-band area picker (PointsSelectInteractorStyle) continues to
24+
operate on the full shared dataset and can select bad points for editing.
25+
26+
When EditDataItem marks a point Bad/Good, TopoDataset emits qualityChanged().
27+
onQualityChanged() dispatches rebuildSurfacePolyData() to the render thread,
28+
which rebuilds the cell array to exclude bad-vertex cells, marks the copy
29+
Modified, and re-renders.
1330
1431
Selection flow
1532
──────────────
16-
1. User switches to MouseDataSelect mode and draws a rectangle.
17-
2. The PointsSelectInteractorStyle / MyRubberBandStyle calls
18-
onRegionSelected(worldBounds) on this item via the TopoDataItem* pointer
19-
it already holds (virtual dispatch ensures the override runs).
20-
3. onRegionSelected() extends the XY selection bounds to the full dataset Z
21-
range (respecting vertical exaggeration) and emits editBoundsChanged().
22-
4. QML connects editBoundsChanged() to EditDataItem::setEditBounds() on the
23-
companion window.
33+
1. User alt-drags a rubber band → PointsSelectInteractorStyle picks from
34+
pipeline_->polyData_ (= dataset_->polyData(), unchanged).
35+
2. onRegionSelected(worldBounds) emits editBoundsChanged().
36+
3. QML shows the EditDataItem window and calls setEditBounds().
2437
*/
2538
class SurfaceDataItem : public TopoDataItem {
2639
Q_OBJECT
2740

2841
public:
2942
SurfaceDataItem();
3043

31-
/// Called by the region-selection interactor style when the user completes a
32-
/// rubber-band selection. worldBounds = {xMin,xMax,yMin,yMax,zMin,zMax}
33-
/// of the selection's world-space bounding box projected onto the surface.
34-
/// Extends the Z extents to cover the full dataset (±margin for vertical
35-
/// exaggeration) and emits editBoundsChanged().
3644
void onRegionSelected(double worldBounds[6]) override;
3745

3846
signals:
39-
/// Emitted after onRegionSelected(). QML connects this to
40-
/// EditDataItem::setEditBounds() on the edit window.
4147
void editBoundsChanged(double xMin, double xMax,
4248
double yMin, double yMax,
4349
double zMin, double zMax);
4450

4551
protected:
4652
void assemblePipeline(Pipeline *pipeline) override;
53+
54+
/// Extends base connectDataset(): after the base wires pipeline_->source_
55+
/// and pipeline_->polyData_ to dataset_->polyData() (unchanged), initialises
56+
/// surfacePolyData_ with good-only geometry and wires qualitySource_ to it.
57+
bool connectDataset(Pipeline *pipeline) override;
58+
59+
/// Returns qualitySource_->GetOutputPort() so all downstream rendering
60+
/// stages (mapper, normals, contours) consume the good-only copy.
61+
/// pipeline_->polyData_ is never touched — rubber-band works against the
62+
/// full dataset as before.
63+
vtkAlgorithmOutput *dataOutputPort(Pipeline *pipeline) override;
64+
65+
protected slots:
66+
/// Rebuilds surfacePolyData_ (good cells only) on the render thread and
67+
/// triggers a re-render of the surface view.
68+
void onQualityChanged() override;
69+
70+
private:
71+
/// Called on the render thread. Shallow-copies dataset_->polyData() into
72+
/// surfacePolyData_, then replaces the polys array with a new cell array
73+
/// that omits any cell containing at least one BAD_DATA vertex.
74+
void rebuildSurfacePolyData();
75+
76+
/// Render-only geometry: good cells only. Points array is shared with
77+
/// dataset_->polyData() via ShallowCopy; the polys array is rebuilt on
78+
/// every quality change.
79+
vtkNew<vtkPolyData> surfacePolyData_;
80+
81+
/// Wraps surfacePolyData_ as a VTK pipeline source so downstream filters
82+
/// can use SetInputConnection().
83+
vtkNew<vtkTrivialProducer> qualitySource_;
4784
};
4885

4986
} // namespace mb_system

0 commit comments

Comments
 (0)