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++

#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>();
}