-1

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.

phinieo
  • 1
  • 3
  • If the "STL file" is a binary file (and not a text-file) then you need to open it in **b**inary mode. If it's a text-file then you can't use `fread` as that will read the contents as raw binary data. – Some programmer dude Oct 11 '22 at 18:43
  • @Someprogrammerdude I am currently only reading binary STLs. I will update the question to specify this. Would there be any potential issue reading binary STLs the way the code is now? – phinieo Oct 11 '22 at 18:45
  • 3
    Well you open the file in *text* mode, which means on some system there might be automatic translations of certain "characters" (bytes). Most notably on Windows the sequence `0x0d 0x0a` will be translated to only `0x0a` (`\r\n` is translated to `\n`). – Some programmer dude Oct 11 '22 at 18:47
  • @Someprogrammerdude I changed the fopen argument 'r' to 'rb'. To read explicitly in binary if I understood your comment correctly. This has not fixed the issue but does seem to be good practice. Thanks – phinieo Oct 11 '22 at 18:52
  • @phinieo Since you read binary files, perhaps [*endianness*](https://en.wikipedia.org/wiki/Endianness) could be an issue? – Some programmer dude Oct 11 '22 at 19:19
  • And have you tried to [*debug*](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) your program in any way? For example by using a [*debugger*](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) to step through the code line by line while monitoring variables and their values. – Some programmer dude Oct 11 '22 at 19:20
  • Also note that `sizeof(char)` is specified to *always* result in `1`, so that's really never needed. However, the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)) `4` should probably be replaced by a proper `sizeof` operation. Like e.g. `fread(&triangles[i].normal.X, 1, sizeof triangles[i].normal.X, fp)` – Some programmer dude Oct 11 '22 at 19:24
  • 1
    By the way, a proper [mre] would be nice to have. And please take some time to read [the help pages](http://stackoverflow.com/help), take the SO [tour], read [ask], as well as [this question checklist](https://codeblog.jonskeet.uk/2012/11/24/stack-overflow-question-checklist/). Also learn how to [edit] your questions to improve them. – Some programmer dude Oct 11 '22 at 19:26
  • Doubt endianness is the issue as almost all of the data is read correctly and creates a valid object as seen in the linked image. Could you be more specific on what I could look for through a debugger in this application? Aside from some sort of memory leak I can't think of many things a debugger could point me towards for trying to find data that never gets read. The sizeof changes also are a good boost to good coding practices but are not very relevant to the problem. Most values are correctly read so unless the sizeof is changing mid execution it cannot effect this issue. – phinieo Oct 11 '22 at 19:46
  • 1
    @SteveSummit Yes, but it reads without interpretation of the bytes (besides the possible translation). Reading "text" into an `int` usually makes no sense, as well as reading the binary data of an `int` value into an array of characters (though that could *sometime* make a little sense, though it's still not a "string"). – Some programmer dude Oct 11 '22 at 20:21
  • 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. This would reduce the usefulness of the header data detailing the actual number of triangles. I"d appreciate if someone familiar with the file format could confirm or deny this. – phinieo Oct 11 '22 at 21:01
  • When you say triangles are "missing", do you mean that the rendered model looks different than you expect? Because you can readily verify that the file actually contains data for (at least) as many triangles as it specifies it does. And if it does contain data for that many triangles, and you read data for that many triangles, then I wouldn't characterize that as any triangles being missing from the perspective of reading the file. – John Bollinger Oct 11 '22 at 21:08
  • @JohnBollingerIn a literal sense yes, the model looks different than I expect in that I know the surface continues over the 'missing' triangle data. Adding the same STL file to a supported slicer such as Slic3r or Cura will not show missing triangles. I have verified that I am reading the same number of triangles that the file header specifies. It's a fair assertion that they are not 'missing' from the files perspective. I'm trying to find out if the STL format requires inference or fixing in order to get the full data of the mesh. I will update the question to specify this distinction. – phinieo Oct 11 '22 at 21:17
  • How large is your STL file? In particular, is it larger than 4.8 GB? – John Bollinger Oct 11 '22 at 22:08

1 Answers1

2

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157