|
|
|
|
|
#include "nmVolumeSlicePipeline.h"
|
|
|
|
|
|
#include "nmVolumeDataRenderAdapter.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
|
|
#include <new>
|
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
#include <vtkAlgorithm.h>
|
|
|
|
|
|
#include <vtkCallbackCommand.h>
|
|
|
|
|
|
#include <vtkCellData.h>
|
|
|
|
|
|
#include <vtkCommand.h>
|
|
|
|
|
|
#include <vtkCutter.h>
|
|
|
|
|
|
#include <vtkDataArray.h>
|
|
|
|
|
|
#include <vtkErrorCode.h>
|
|
|
|
|
|
#include <vtkMath.h>
|
|
|
|
|
|
#include <vtkPlane.h>
|
|
|
|
|
|
#include <vtkPointData.h>
|
|
|
|
|
|
#include <vtkPolyData.h>
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
/** @brief 记录 VTK 错误;回调只访问本次同步计算的栈标记,不接触界面。 */
|
|
|
|
|
|
void recordSliceError(vtkObject*, unsigned long, void* pClientData, void*)
|
|
|
|
|
|
{
|
|
|
|
|
|
*static_cast<bool*>(pClientData) = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmVolumeSlicePipeline::build(const QSharedPointer<const nmVolumeData>& pData,
|
|
|
|
|
|
const nmVolumeSliceSettings& oSettings, vtkSmartPointer<vtkPolyData>& pOutput,
|
|
|
|
|
|
QString* pError)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(pError != nullptr) pError->clear();
|
|
|
|
|
|
nmVolumeSliceSettings oNormalized = oSettings;
|
|
|
|
|
|
if(!oNormalized.normalize(pError)) return false;
|
|
|
|
|
|
if(pData.isNull()) {
|
|
|
|
|
|
if(pError != nullptr) *pError = QCoreApplication::translate("nmVolumeSlicePipeline", "No volume model is available.");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
nmVolumeDataRenderAdapter oAdapter(pData);
|
|
|
|
|
|
bool bFailed = false;
|
|
|
|
|
|
vtkSmartPointer<vtkCallbackCommand> pObserver = vtkSmartPointer<vtkCallbackCommand>::New();
|
|
|
|
|
|
pObserver->SetClientData(&bFailed);
|
|
|
|
|
|
pObserver->SetCallback(recordSliceError);
|
|
|
|
|
|
vtkSmartPointer<vtkPlane> pPlane = vtkSmartPointer<vtkPlane>::New();
|
|
|
|
|
|
pPlane->SetOrigin(oNormalized.m_dOrigin);
|
|
|
|
|
|
pPlane->SetNormal(oNormalized.m_dNormal);
|
|
|
|
|
|
vtkSmartPointer<vtkCutter> pCutter = vtkSmartPointer<vtkCutter>::New();
|
|
|
|
|
|
pCutter->SetInputConnection(oAdapter.getOutputPort());
|
|
|
|
|
|
pCutter->SetCutFunction(pPlane);
|
|
|
|
|
|
pCutter->SetValue(0, 0.0);
|
|
|
|
|
|
pCutter->GenerateCutScalarsOff();
|
|
|
|
|
|
pCutter->GenerateTrianglesOff();
|
|
|
|
|
|
pCutter->SetOutputPointsPrecision(vtkAlgorithm::DOUBLE_PRECISION);
|
|
|
|
|
|
pCutter->AddObserver(vtkCommand::ErrorEvent, pObserver);
|
|
|
|
|
|
pCutter->Update();
|
|
|
|
|
|
pCutter->RemoveObservers(vtkCommand::ErrorEvent, pObserver);
|
|
|
|
|
|
pObserver->SetClientData(nullptr);
|
|
|
|
|
|
vtkPolyData* pResult = pCutter->GetOutput();
|
|
|
|
|
|
if(!bFailed && pResult != nullptr && pCutter->GetErrorCode() == vtkErrorCode::NoError) {
|
|
|
|
|
|
for(vtkIdType nPoint = 0; nPoint < pResult->GetNumberOfPoints(); ++nPoint) {
|
|
|
|
|
|
double dPoint[3];
|
|
|
|
|
|
pResult->GetPoint(nPoint, dPoint);
|
|
|
|
|
|
if(!vtkMath::IsFinite(dPoint[0]) || !vtkMath::IsFinite(dPoint[1]) || !vtkMath::IsFinite(dPoint[2])) {
|
|
|
|
|
|
bFailed = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 空截面可能没有属性数组;非空截面必须完整保留发布数据的属性关联。
|
|
|
|
|
|
const QVector<nmVolumePropertyDescriptor>& vecProperties = pData->getProperties();
|
|
|
|
|
|
for(int nProperty = 0; !bFailed && pResult->GetNumberOfCells() > 0 && nProperty < vecProperties.size(); ++nProperty) {
|
|
|
|
|
|
const nmVolumePropertyDescriptor& oProperty = vecProperties.at(nProperty);
|
|
|
|
|
|
vtkDataSetAttributes* pAttributes = oProperty.m_eAssociation == NM_VolumeProperty_CellData
|
|
|
|
|
|
? static_cast<vtkDataSetAttributes*>(pResult->GetCellData())
|
|
|
|
|
|
: static_cast<vtkDataSetAttributes*>(pResult->GetPointData());
|
|
|
|
|
|
vtkDataArray* pArray = pAttributes->GetArray(oProperty.m_sArrayName.toUtf8().constData());
|
|
|
|
|
|
const vtkIdType nExpected = oProperty.m_eAssociation == NM_VolumeProperty_CellData
|
|
|
|
|
|
? pResult->GetNumberOfCells() : pResult->GetNumberOfPoints();
|
|
|
|
|
|
bFailed = pArray == nullptr || pArray->GetNumberOfComponents() != 1 || pArray->GetNumberOfTuples() != nExpected;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(!bFailed) {
|
|
|
|
|
|
vtkSmartPointer<vtkPolyData> pCandidate = vtkSmartPointer<vtkPolyData>::New();
|
|
|
|
|
|
// 与临时 Cutter 完全断开;后续视图刷新不能重新执行切片。
|
|
|
|
|
|
pCandidate->DeepCopy(pResult);
|
|
|
|
|
|
// 节点插值也可能产生非有限数,只规范化独立输出,绝不写入冻结的原始数组。
|
|
|
|
|
|
for(int nProperty = 0; nProperty < vecProperties.size(); ++nProperty) {
|
|
|
|
|
|
const nmVolumePropertyDescriptor& oProperty = vecProperties.at(nProperty);
|
|
|
|
|
|
vtkDataArray* pArray = oProperty.m_eAssociation == NM_VolumeProperty_CellData
|
|
|
|
|
|
? pCandidate->GetCellData()->GetArray(oProperty.m_sArrayName.toUtf8().constData())
|
|
|
|
|
|
: pCandidate->GetPointData()->GetArray(oProperty.m_sArrayName.toUtf8().constData());
|
|
|
|
|
|
for(vtkIdType nTuple = 0; pArray != nullptr && nTuple < pArray->GetNumberOfTuples(); ++nTuple) {
|
|
|
|
|
|
if(!vtkMath::IsFinite(pArray->GetComponent(nTuple, 0))) {
|
|
|
|
|
|
pArray->SetComponent(nTuple, 0, std::numeric_limits<double>::quiet_NaN());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
pOutput = pCandidate;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if(pError != nullptr) *pError = QCoreApplication::translate("nmVolumeSlicePipeline", "Unable to compute a valid slice. The previous view has been kept.");
|
|
|
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
|
|
|
if(pError != nullptr) *pError = QCoreApplication::translate("nmVolumeSlicePipeline", "Not enough memory to compute the slice.");
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|