phinieo
phinieo

Reputation: 1

Are .STL files missing triangles? This STL parsing code is consistently missing triangles

This is my second project that parses binary STL files. I'm running into the same problem I experienced several years ago in which the representations of my STL file ends up missing several triangles. I've tried reading more than the number of triangles indicated in the STL header to see if there are more triangles than indicated but I end up with the same missing triangles.

I'm confident this is an issue with the STL or the way I am reading it and not my visualization because I've encountered an identical issue before in a project that was essentially just the reading of an STL using code almost identical to the C code below.

Gomboc STL missing triangles after being run through my slicing project code

Low-poly Cat STL missing triangles after being run through my slicing project. Seen from the inside for clarity

//RETURNED POINTER MUST BE FREE'D
struct tri* readSTL(char* filename, int* numTriangles) {
    FILE* fp;
    char wordIn = '\0'; 

    fp = fopen(filename, "r");

    //READ 80 BYTE HEADER
    for (int i = 0; i < 80; i++) {
        fread(&wordIn, sizeof(char), 1, fp);
    }

    //READ 4 BYTE NUMBER OF TRIANGLES
    fread(numTriangles, sizeof(char), 4, fp);

    //READ ALL TRIANGLES
    struct tri* triangles = calloc(*numTriangles, sizeof(struct tri));

    for (int i = 0; i < *numTriangles; i++) {

       //READ NORMAL VECTOR
      fread(&(triangles[i].normal.X), sizeof(char), 4, fp);
      fread(&(triangles[i].normal.Y), sizeof(char), 4, fp);
      fread(&(triangles[i].normal.Z), sizeof(char), 4, fp);

      //READ X
      fread(&(triangles[i].p1.X), sizeof(char), 4, fp);
      fread(&(triangles[i].p1.Y), sizeof(char), 4, fp);
      fread(&(triangles[i].p1.Z), sizeof(char), 4, fp);

      //READ Y
      fread(&(triangles[i].p2.X), sizeof(char), 4, fp);
      fread(&(triangles[i].p2.Y), sizeof(char), 4, fp);
      fread(&(triangles[i].p2.Z), sizeof(char), 4, fp);

      //READ Z
      fread(&(triangles[i].p3.X), sizeof(char), 4, fp);
      fread(&(triangles[i].p3.Y), sizeof(char), 4, fp);
      fread(&(triangles[i].p3.Z), sizeof(char), 4, fp);

      //READ THROW-AWAY
      int throwaway;
      fread(&throwaway, sizeof(char), 2, fp);
      
      printf("FILE NORMAL: %f, %f, %f\n", triangles[i].normal.X,
              triangles[i].normal.Y, triangles[i].normal.Z);

      struct point Normal = computeNormal(triangles[i]);
      printf("Computed NORMAL: %f, %f, %f\n\n", Normal.X, Normal.Y, Normal.Z);
    }

    fclose(fp);

    return triangles;
}

Is there something I'm missing about the STL data structure that would account for this? This issue seems to come up in any STL I read above ~100 triangles. Parsing a 6 sided cube composed of 12 triangles looks perfect.

EDIT 1 This code is currently only set up to read BINARY STL files and that is all that is being given to it to read.

EDIT 2 My leading theory right now is that missing Triangles is standard in most softwares exporting to STL and if a triangle is defined by the triangles around it then it is not added. The odd thing is how these unspecified triangles are decided as they do not have a visual or symmetric pattern on objects, nor do missing Triangles appear in every spot where they could be reasonably inferred from the surrounding triangles.

Upvotes: -1

Views: 471

Answers (1)

John Bollinger
John Bollinger

Reputation: 180998

Binary STL is a very simple format. You can find documentation for it on Wikipedia and elsewhere. It consists of an 80-byte header with no generally-accepted interpretation, a triangle count, and a list of the specified number of triangles, where

  • The triangle count is a expressed as an unsigned, little-endian, 32-bit integer.

  • Each triangle is expressed as

    • 12 little-endian, 32-bit IEEE-754 binary floating-point numbers representing a face normal and the coordinates of the three vertices, plus
    • one unsigned, little-endian, 16-bit integer described as an "attribute byte count".

    (50 bytes total)

Apparently there is some variation among STL writers with regard to use of the header and the attribute byte counts, but since you're ignoring those, that won't affect your program. There also is some variation among STL writers with regard to the fields of the normals. The normals are redundant with the vertices, given the rule for vertex ordering, and apparently some software stuffs the normals with different data instead. Your code appears to be checking the normals, however, so I assume that you are satisfied that the normals you are using are ok.

Since you have verified that other software renders the contents of your STL files correctly, it seems reasonable to assume that they are not malformed or corrupt.

You remark that ...

My leading theory right now is that missing Triangles is standard in most softwares exporting to STL and if a triangle is defined by the triangles around it then it is not added.

... but that seems implausible. All triangles of a closed surface without any holes are defined by the triangles around them. Also, I'm not sure exactly what your example surface is supposed to look like, but it does not look consistent with this explanation. In particular, the gaps don't appear all to be triangular, and those that are don't look like they could correspond to a single triangle that satisfies the STL vertex-to-vertex rule. And of course, none of the documentation suggests such a practice.

I'm confident this is an issue with the STL or the way I am reading it and not my visualization

I wouldn't be.

because I've encountered an identical issue before in a project that was essentially just the reading of an STL using code almost identical to the C code below.

You evidently wrote nearly identical file-reading code for a previous project, so what would be surprising about having committed an error elsewhere in that project whose analog you then repeated in your current project?

Overall your code looks more or less ok. Issues I notice include:

  • using signed integers where the STL spec specifies unsigned ones, especially for the triangle count. But this will bite you only if you have more triangles than can be represented by a signed integer (about 2.4 billion for a signed 32-bit integer).

  • assuming that the size of type int is 32 bits. Although that's very common today, C does not require it. Together with the previous point, it would be better to include stdint.h and use type uint32_t to represent the triangle count.

  • the code assumes a little-endian machine. That's probably what you're running it on, but if you have any uncertainty about that then you should confirm.

  • you do not check the return values of your function calls. Some of them are confirmed successful in practice by successful continuation of your program, but others are not. Especially your fread() calls should be checked to ensure that they complete successfully, reading the full number of items expected. If your problem is indeed manifesting in the function you present then it is highly likely that one of the observable symptoms would be failing reads.

Finally, coming around to the only actual question in the question:

Is there something I'm missing about the STL data structure that would account for this?

Other than integer signedness and possibly integer size, no, it does not appear so.

Upvotes: 2

Related Questions