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