【发布时间】:2018-03-15 05:34:19
【问题描述】:
在阅读了关于 STL 文件格式的specs 之后,我想写一些测试来确保一个文件实际上是一个有效的二进制或 ASCII 文件。
可以通过在字节 0 处找到文本“solid”,然后是空格(十六进制值 \x20),然后是可选的文本字符串来确定基于 ASCII 的 STL 文件,然后换行符。
二进制 STL 文件有一个保留的 80 字节头,后跟一个 4 字节的无符号整数 (NumberOfTriangles),然后50 个字节的数据,用于指定的每个 NumberOfTriangles 个方面。
每个三角形面的长度为 50 字节:12 个单精度(4 字节)浮点数,后跟一个无符号短(2 字节)无符号整数。
如果一个二进制文件的长度正好是 84 + NumberOfTriangles*50 个字节,那么它通常可以被认为是一个有效的二进制文件。
不幸的是,二进制文件可以包含文本“solid”,该文本从 80 字节标题的内容中的字节 0 开始。因此,仅针对该关键字的测试不能肯定地判断文件是 ASCII 还是二进制文件。
这是我目前所拥有的:
STL_STATUS getStlFileFormat(const QString &path)
{
// Each facet contains:
// - Normals: 3 floats (4 bytes)
// - Vertices: 3x floats (4 bytes each, 12 bytes total)
// - AttributeCount: 1 short (2 bytes)
// Total: 50 bytes per facet
const size_t facetSize = 3*sizeof(float_t) + 3*3*sizeof(float_t) + sizeof(uint16_t);
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
{
qDebug("\n\tUnable to open \"%s\"", qPrintable(path));
return STL_INVALID;
}
QFileInfo fileInfo(path);
size_t fileSize = fileInfo.size();
if (fileSize < 84)
{
// 80-byte header + 4-byte "number of triangles" marker
qDebug("\n\tThe STL file is not long enough (%u bytes).", uint(fileSize));
return STL_INVALID;
}
// Look for text "solid" in first 5 bytes, indicating the possibility that this is an ASCII STL format.
QByteArray fiveBytes = file.read(5);
// Header is from bytes 0-79; numTriangleBytes starts at byte offset 80.
if (!file.seek(80))
{
qDebug("\n\tCannot seek to the 80th byte (after the header)");
return STL_INVALID;
}
// Read the number of triangles, uint32_t (4 bytes), little-endian
QByteArray nTrianglesBytes = file.read(4);
file.close();
uint32_t nTriangles = *((uint32_t*)nTrianglesBytes.data());
// Verify that file size equals the sum of header + nTriangles value + all triangles
size_t targetSize = 84 + nTriangles * facetSize;
if (fileSize == targetSize)
{
return STL_BINARY;
}
else if (fiveBytes.contains("solid"))
{
return STL_ASCII;
}
else
{
return STL_INVALID;
}
}
到目前为止,这对我有用,但我担心纯 ASCII 文件的第 80 个字节可能包含一些 ASCII 字符,当转换为 uint32_t 时,这些字符实际上可能等于文件的长度(不太可能,但并非不可能)。
在验证我是否可以“绝对确定”文件是 ASCII 还是二进制文件时,是否有其他步骤有用?
更新:
按照@Powerswitch 和@RemyLebeau 的建议,我正在对关键字进行进一步的测试。这就是我现在得到的:
STL_STATUS getStlFileFormat(const QString &path)
{
// Each facet contains:
// - Normals: 3 floats (4 bytes)
// - Vertices: 3x floats (4 byte each, 12 bytes total)
// - AttributeCount: 1 short (2 bytes)
// Total: 50 bytes per facet
const size_t facetSize = 3*sizeof(float_t) + 3*3*sizeof(float_t) + sizeof(uint16_t);
QFile file(path);
bool canFileBeOpened = file.open(QIODevice::ReadOnly);
if (!canFileBeOpened)
{
qDebug("\n\tUnable to open \"%s\"", qPrintable(path));
return STL_INVALID;
}
QFileInfo fileInfo(path);
size_t fileSize = fileInfo.size();
// The minimum size of an empty ASCII file is 15 bytes.
if (fileSize < 15)
{
// "solid " and "endsolid " markers for an ASCII file
qDebug("\n\tThe STL file is not long enough (%u bytes).", uint(fileSize));
file.close();
return STL_INVALID;
}
// Binary files should never start with "solid ", but just in case, check for ASCII, and if not valid
// then check for binary...
// Look for text "solid " in first 6 bytes, indicating the possibility that this is an ASCII STL format.
QByteArray sixBytes = file.read(6);
if (sixBytes.startsWith("solid "))
{
QString line;
QTextStream in(&file);
while (!in.atEnd())
{
line = in.readLine();
if (line.contains("endsolid"))
{
file.close();
return STL_ASCII;
}
}
}
// Wasn't an ASCII file. Reset and check for binary.
if (!file.reset())
{
qDebug("\n\tCannot seek to the 0th byte (before the header)");
file.close();
return STL_INVALID;
}
// 80-byte header + 4-byte "number of triangles" for a binary file
if (fileSize < 84)
{
qDebug("\n\tThe STL file is not long enough (%u bytes).", uint(fileSize));
file.close();
return STL_INVALID;
}
// Header is from bytes 0-79; numTriangleBytes starts at byte offset 80.
if (!file.seek(80))
{
qDebug("\n\tCannot seek to the 80th byte (after the header)");
file.close();
return STL_INVALID;
}
// Read the number of triangles, uint32_t (4 bytes), little-endian
QByteArray nTrianglesBytes = file.read(4);
if (nTrianglesBytes.size() != 4)
{
qDebug("\n\tCannot read the number of triangles (after the header)");
file.close();
return STL_INVALID;
}
uint32_t nTriangles = *((uint32_t*)nTrianglesBytes.data());
// Verify that file size equals the sum of header + nTriangles value + all triangles
if (fileSize == (84 + (nTriangles * facetSize)))
{
file.close();
return STL_BINARY;
}
return STL_INVALID;
}
它似乎可以处理更多的边缘情况,我尝试以一种可以优雅地处理超大(几千兆字节)STL 文件的方式编写它,而不需要将整个文件立即加载到内存中进行扫描对于“endsolid”文本。
随时提供任何反馈和建议(尤其是对于未来寻求解决方案的人)。
【问题讨论】:
-
维基百科文章说 二进制 STL 文件有一个 80 个字符的标题(通常会被忽略,但绝不应以“solid”开头,因为这会导致大多数软件认为这是一个 ASCII STL 文件)
-
是的。但是在测试从像Thingiverse 这样的地方下载的随机 STL 文件时,那里的许多二进制 STL 文件实际上确实以“solid”开头,即使它们“不应该”。因此,检查前 5-6 个字节并不总是有保证的原因之一。