2014-10-03 17 views
12

Dopo aver letto specs nel formato di file STL, desidero scrivere alcuni test per garantire che un file sia, in effetti, un file binario o ASCII valido.Verificare che un file STL sia ASCII o binario

Un file STL ASCII-based può essere determinato trovando il testo "solido" al byte 0, seguito da uno spazio (valore esadecimale \x20), e poi una stringa di testo opzionale, seguito da un ritorno a capo.

Un file STL binario ha una riservata intestazione -byte, seguita da un numero intero senza segno -byte ( NumberOfTriangles) e quindi byte di dati per ciascuno dei NumberOfTriangles sfaccettature specificato.

Ogni faccetta del triangolo è byte di lunghezza: 12 galleggianti a precisione singola (4 byte) seguiti da un numero intero senza segno corto (2 byte) senza segno.

Se un file binario è esattamente 84 + NumberOfTriangles * 50 byte, può essere in genere considerato un file binario valido.

Purtroppo, i file binari può contengono il testo "solido" a partire dal byte 0 nei contenuti dell'intestazione di 80 byte. Pertanto, un test solo per quella parola chiave non può determinare in modo positivo che un file sia ASCII o binario.

Questo è quello che ho finora:

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

Finora, questo ha funzionato per me, ma io sono preoccupato che byte 80 ° di un file ASCII potrebbe contenere alcuni caratteri ASCII che, quando tradotto in uint32_t, potrebbe effettivamente essere uguale alla lunghezza del file (molto improbabile, ma non impossibile).

Ci sono ulteriori passaggi che potrebbero rivelarsi utili per convalidare se posso essere "assolutamente sicuro" che un file sia ASCII o binario?

UPDATE:

Seguendo i consigli di @Powerswitch e @RemyLebeau, sto facendo ulteriori test per le parole chiave. Questo è quello che ho adesso:

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

Sembra di gestire più casi limite, e ho tentato di scrivere in un modo che gestisce estremamente grandi (da pochi gigabyte) file STL con garbo senza richiedere la INTERO file da caricare in memoria in una sola volta per la scansione per il testo "endsolid".

Sentiti libero di fornire commenti e suggerimenti (soprattutto per le persone in cerca di soluzioni future).

+2

L'articolo di Wikipedia dice che * Un file STL binario ha un header di 80 caratteri (che è generalmente ignorata, ma non dovrebbe mai cominciare con "solido", perché che porterà la maggior parte dei software presuppone che si tratti di un file STL ASCII) * –

+2

Vero. Ma nel testare i file STL casuali scaricati da luoghi come [Thingiverse] (http://www.thingiverse.com), molti file STL binari effettivamente iniziano con "solido" anche se "non dovrebbero". Ecco una ragione per cui controllare quei primi 5-6 byte non è sempre una garanzia. – OnlineCop

risposta

8

Se il file non inizia con "solid ", e se la dimensione del file è esattamente 84 + (numTriangles * 50) byte, dove numTriangles viene letto da compensare 80, allora il file è binario.

Se la dimensione del file è di almeno 15 byte (minimo assoluto per un file ASCII senza triangoli) e inizia con "solid ", leggere il nome che lo segue fino a raggiungere un'interruzione di linea. Verifica se la riga successiva inizia con "facet " o è "endsolid [name]" (non è consentito nessun altro valore). Se "facet ", cerca fino alla fine del file e assicurati che termini con una riga che dice "endsolid [name]". Se tutte queste sono vere, il file è ASCII.

Trattare qualsiasi altra combinazione come non valida.

Quindi, qualcosa di simile:

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

Ci sono ulteriori passaggi che potrebbero rivelarsi utili per convalidare se posso essere "assolutamente sicuro" che un file sia ASCII o binario?

Poiché non vi è alcun tag di formato nelle specifiche di stl, non è possibile essere assolutamente certi del formato di file.

Nella maggior parte dei casi, il controllo di "solido" all'inizio del file dovrebbe essere sufficiente. Inoltre, puoi verificare ulteriori parole chiave come "facet" o "vertice" per assicurarti che sia ASCII. Queste parole dovrebbero essere presenti solo nel formato ASCII (o nell'intestazione binary inutile), ma c'è una piccola possibilità che il binario fluttui casualmente per formare queste parole. Quindi potresti anche controllare se le parole chiave sono nel giusto ordine.

E ovviamente verificare se la lunghezza nell'intestazione binaria corrisponde alla lunghezza del file.

Ma: il tuo codice funzionerebbe più velocemente se avessi letto il file in modo lineare e sperassi che nessuno inserisca le parole "solide" nell'intestazione binaria. Forse dovresti preferire l'analisi ASCII se il file inizia con "solid" e usa l'analizzatore binario come fallback se l'analisi ASCII fallisce.

+1

Se si scaricano file STL casuali (un buon repository è [Thingiverse] (http://www.thingiverse.com)), si può notare che molti dei file binari effettivamente DO iniziano con "solido" nell'intestazione da 80 byte . Il suggerimento di cercare le altre parole chiave come "facet" e "vertice" potrebbe in realtà essere molto fattibile, però. Dubito che qualsiasi file binario contenga quelle parole dopo l'intestazione, che è dove posso iniziare a cercare. – OnlineCop

Problemi correlati