Подтвердить что ты не робот

Проверка того, что 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".

Не стесняйтесь предоставлять любые отзывы и предложения (особенно для людей, которые в будущем ищут решения).

4b9b3361

Ответ 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 не выполняется.