#include "nmVolumeDataImporter.h" #include "nmVolumeDataBuilder.h" #include #include #include #include #include #include #include #include #include #include #include #include #include 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(pClientData); if(nEvent == vtkCommand::ErrorEvent) { pState->m_bFailed = true; if(pCallData != nullptr) pState->m_sDetail = QString::fromLocal8Bit(static_cast(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 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 pObserver = vtkSmartPointer::New(); pObserver->SetClientData(&oState); pObserver->SetCallback(handleReaderEvent); vtkSmartPointer pReader; vtkSmartPointer pGrid; if(sSuffix == QLatin1String("vtk")) { vtkSmartPointer pLegacy = vtkSmartPointer::New(); pReader = pLegacy; pLegacy->SetFileName(aReaderPath.constData()); pLegacy->ReadAllScalarsOn(); pLegacy->ReadAllFieldsOn(); } else { vtkSmartPointer pXml = vtkSmartPointer::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(); }