feat/vtk-3d-view #7
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
#include <fstream>
|
||||
|
||||
#include <vtkCamera.h>
|
||||
#include <vtkDataArray.h>
|
||||
#include <vtkImageData.h>
|
||||
#include <vtkNew.h>
|
||||
#include <vtkPNGWriter.h>
|
||||
#include <vtkPointData.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkRendererCollection.h>
|
||||
#include <vtkWindowToImageFilter.h>
|
||||
|
||||
namespace geopro::app {
|
||||
|
|
@ -41,6 +44,68 @@ bool captureRenderWindowPng(vtkRenderWindow* win, const std::string& path, int&
|
|||
return writer->GetErrorCode() == 0;
|
||||
}
|
||||
|
||||
bool captureFramedRegionPng(vtkRenderWindow* win, const double regionBounds[6], double padFactor,
|
||||
double minExtent, const std::string& path, int& outW, int& outH) {
|
||||
outW = outH = 0;
|
||||
if (win == nullptr || path.empty()) return false;
|
||||
vtkRenderer* ren =
|
||||
win->GetRenderers() ? win->GetRenderers()->GetFirstRenderer() : nullptr;
|
||||
vtkCamera* cam = ren ? ren->GetActiveCamera() : nullptr;
|
||||
if (ren == nullptr || cam == nullptr)
|
||||
return captureRenderWindowPng(win, path, outW, outH); // 无渲染器 → 退回整窗
|
||||
|
||||
// 1) 区域包围盒:minExtent 兜底(点零体积/共面零厚度) → padFactor 以中心外扩留边距。
|
||||
double b[6];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const double lo = regionBounds[2 * i], hi = regionBounds[2 * i + 1];
|
||||
const double c = 0.5 * (lo + hi);
|
||||
double half = 0.5 * (hi - lo);
|
||||
if (2.0 * half < minExtent) half = 0.5 * minExtent; // 退化轴兜底
|
||||
half *= padFactor; // 外扩边距
|
||||
b[2 * i] = c - half;
|
||||
b[2 * i + 1] = c + half;
|
||||
}
|
||||
|
||||
// 2) 存相机现场(ResetCamera 改 position/focalPoint/clipping/parallelScale)。
|
||||
double pos[3], fp[3], up[3], clip[2];
|
||||
cam->GetPosition(pos);
|
||||
cam->GetFocalPoint(fp);
|
||||
cam->GetViewUp(up);
|
||||
cam->GetClippingRange(clip);
|
||||
const double va = cam->GetViewAngle();
|
||||
const double ps = cam->GetParallelScale();
|
||||
|
||||
// 3) 重构图:保持视角方向,仅推近/缩放框住外扩区域。
|
||||
ren->ResetCamera(b);
|
||||
|
||||
// 4) 截图(后台缓冲 + 关交换 → 屏幕不闪)。
|
||||
vtkNew<vtkWindowToImageFilter> w2i;
|
||||
w2i->SetInput(win);
|
||||
w2i->ReadFrontBufferOff();
|
||||
w2i->Update();
|
||||
if (auto* img = w2i->GetOutput()) {
|
||||
int dims[3];
|
||||
img->GetDimensions(dims);
|
||||
outW = dims[0];
|
||||
outH = dims[1];
|
||||
}
|
||||
vtkNew<vtkPNGWriter> writer;
|
||||
writer->SetFileName(path.c_str());
|
||||
writer->SetInputConnection(w2i->GetOutputPort());
|
||||
writer->Write();
|
||||
const bool ok = writer->GetErrorCode() == 0;
|
||||
|
||||
// 5) 还原相机 + 重绘回原视图。
|
||||
cam->SetPosition(pos);
|
||||
cam->SetFocalPoint(fp);
|
||||
cam->SetViewUp(up);
|
||||
cam->SetViewAngle(va);
|
||||
cam->SetParallelScale(ps);
|
||||
cam->SetClippingRange(clip);
|
||||
win->Render();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool exportSliceDat(vtkImageData* slice, const std::string& path) {
|
||||
if (slice == nullptr || path.empty()) return false;
|
||||
vtkDataArray* arr = slice->GetPointData() ? slice->GetPointData()->GetScalars() : nullptr;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,15 @@ bool exportSliceImagePng(vtkImageData* colorImage, const std::string& path);
|
|||
// 截整个渲染窗口为 PNG(异常标识截图,需求 R88);成功返回 true,并填回截图像素宽高。
|
||||
bool captureRenderWindowPng(vtkRenderWindow* win, const std::string& path, int& outW, int& outH);
|
||||
|
||||
// 「相机重构图」截图(方案A):把相机临时重新取景到 regionBounds(圈定范围)外扩后的区域,
|
||||
// 使异常框在画面中央带周边语境,再截图、还原相机。业界 frame/zoom-to-fit selection 范式。
|
||||
// regionBounds: {xmin,xmax,ymin,ymax,zmin,zmax} 世界系圈定包围盒;
|
||||
// padFactor: 以盒中心外扩的倍数(1.4≈异常占画面~70%);
|
||||
// minExtent: 退化兜底(点=零体积、线/面共面=某轴零厚度)时各轴的最小世界尺寸。
|
||||
// 视角方向不变(仅推近/缩放);屏幕无闪(后台缓冲+关交换)。失败回退整窗截图。
|
||||
bool captureFramedRegionPng(vtkRenderWindow* win, const double regionBounds[6], double padFactor,
|
||||
double minExtent, const std::string& path, int& outW, int& outH);
|
||||
|
||||
// 把切片重采样 2D 标量影像写为 .dat 文本网格(行=j、列=i,空格分隔,每格取标量首分量);成功返回 true。
|
||||
bool exportSliceDat(vtkImageData* slice, const std::string& path);
|
||||
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
anomalyDrawTool->start(
|
||||
o, normal,
|
||||
[&window, sceneView, scene3dRepo, renderWindowPtr, refreshAnomalies, refreshAnalysis,
|
||||
volId, savedSliceId, normal, o](const std::vector<ri::Vec3>& worldPts) {
|
||||
volId, savedSliceId, normal, o, p1, p2](const std::vector<ri::Vec3>& worldPts) {
|
||||
// 草稿异常:先临时渲染(让用户在对话框前看到所画,且截图含异常)。
|
||||
geopro::core::Anomaly a;
|
||||
a.markType = geopro::core::AnomalyMarkType::Polygon;
|
||||
|
|
@ -558,7 +558,23 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
|||
const QString shot =
|
||||
QDir(QDir::tempPath()).filePath(QStringLiteral("geopro_anomaly_shot.png"));
|
||||
int sw = 0, sh = 0;
|
||||
geopro::app::captureRenderWindowPng(renderWindowPtr, shot.toStdString(), sw, sh);
|
||||
// 相机重构图(方案A):框住圈定 worldPts 外扩区域(异常居中带语境);
|
||||
// 点/共面退化用切片尺寸兜底框景半径。
|
||||
double rb[6] = {worldPts[0][0], worldPts[0][0], worldPts[0][1],
|
||||
worldPts[0][1], worldPts[0][2], worldPts[0][2]};
|
||||
for (const auto& p : worldPts) {
|
||||
rb[0] = std::min(rb[0], p[0]); rb[1] = std::max(rb[1], p[0]);
|
||||
rb[2] = std::min(rb[2], p[1]); rb[3] = std::max(rb[3], p[1]);
|
||||
rb[4] = std::min(rb[4], p[2]); rb[5] = std::max(rb[5], p[2]);
|
||||
}
|
||||
auto vlen = [](double x, double y, double z) {
|
||||
return std::sqrt(x * x + y * y + z * z);
|
||||
};
|
||||
const double e1 = vlen(p1[0] - o[0], p1[1] - o[1], p1[2] - o[2]);
|
||||
const double e2 = vlen(p2[0] - o[0], p2[1] - o[1], p2[2] - o[2]);
|
||||
const double minExt = 0.25 * std::min(e1, e2); // 点/线退化框景半径
|
||||
geopro::app::captureFramedRegionPng(renderWindowPtr, rb, 1.4, minExt,
|
||||
shot.toStdString(), sw, sh);
|
||||
geopro::app::AnomalySaveDialog dlg(shot, sw, sh, &window);
|
||||
if (dlg.exec() != QDialog::Accepted) {
|
||||
sceneView->removeAnomaly(draftId);
|
||||
|
|
|
|||
Loading…
Reference in New Issue