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.
153 lines
7.7 KiB
C++
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>();
|
|
}
|