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/nmVolumeDataBuilder.cpp

234 lines
12 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmVolumeDataBuilder.h"
#include <QCoreApplication>
#include <QMap>
#include <QUuid>
#include <limits>
#include <new>
#include <vtkCellArray.h>
#include <vtkCellData.h>
#include <vtkCellType.h>
#include <vtkDataArray.h>
#include <vtkDoubleArray.h>
#include <vtkIdTypeArray.h>
#include <vtkMath.h>
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkUnsignedCharArray.h>
#include <vtkUnstructuredGrid.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", "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<const nmVolumeData> 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<nmVolumeData> 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<vtkUnstructuredGrid> pStructure = vtkSmartPointer<vtkUnstructuredGrid>::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<vtkDataSetAttributes*>(pGrid->GetCellData())
: static_cast<vtkDataSetAttributes*>(pGrid->GetPointData());
vtkDataSetAttributes* pTarget = nAssociation == 0
? static_cast<vtkDataSetAttributes*>(pCandidate->m_pGrid->GetCellData())
: static_cast<vtkDataSetAttributes*>(pCandidate->m_pGrid->GetPointData());
const vtkIdType nTupleCount = nAssociation == 0 ? nCellCount : nPointCount;
QMap<QString, int> 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<vtkDoubleArray> pValues = vtkSmartPointer<vtkDoubleArray>::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<double>::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<const nmVolumeData>();
}