2014-10-03 15 views
12

Sau khi đọc specs trên định dạng tệp STL, tôi muốn viết một vài thử nghiệm để đảm bảo rằng tệp trên thực tế là tệp nhị phân hoặc ASCII hợp lệ.Xác minh rằng tệp STL là ASCII hoặc nhị phân

Tệp STL ASCII có thể được xác định bằng cách tìm văn bản "solid" ở byte 0, theo sau là dấu cách (giá trị hex \x20), sau đó là chuỗi văn bản tùy chọn, theo sau là dòng mới.

Một tập tin nhị phân STL có reserved tiêu đề -byte, tiếp theo là một số nguyên unsigned -byte ( NumberOfTriangles), và sau đó byte dữ liệu cho mỗi NumberOfTriangles khía cạnh được chỉ định.

Mỗi khía cạnh hình tam giác là byte dài: 12 độ chính xác đơn (4 byte) theo sau là số nguyên không dấu (2 byte) chưa ký.

Nếu tệp nhị phân chính xác là 84 + NumberOfTriangles * 50 byte, thường có thể được coi là tệp nhị phân hợp lệ.

Thật không may, các file nhị phân thể chứa văn bản "rắn" bắt đầu từ byte 0 trong các nội dung của tiêu đề 80-byte. Do đó, một thử nghiệm cho chỉ từ khóa đó không thể xác định một cách tích cực rằng một tệp là ASCII hoặc nhị phân.

Đây là những gì tôi có cho đến nay:

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; 
    } 
} 

Cho đến nay, điều này đã làm việc cho tôi, nhưng tôi lo lắng rằng byte thứ 80 một ASCII tập tin đơn giản của thể chứa một số ký tự ASCII đó, khi được dịch sang uint32_t, có thể thực sự bằng độ dài của tệp (rất khó, nhưng không phải là không thể).

Có các bước bổ sung nào hữu ích trong việc xác thực liệu tôi có thể "hoàn toàn chắc chắn" rằng một tệp là ASCII hoặc nhị phân không?

UPDATE:

Theo lời khuyên của @Powerswitch và @RemyLebeau, tôi đang làm thêm các xét nghiệm cho các từ khóa. Đây là những gì tôi đã có bây giờ:

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; 
} 

Nó xuất hiện để xử lý nhiều trường hợp cạnh, và tôi đã cố gắng để viết nó trong một cách mà xử lý vô cùng lớn (một vài gigabyte) file STL một cách duyên dáng mà không yêu cầu ENTIRE tập tin được nạp vào bộ nhớ cùng một lúc cho nó để quét cho văn bản "kết thúc".

Hãy thoải mái cung cấp bất kỳ phản hồi và đề xuất nào (đặc biệt là cho những người trong tương lai tìm kiếm giải pháp).

+2

Các bài viết wikipedia nói rằng * Một tập tin STL nhị phân có một tiêu đề 80 ký tự (mà thường bị bỏ qua, nhưng không bao giờ nên bắt đầu với "rắn" bởi vì đó sẽ dẫn hầu hết các phần mềm giả định rằng đây là tệp STL ASCII) * –

+2

Đúng. Nhưng trong việc thử nghiệm các tệp STL ngẫu nhiên được tải xuống từ những nơi như [Thingiverse] (http://www.thingiverse.com), nhiều tệp STL nhị phân thực sự bắt đầu bằng "rắn" mặc dù chúng "không nên". Vì vậy, đó là một lý do mà việc kiểm tra 5-6 byte đầu tiên không phải lúc nào cũng đảm bảo. – OnlineCop

Trả lời

8

Nếu tệp không bắt đầu bằng "solid " và nếu kích thước tệp chính xác là 84 + (numTriangles * 50) byte, trong đó numTriangles được đọc từ offset 80, thì tệp đó là nhị phân.

Nếu kích thước tệp tối thiểu là 15 byte (tối thiểu tuyệt đối đối với tệp ASCII không có hình tam giác) và bắt đầu bằng "solid ", hãy đọc tên theo sau cho đến khi ngắt dòng. Kiểm tra xem dòng tiếp theo hoặc bắt đầu bằng "facet " hoặc là "endsolid [name]" (không cho phép giá trị nào khác). Nếu "facet ", hãy tìm đến cuối tệp và đảm bảo tệp kết thúc bằng một dòng ghi là "endsolid [name]". Nếu tất cả những điều này là đúng, tệp đó là ASCII.

Xử lý bất kỳ kết hợp nào khác là không hợp lệ.

Vì vậy, một cái gì đó như thế này:

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

Có các bước bổ sung nào hữu ích trong việc xác thực liệu tôi có thể "hoàn toàn chắc chắn" rằng tệp là ASCII hoặc nhị phân không?

Vì không có thẻ định dạng trong thông số kỹ thuật stl, bạn không thể hoàn toàn chắc chắn về định dạng tệp.

Kiểm tra "rắn" ở đầu tệp phải đủ trong hầu hết các trường hợp. Ngoài ra, bạn có thể kiểm tra các từ khóa khác như "facet" hoặc "vertex" để chắc chắn đó là ASCII. Những từ này chỉ nên xảy ra ở định dạng ASCII (hoặc trong tiêu đề nhị phân vô dụng), nhưng có một chút khả năng là nhị phân nổi trùng hợp thành những từ này. Vì vậy, bạn cũng có thể kiểm tra xem các từ khóa có đúng thứ tự hay không.

Và tất nhiên hãy kiểm tra xem độ dài trong tiêu đề nhị phân có khớp với độ dài tệp hay không.

Nhưng: Mã của bạn sẽ hoạt động nhanh hơn nếu bạn đọc tệp tuyến tính và hy vọng rằng không ai đặt các từ "rắn" trong tiêu đề nhị phân. Có lẽ bạn nên thích phân tích cú pháp ASCII nếu tệp bắt đầu bằng "rắn" và sử dụng trình phân tích cú pháp nhị phân làm dự phòng nếu phân tích cú pháp ASCII không thành công.

+1

Nếu bạn tải xuống các tệp STL ngẫu nhiên (một repo tốt là [Thingiverse] (http://www.thingiverse.com)), bạn có thể nhận thấy rằng nhiều tệp nhị phân thực sự DO bắt đầu bằng "solid" trong tiêu đề 80 byte . Tuy nhiên, đề xuất tìm kiếm các từ khóa khác như "mặt" và "đỉnh" có thể thực sự rất khả thi. Tôi nghi ngờ bất kỳ tập tin nhị phân sẽ chứa những từ sau khi tiêu đề, đó là nơi tôi có thể bắt đầu tìm kiếm. – OnlineCop

Các vấn đề liên quan