You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmData/nmVolumeDataImporter.cpp

153 lines
7.7 KiB
C++

#include "nmVolumeDataImporter.h"
#include "nmVolumeDataBuilder.h"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTemporaryFile>
#include <new>
#include <vtkAlgorithm.h>
#include <vtkCallbackCommand.h>
#include <vtkCommand.h>
#include <vtkErrorCode.h>
#include <vtkUnstructuredGrid.h>
#include <vtkUnstructuredGridReader.h>
#include <vtkXMLUnstructuredGridReader.h>
namespace {
// 固定文本标记供 Qt 4 lupdate 提取;运行期通过统一上下文翻译。
static const char* const s_pVolumeTranslationMarkers[] = {
QT_TRANSLATE_NOOP("nmVolumeData", "Not enough memory to import the volume mesh."),
QT_TRANSLATE_NOOP("nmVolumeData", "Select a .vtk or .vtu volume mesh file."),
QT_TRANSLATE_NOOP("nmVolumeData", "The temporary directory path cannot be read by VTK."),
QT_TRANSLATE_NOOP("nmVolumeData", "The volume mesh file is empty or cannot be opened."),
QT_TRANSLATE_NOOP("nmVolumeData", "Unable to create a temporary file for this path."),
QT_TRANSLATE_NOOP("nmVolumeData", "Unable to read the volume mesh file."),
QT_TRANSLATE_NOOP("nmVolumeData", "VTK could not read the volume mesh: %1"),
QT_TRANSLATE_NOOP("nmVolumeData", "Volume import canceled.")
};
QString importText(const char* pText)
{
Q_UNUSED(s_pVolumeTranslationMarkers);
return QCoreApplication::translate("nmVolumeData", pText);
}
struct ReaderState
{
nmVolumeImportFeedback* m_pFeedback; ///< 非所有权反馈接口,生命周期覆盖 Reader。
bool m_bFailed; ///< 任一 ErrorEvent 都使本次候选永久失败。
QString m_sDetail; ///< VTK 原始诊断,仅用作错误详情显示。
};
/** @brief 在读取线程内处理 VTK 错误和进度;回调绝不访问 GUI。 */
void handleReaderEvent(vtkObject* pCaller, unsigned long nEvent,
void* pClientData, void* pCallData)
{
ReaderState* pState = static_cast<ReaderState*>(pClientData);
if(nEvent == vtkCommand::ErrorEvent) {
pState->m_bFailed = true;
if(pCallData != nullptr) pState->m_sDetail = QString::fromLocal8Bit(static_cast<const char*>(pCallData)).trimmed();
}
vtkAlgorithm* pAlgorithm = vtkAlgorithm::SafeDownCast(pCaller);
if(pAlgorithm != nullptr && pState->m_pFeedback != nullptr) {
if(pState->m_pFeedback->isCanceled()) pAlgorithm->AbortExecuteOn();
if(nEvent == vtkCommand::ProgressEvent) {
pState->m_pFeedback->reportProgress(10 + qBound(0, int(pAlgorithm->GetProgress() * 45.0), 45));
}
}
}
}
QSharedPointer<const nmVolumeData> nmVolumeDataImporter::loadFile(
const QString& sFilePath, QString* pError, nmVolumeImportFeedback* pFeedback)
{
if(pError != nullptr) pError->clear();
QString sError;
try {
do {
const QFileInfo oFileInfo(sFilePath);
const QString sSuffix = oFileInfo.suffix().toLower();
if(sSuffix != QLatin1String("vtk") && sSuffix != QLatin1String("vtu")) {
sError = importText("Select a .vtk or .vtu volume mesh file.");
break;
}
QFile oSource(sFilePath);
if(!oSource.open(QIODevice::ReadOnly) || oSource.size() == 0) {
sError = importText("The volume mesh file is empty or cannot be opened.");
break;
}
// VTK 7.1 的文件流使用本地窄字符串。不可往返的路径通过 Qt 复制到临时
// 文件,保持二进制内容不变,并以流式复制避免额外占用整文件内存。
QString sReaderPath = oFileInfo.absoluteFilePath();
QTemporaryFile oTemporary(QDir::tempPath() + QLatin1String("/nm_volume_XXXXXX.") + sSuffix);
QByteArray aReaderPath = QFile::encodeName(sReaderPath);
if(QFile::decodeName(aReaderPath) != sReaderPath) {
if(!oTemporary.open()) {
sError = importText("Unable to create a temporary file for this path.");
break;
}
while(!oSource.atEnd() && !(pFeedback != nullptr && pFeedback->isCanceled())) {
const QByteArray aChunk = oSource.read(1024 * 1024);
if(aChunk.isEmpty() || oTemporary.write(aChunk) != aChunk.size()) {
sError = importText("Unable to read the volume mesh file.");
break;
}
}
if(!oTemporary.flush()) sError = importText("Unable to read the volume mesh file.");
sReaderPath = oTemporary.fileName();
oTemporary.close();
aReaderPath = QFile::encodeName(sReaderPath);
if(QFile::decodeName(aReaderPath) != sReaderPath) sError = importText("The temporary directory path cannot be read by VTK.");
}
if(!sError.isEmpty() || (pFeedback != nullptr && pFeedback->isCanceled())) break;
ReaderState oState;
oState.m_pFeedback = pFeedback;
oState.m_bFailed = false;
vtkSmartPointer<vtkCallbackCommand> pObserver = vtkSmartPointer<vtkCallbackCommand>::New();
pObserver->SetClientData(&oState);
pObserver->SetCallback(handleReaderEvent);
vtkSmartPointer<vtkAlgorithm> pReader;
vtkSmartPointer<vtkUnstructuredGrid> pGrid;
if(sSuffix == QLatin1String("vtk")) {
vtkSmartPointer<vtkUnstructuredGridReader> pLegacy = vtkSmartPointer<vtkUnstructuredGridReader>::New();
pReader = pLegacy;
pLegacy->SetFileName(aReaderPath.constData());
pLegacy->ReadAllScalarsOn();
pLegacy->ReadAllFieldsOn();
} else {
vtkSmartPointer<vtkXMLUnstructuredGridReader> pXml = vtkSmartPointer<vtkXMLUnstructuredGridReader>::New();
pReader = pXml;
pXml->SetFileName(aReaderPath.constData());
}
pReader->AddObserver(vtkCommand::ErrorEvent, pObserver);
pReader->AddObserver(vtkCommand::ProgressEvent, pObserver);
pReader->UpdateInformation();
vtkXMLUnstructuredGridReader* pXml = vtkXMLUnstructuredGridReader::SafeDownCast(pReader);
if(pXml != nullptr && !oState.m_bFailed) {
for(int nIndex = 0; nIndex < pXml->GetNumberOfPointArrays(); ++nIndex) pXml->SetPointArrayStatus(pXml->GetPointArrayName(nIndex), 1);
for(int nIndex = 0; nIndex < pXml->GetNumberOfCellArrays(); ++nIndex) pXml->SetCellArrayStatus(pXml->GetCellArrayName(nIndex), 1);
}
if(!oState.m_bFailed && !(pFeedback != nullptr && pFeedback->isCanceled())) pReader->Update();
// 观察者引用的是栈状态;必须在状态离开作用域前解除回调。
pReader->RemoveObservers(vtkCommand::ErrorEvent, pObserver);
pReader->RemoveObservers(vtkCommand::ProgressEvent, pObserver);
pObserver->SetClientData(nullptr);
if(pFeedback != nullptr && pFeedback->isCanceled()) break;
if(oState.m_bFailed || pReader->GetErrorCode() != vtkErrorCode::NoError) {
sError = importText("VTK could not read the volume mesh: %1").arg(oState.m_sDetail);
break;
}
pGrid = vtkUnstructuredGrid::SafeDownCast(pReader->GetOutputDataObject(0));
return nmVolumeDataBuilder::buildFromGrid(pGrid, oFileInfo.fileName(), pError, pFeedback);
} while(false);
} catch(const std::bad_alloc&) {
sError = importText("Not enough memory to import the volume mesh.");
}
if(pError != nullptr) {
*pError = pFeedback != nullptr && pFeedback->isCanceled()
? importText("Volume import canceled.") : sError;
}
return QSharedPointer<const nmVolumeData>();
}