#include "nmVolumeDataBuilder.h" #include #include #include #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", "Only linear tetrahedron, voxel, hexahedron, wedge and pyramid volume cells are supported."), QT_TRANSLATE_NOOP("nmVolumeData", "Skipped invalid scalar array: %1"), QT_TRANSLATE_NOOP("nmVolumeData", "Skipped scalar array without finite values: %1"), QT_TRANSLATE_NOOP("nmVolumeData", "The file contains no volume mesh."), QT_TRANSLATE_NOOP("nmVolumeData", "The mesh connectivity is invalid."), QT_TRANSLATE_NOOP("nmVolumeData", "The mesh contains an invalid point index."), QT_TRANSLATE_NOOP("nmVolumeData", "The mesh contains non-finite coordinates."), QT_TRANSLATE_NOOP("nmVolumeData", "The mesh contains repeated cell vertices."), QT_TRANSLATE_NOOP("nmVolumeData", "The mesh must have a finite extent in all three directions."), QT_TRANSLATE_NOOP("nmVolumeData", "Volume import canceled.") }; /** @brief 数据层使用固定翻译上下文,避免依赖界面类。 */ QString volumeText(const char* pText) { Q_UNUSED(s_pVolumeTranslationMarkers); return QCoreApplication::translate("nmVolumeData", pText); } /** @brief 可选反馈的取消查询,适用于所有较长的校验和属性复制循环。 */ bool canceled(nmVolumeImportFeedback* pFeedback) { return pFeedback != nullptr && pFeedback->isCanceled(); } /** @brief 返回首版支持体单元的固定顶点数;零值表示明确不支持。 */ int expectedPointCount(int nCellType) { switch(nCellType) { case VTK_TETRA: return 4; case VTK_VOXEL: case VTK_HEXAHEDRON: return 8; case VTK_WEDGE: return 6; case VTK_PYRAMID: return 5; default: return 0; } } } QSharedPointer nmVolumeDataBuilder::buildFromGrid( vtkUnstructuredGrid* pGrid, const QString& sSourceName, QString* pError, nmVolumeImportFeedback* pFeedback) { if(pError != nullptr) pError->clear(); QString sError; try { do { if(canceled(pFeedback)) break; if(pGrid == nullptr || pGrid->GetNumberOfPoints() <= 0 || pGrid->GetNumberOfCells() <= 0) { sError = volumeText("The file contains no volume mesh."); break; } const vtkIdType nPointCount = pGrid->GetNumberOfPoints(); const vtkIdType nCellCount = pGrid->GetNumberOfCells(); vtkCellArray* pCells = pGrid->GetCells(); vtkUnsignedCharArray* pTypes = pGrid->GetCellTypesArray(); vtkIdTypeArray* pLocations = pGrid->GetCellLocationsArray(); vtkIdTypeArray* pConnectivity = pCells == nullptr ? nullptr : pCells->GetData(); if(pConnectivity == nullptr || pTypes == nullptr || pLocations == nullptr || pTypes->GetNumberOfTuples() != nCellCount || pLocations->GetNumberOfTuples() != nCellCount || pTypes->GetNumberOfComponents() != 1 || pLocations->GetNumberOfComponents() != 1 || pConnectivity->GetNumberOfComponents() != 1) { sError = volumeText("The mesh connectivity is invalid."); break; } // 先按原始连接数组边界校验,再调用 VTK 的复制/渲染 API,避免非法索引被解引用。 vtkIdType nOffset = 0; const vtkIdType nConnectivitySize = pConnectivity->GetNumberOfTuples(); for(vtkIdType nCell = 0; nCell < nCellCount; ++nCell) { if((nCell % 4096) == 0 && canceled(pFeedback)) break; const int nExpected = expectedPointCount(pTypes->GetValue(nCell)); if(nExpected == 0) { sError = volumeText("Only linear tetrahedron, voxel, hexahedron, wedge and pyramid volume cells are supported."); break; } if(nOffset >= nConnectivitySize || pLocations->GetValue(nCell) != nOffset || pConnectivity->GetValue(nOffset) != nExpected || nConnectivitySize - nOffset - 1 < nExpected) { sError = volumeText("The mesh connectivity is invalid."); break; } for(int nLocal = 0; nLocal < nExpected; ++nLocal) { const vtkIdType nPoint = pConnectivity->GetValue(nOffset + 1 + nLocal); if(nPoint < 0 || nPoint >= nPointCount) { sError = volumeText("The mesh contains an invalid point index."); break; } // 固定线性体单元中重复顶点通常表示损坏或退化拓扑。 for(int nPrevious = 0; nPrevious < nLocal; ++nPrevious) { if(nPoint == pConnectivity->GetValue(nOffset + 1 + nPrevious)) { sError = volumeText("The mesh contains repeated cell vertices."); break; } } if(!sError.isEmpty()) break; } if(!sError.isEmpty()) break; nOffset += nExpected + 1; } if(canceled(pFeedback) || !sError.isEmpty()) break; if(nOffset != nConnectivitySize) { sError = volumeText("The mesh connectivity is invalid."); break; } for(vtkIdType nPoint = 0; nPoint < nPointCount; ++nPoint) { if((nPoint % 4096) == 0 && canceled(pFeedback)) break; double dPoint[3]; pGrid->GetPoint(nPoint, dPoint); if(!vtkMath::IsFinite(dPoint[0]) || !vtkMath::IsFinite(dPoint[1]) || !vtkMath::IsFinite(dPoint[2])) { sError = volumeText("The mesh contains non-finite coordinates."); break; } } if(canceled(pFeedback) || !sError.isEmpty()) break; QSharedPointer pCandidate(new nmVolumeData); pGrid->GetBounds(pCandidate->m_dBounds); for(int nAxis = 0; nAxis < 3; ++nAxis) { const double dLength = pCandidate->m_dBounds[nAxis * 2 + 1] - pCandidate->m_dBounds[nAxis * 2]; if(!vtkMath::IsFinite(dLength) || dLength <= 0.0) { sError = volumeText("The mesh must have a finite extent in all three directions."); break; } } if(!sError.isEmpty()) break; if(pFeedback != nullptr) pFeedback->reportProgress(65); // 只复制几何拓扑;属性逐项规范化,避免复制无效数组和多分量大载荷。 vtkSmartPointer pStructure = vtkSmartPointer::New(); pStructure->CopyStructure(pGrid); pCandidate->m_pGrid->DeepCopy(pStructure); QString sActiveProperty; for(int nAssociation = 0; nAssociation < 2 && !canceled(pFeedback); ++nAssociation) { vtkDataSetAttributes* pSource = nAssociation == 0 ? static_cast(pGrid->GetCellData()) : static_cast(pGrid->GetPointData()); vtkDataSetAttributes* pTarget = nAssociation == 0 ? static_cast(pCandidate->m_pGrid->GetCellData()) : static_cast(pCandidate->m_pGrid->GetPointData()); const vtkIdType nTupleCount = nAssociation == 0 ? nCellCount : nPointCount; QMap mapNameCounts; for(int nArray = 0; nArray < pSource->GetNumberOfArrays(); ++nArray) { const char* pName = pSource->GetArrayName(nArray); if(pName != nullptr) ++mapNameCounts[QString::fromUtf8(pName)]; } for(int nArray = 0; nArray < pSource->GetNumberOfArrays() && !canceled(pFeedback); ++nArray) { vtkDataArray* pArray = pSource->GetArray(nArray); const char* pName = pSource->GetArrayName(nArray); const QString sName = pName == nullptr ? QString() : QString::fromUtf8(pName); if(pArray == nullptr || sName.isEmpty() || mapNameCounts.value(sName) != 1 || pArray->GetNumberOfComponents() != 1 || pArray->GetNumberOfTuples() != nTupleCount) { pCandidate->m_listDiagnostics.append(volumeText("Skipped invalid scalar array: %1").arg(sName)); continue; } vtkSmartPointer pValues = vtkSmartPointer::New(); pValues->SetName(sName.toUtf8().constData()); pValues->SetNumberOfTuples(nTupleCount); double dMinimum = VTK_DOUBLE_MAX; double dMaximum = -VTK_DOUBLE_MAX; for(vtkIdType nTuple = 0; nTuple < nTupleCount; ++nTuple) { if((nTuple % 4096) == 0 && canceled(pFeedback)) break; double dValue = pArray->GetComponent(nTuple, 0); if(vtkMath::IsFinite(dValue)) { dMinimum = qMin(dMinimum, dValue); dMaximum = qMax(dMaximum, dValue); } else { dValue = std::numeric_limits::quiet_NaN(); } pValues->SetValue(nTuple, dValue); } if(canceled(pFeedback)) break; if(dMinimum > dMaximum) { pCandidate->m_listDiagnostics.append(volumeText("Skipped scalar array without finite values: %1").arg(sName)); continue; } nmVolumePropertyDescriptor oProperty; oProperty.m_eAssociation = nAssociation == 0 ? NM_VolumeProperty_CellData : NM_VolumeProperty_PointData; oProperty.m_sArrayName = sName; oProperty.m_sPropertyId = (nAssociation == 0 ? QString::fromLatin1("cell:") : QString::fromLatin1("point:")) + sName; oProperty.m_dMinimum = dMinimum; oProperty.m_dMaximum = dMaximum; pCandidate->m_vecProperties.append(oProperty); pTarget->AddArray(pValues); if(pArray == pSource->GetScalars() && sActiveProperty.isEmpty()) sActiveProperty = oProperty.m_sPropertyId; // 提前生成范围缓存;GUI 绑定时不再扫描整份标量。 double dCachedRange[2]; pValues->GetRange(dCachedRange); } if(pFeedback != nullptr) pFeedback->reportProgress(80 + nAssociation * 15); } if(canceled(pFeedback)) break; pCandidate->m_sInitialPropertyId = sActiveProperty; if(sActiveProperty.isEmpty() && !pCandidate->m_vecProperties.isEmpty()) { pCandidate->m_sInitialPropertyId = pCandidate->m_vecProperties.first().m_sPropertyId; } pCandidate->m_sDataId = QUuid::createUuid().toString(); pCandidate->m_sSourceName = sSourceName; if(pFeedback != nullptr) pFeedback->reportProgress(100); return pCandidate; } while(false); } catch(const std::bad_alloc&) { sError = volumeText("Not enough memory to import the volume mesh."); } if(pError != nullptr) { *pError = canceled(pFeedback) ? volumeText("Volume import canceled.") : sError; } return QSharedPointer(); }