2014-10-03 1289 views
12

在讀取STL文件格式的specs之後,我想編寫一些測試以確保文件實際上是有效的二進制文件或ASCII文件。驗證STL文件是ASCII還是二進制

基於ASCII的STL文件可以通過尋找文本「固體」在字節0,後跟一個空格(十六進制值\x20),然後任選的文本串,然後換行來確定。

二進制STL文件具有保留字節頭,後跟一個字節無符號整數( NumberOfTriangles),並且對於每個 NumberOfTriangles刻面然後個字節的數據指定。

每個三角形方面是字節的長度:12個單精度(4字節)浮點數,後跟無符號短(2字節)無符號整數。

如果二進制文件正好是84 + NumberOfTriangles * 50字節長,通常可以認爲它是有效的二進制文件。

不幸的是,二進制文件可以包含文本「固體」,在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還是二進制文件?

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文件一次加載到內存中,以便掃描「endsolid」文本。

隨時提供任何反饋和建議(特別是在未來尋找解決方案的人)。

+2

維基百科的文章說,*一個二進制STL文件有80個字符的標題(這通常被忽略,但不應該與「實」開始,因爲這將導致大多數軟件假設這是一個ASCII STL文件)* – 2014-10-03 00:14:44

+2

是真的。但是在測試從[Thingiverse](http://www.thingiverse.com)等地下載的隨機STL文件時,許多二進制STL文件實際上都以「固定」開頭,即使它們「不應該」。所以這是檢查那些第一個5-6字節並不總是保證的原因之一。 – OnlineCop 2014-10-03 16:15:49

回答

8

如果文件未以"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; 
} 
4

是否有額外的步驟證明我是否可以「絕對確定」文件是ASCII還是二進制文件?

由於stl規範中沒有格式標籤,因此不能完全確定文件格式。

在大多數情況下,檢查文件開頭的「固定」應該足夠了。此外,你可以檢查更多的關鍵字,如「facet」或「vertex」,以確保它是ASCII。這些單詞只能以ASCII格式(或無用的二進制頭文件)出現,但二進制浮點數偶然形成這些單詞的可能性很小。因此,您還可以檢查關鍵字是否按正確的順序排列。

當然,檢查二進制文件頭中的長度是否與文件長度匹配。

但是:如果您要讀取文件的線性並希望沒有人在「二進制頭」中放置單詞「solid」,那麼您的代碼將工作得更快。也許如果文件以「solid」開頭,並且如果ASCII解析失敗,則使用二進制解析器作爲後備,您應該更喜歡ASCII解析。

+1

如果你下載了隨機的STL文件(一個好的回購是[Thingiverse](http://www.thingiverse.com)),你可能會注意到許多二進制文件實際上在80字節的頭文件中以「固定」開頭。但是,搜索「facet」和「vertex」等其他關鍵字的建議實際上可能非常可行。我懷疑任何二進制文件都會在標題後面包含這些單詞,這是我可以開始搜索的地方。 – OnlineCop 2014-10-03 16:18:34

相關問題