Проверка того, что STL файл является ASCII или двоичным
После прочтения спецификаций в формате файла STL, я хочу написать несколько тестов, чтобы убедиться, что файл является фактически действующим двоичным или ASCII файлом.
Файл STL, основанный на ASCII, может быть определен путем нахождения текста " solid" в байте 0, за которым следует пробел (шестнадцатеричное значение \x20
), а затем необязательная текстовая строка, затем по новой строке.
В двоичном файле STL есть зарезервированный заголовок 80 -byte, за которым следует 4 -byte беззнаковое целое число (NumberOfTriangles), а затем 50 байтов данных для каждой из факетов NumberOfTriangles.
Каждый грань треугольника имеет длину 50: 12 одноточечных (4 байтовых) полях с последующим беззнаковым коротким (2 байтовым) целым без знака.
Если двоичный файл в точности длиннее 84 + NumberOfTriangles * 50, его обычно можно считать действительным двоичным файлом.
К сожалению, двоичные файлы могут содержать текст " solid", начиная с байта 0 в содержимом 80-байтового заголовка. Поэтому тест только для этого ключевого слова не может положительно утверждать, что файл является 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;
}
}
До сих пор это работало для меня, но я беспокоюсь, что простой 80-байтовый файл ASCII файла может содержать некоторые символы ASCII, которые при переводе на uint32_t могут фактически равняться длине файла (очень маловероятно, но не невозможно).
Существуют ли дополнительные шаги, которые могут оказаться полезными при проверке того, могу ли я быть абсолютно уверенным в том, что файл является либо ASCII, либо двоичным?
UPDATE:
Следуя советам @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 файлов, не требуя, чтобы файл ENTIRE загружался в память сразу для его сканирования для текста "endolid".
Не стесняйтесь предоставлять любые отзывы и предложения (особенно для людей, которые в будущем ищут решения).
Ответы
Ответ 1
Если файл не начинается с "solid "
, и если размер файла равен 84 + (numTriangles * 50)
байтам, где numTriangles
считывается со смещения 80, тогда файл является двоичным.
Если размер файла не менее 15 байт (абсолютный минимум для файла ASCII без треугольников) и начинается с "solid "
, прочитайте имя, которое следует за ним, пока не будет достигнут разрыв строки. Проверьте, начинается ли следующая строка с "facet "
или "endsolid [name]"
(другое значение не допускается). Если "facet "
, найдите конец файла и убедитесь, что он заканчивается строкой с надписью "endsolid [name]"
. Если все они верны, файл ASCII.
Относитесь к любой другой комбинации как к недействительной.
Итак, что-то вроде этого:
STL_STATUS getStlFileFormat(const QString &path)
{
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();
// Look for text "solid " in first 6 bytes, indicating the possibility that this is an ASCII STL format.
if (fileSize < 15)
{
// "solid " and "endsolid " markers for an ASCII file
qDebug("\n\tThe STL file is not long enough (%u bytes).", uint(fileSize));
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...
QByteArray sixBytes = file.read(6);
if (sixBytes.startsWith("solid "))
{
QByteArray name = file.readLine();
QByteArray endLine = name.prepend("endsolid ");
QByteArray nextLine = file.readLine();
if (line.startsWith("facet "))
{
// TODO: seek to the end of the file, read the last line,
// and make sure it is "endsolid [name]"...
/*
line = ...;
if (!line.startsWith(endLine))
return STL_INVALID;
*/
return STL_ASCII;
}
if (line.startsWith(endLine))
return STL_ASCII;
// reset and check for binary...
if (!file.reset())
{
qDebug("\n\tCannot seek to the 0th byte (before the header)");
return STL_INVALID;
}
}
if (fileSize < 84)
{
// 80-byte header + 4-byte "number of triangles" for a binary file
qDebug("\n\tThe STL file is not long enough (%u bytes).", uint(fileSize));
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)");
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)");
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 * 50)))
return STL_BINARY;
return STL_INVALID;
}
Ответ 2
Существуют ли дополнительные шаги, которые могут оказаться полезными при проверке того, могу ли я быть абсолютно уверенным в том, что файл является либо ASCII, либо двоичным?
Поскольку в спецификациях stl нет тега формата, вы не можете быть абсолютно уверены в формате файла.
Проверка для "твердого" в начале файла должна быть достаточной в большинстве случаев. Кроме того, вы можете проверить дополнительные ключевые слова, такие как "facet" или "vertex", чтобы убедиться в этом ASCII. Эти слова должны встречаться только в формате ASCII (или в бесполезном двоичном заголовке), но есть немного шансов, что двоичные поплавки по совпадению образуют эти слова. Таким образом, вы также можете проверить, находятся ли ключевые слова в правильном порядке.
И, конечно, проверьте, соответствует ли длина в двоичном заголовке длине файла.
Но: ваш код будет работать быстрее, если вы читаете файл линейным и надеетесь, что никто не добавит слова "solid" в двоичный заголовок. Возможно, вам следует предпочесть ASCII-синтаксический анализ, если файл начинается с "solid" и использует двоичный синтаксический анализатор в качестве резервной копии, если разбор ASCII не выполняется.