/* See {stmesh_STL.h} */ /* Last edited on 2016-04-18 19:44:56 by stolfilocal */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include bool_t stmesh_STL_is_break(int ch); /* TRUE iff {ch} is a line or page break character: ASCII CR ('\015'), LF ('\012'), VT ('\013'), or FF ('\014'). */ bool_t stmesh_STL_is_space(int ch); /* TRUE iff {ch} is a blank space character, but not a line or page break; namely, if {ch} is ASCII SP ('\040'), TAB ('\011'), NUL ('\000') and ISO Latin non-breaking space ('\240'). */ /* STL FILE PARSING The following procedures increment {*lineP} whenever they skip over a line or page break character (except that Apple's CR-LF pair is counted as a single line break). The {fileName} argument is used in error messages. */ bool_t stmesh_STL_skip_white_space(FILE *rd, char *fileName, int *lineP); /* Skips space characters, line breaks, and page breaks from {rd}. Returns TRUE if it finds a character other than those (which is not consumed), FALSE if it runs into the end-of-file. */ char *stmesh_STL_get_keyword(FILE *rd, char *fileName, int *lineP); /* Skips space characters, line breaks, and page breaks from {rd}. If it runs into end-of-file or a character that is not an ASCII letter, fails with an error message. Otherwise reads characters from {rd}, until end-of-file, space, line break, or page break, and returns those characters as a newly allocated string. */ void stmesh_STL_check_keyword(FILE *rd, char *fileName, int *lineP, char *key); /* Skips space characters, line breaks, and page breaks from {rd} and obtains the next token with {stmesh_STL_get_keyword}. Checks whether it is equal to {key}. Fails with an error message if there is no next token in {rd}, or the next token does not start with an ASCII letter, or it is not equal to {key}. */ bool_t stmesh_STL_ascii_read_face(FILE *rd, char *fileName, int *lineP, stmesh_STL_face_t *face); /* Tries to read another triangular face from the ASCII STL file {rd}. If the next token in {rd} is "facet", reads the face data up to and including the "endfacet" token, stores that data in {*face}, and returns {TRUE}. If the next token is "endsolid" instead, consumes it and returns {FALSE}. Skips spaces, line breaks, and page breaks, and other formatting chars before and inside the parsed data. Fails with error message if the next token is something else, or the face is malformed. */ int stmesh_STL_binary_read_header(FILE *rd, char *fileName, int *lineP); /* Reads the 80-byte header of the binary STL file {rd}, and the next line with the number of faces {nf}. Returns {nf}. The line counter {*lineP} is incremented by 2. */ void stmesh_STL_binary_read_face(FILE *rd, char *fileName, int *lineP, stmesh_STL_face_t *face); /* Tries to read another triangular face from the binary STL file {rd}. Assumes that each face is 50 bytes: three components of the normal (3*float), the coordinates of the three vertices (9*float), and a spacer (2 bytes). Increments the line counter {*lineP} by 1. */ /* IMPLEMENTATIONS */ bool_t stmesh_STL_is_break(int ch) { return (ch == '\012') || (ch == '\013') || (ch == '\014') || (ch == '\015'); } bool_t stmesh_STL_is_space(int ch) { return (ch == '\000') || (ch == '\011') || (ch == ' ') || (ch == '\240'); } bool_t stmesh_STL_skip_white_space(FILE *rd, char *fileName, int *lineP) { int prev_ch = EOF; /* Previous character parsed in this call, or EOF if first. */ int ch = fgetc(rd); while (ch != EOF) { if (stmesh_STL_is_break(ch)) { /* Possible line break, unless it is part of an Apple end-of-line (LF/VT/FF after a CR): */ if ((prev_ch != '\015') || (ch != '\012')) { (*lineP)++; } } else if (! stmesh_STL_is_space(ch)) { ungetc(ch, rd); return TRUE; } ch = fgetc(rd); } return FALSE; } char *stmesh_STL_get_keyword(FILE *rd, char *fileName, int *lineP) { if (! stmesh_STL_skip_white_space(rd, fileName, lineP)) { fprintf(stderr, "%s:%d: ** expecting keyword, found end-of-file\n", fileName, (*lineP)); exit(1); } int ch = fgetc(rd); if (((ch < 'a') || (ch > 'z')) && ((ch < 'A') || (ch > 'Z'))) { fprintf(stderr, "%s:%d: ** expecting keyword, found '%c'\n", fileName, (*lineP), ch); exit(1); } ungetc(ch, rd); return fget_string(rd); } void stmesh_STL_check_keyword(FILE *rd, char *fileName, int *lineP, char *key) { char *tok = stmesh_STL_get_keyword(rd, fileName, lineP); if (strcmp(tok, key) != 0) { fprintf(stderr, "%s:%d: ** expecting '%s', found '%s'\n", fileName, (*lineP), key, tok); exit(1); } free(tok); } void stmesh_STL_read(char *fileName, bool_t binary, stmesh_STL_face_proc_t *process_face) { char *ftype = (binary ? "rb" : "r"); FILE *rd = fopen(fileName, ftype); if (rd == NULL) { fprintf(stderr, "** failed to open file '%s'\n", fileName); exit(1); } stmesh_STL_face_t face; int line = 1; /* Line number in file. */ int nf = 0; /* Number of faces read from the STL file. */ if (binary) { int nf = stmesh_STL_binary_read_header(rd, fileName, &line); int it; for (it = 0; it < nf; it++) { stmesh_STL_binary_read_face(rd, fileName, &line, &face); process_face(line, &face); } } else { stmesh_STL_check_keyword(rd, fileName, &line, "solid"); while (stmesh_STL_ascii_read_face(rd,fileName, &line, &face)) { nf++; process_face(line, &face); } } fclose(rd); } bool_t stmesh_STL_ascii_read_face(FILE *rd, char *fileName, int *lineP, stmesh_STL_face_t *face) { char *tok = stmesh_STL_get_keyword(rd, fileName, lineP); if (strcmp(tok, "endsolid") == 0) { return FALSE; } else if (strcmp(tok, "facet") == 0) { int i, k; stmesh_STL_check_keyword(rd, fileName, lineP, "normal"); for (i = 0; i < 3; i++) { if (! stmesh_STL_skip_white_space(rd, fileName, lineP)) { fprintf(stderr, "%s:%d: ** expecting normal component, found end-of-file\n", fileName, (*lineP)); exit(1); } face->normal.c[i] = (float)fget_double(rd); } stmesh_STL_check_keyword(rd, fileName, lineP, "outer"); stmesh_STL_check_keyword(rd, fileName, lineP, "loop"); for (k = 0; k < 3; k++) { stmesh_STL_check_keyword(rd, fileName, lineP, "vertex"); for (i = 0; i < 3; i++) { if (! stmesh_STL_skip_white_space(rd, fileName, lineP)) { fprintf(stderr, "%s:%d: ** expecting vertex coord, found end-of-file\n", fileName, (*lineP)); exit(1); } face->v[k].c[i] = (float)fget_double(rd); } } stmesh_STL_check_keyword(rd, fileName, lineP, "endloop"); stmesh_STL_check_keyword(rd, fileName, lineP, "endfacet"); return TRUE; } else { fprintf(stderr, "%s:%d: ** expected 'facet' or 'endsolid', found '%s'\n", fileName, (*lineP), tok); exit(1); } } int stmesh_STL_binary_read_header(FILE *rd, char *fileName, int *lineP) { char title[80]; int nf; int err; err = (int)fread(title, 80, 1, rd); if (err != 1) { fprintf(stderr, "%s: error reading binary STL file header\n", fileName); exit(1); } (*lineP)++; err = (int)fread((void*)(&nf), 4, 1, rd); if (err != 1) { fprintf(stderr, "%s: error reading {nf} from binary STL file\n", fileName); exit(1); } (*lineP)++; return nf; } void stmesh_STL_binary_read_face(FILE *rd, char *fileName, int *lineP, stmesh_STL_face_t *face) { /* Read the coordinates of normal and 3 vertices: */ float vc[12]; /* Coordinates of normal (3) and vertices (3*3). */ int err; int k; for (k = 0; k < 12; k++) { err = (int)fread((void*)(&vc[k]), sizeof(float), 1, rd); if (err != 1) { fprintf(stderr, "%s:%d: error reading binary STL datum %d\n", fileName, (*lineP), k); exit(1); } } /* Read the 16-bit padding: */ unsigned short uint16; /* Padding between faces. */ err = (int)fread((void*)(&uint16), sizeof(unsigned short), 1, rd); // spacer between successive faces if (err != 1) { fprintf(stderr, "%s:%d: error reading binary STL padding\n", fileName, (*lineP)); exit(1); } (*lineP)++; /* Fill the {stmesh_STL_face_t}. */ face->normal = (stmesh_STL_r3_t){{ vc[0], vc[1], vc[2] }}; face->v[0] = (stmesh_STL_r3_t){{ vc[3], vc[4], vc[5] }}; face->v[1] = (stmesh_STL_r3_t){{ vc[6], vc[7], vc[8] }}; face->v[2] = (stmesh_STL_r3_t){{ vc[9], vc[10], vc[11] }}; } void stmesh_STL_print_triangle(FILE *wr, stmesh_STL_face_t *f) { int k; for (k = 0; k < 3; k++) { stmesh_STL_r3_t *vk = &(f->v[k]); fprintf(wr, " v[%d] = ( %.8f %.8f %.8f )\n", k, vk->c[0], vk->c[1], vk->c[2]); } stmesh_STL_r3_t *vn = &(f->normal); fprintf(wr, " norm = ( %.8f %.8f %.8f )\n", vn->c[0], vn->c[1], vn->c[2]); } i3_t stmesh_STL_round_point(stmesh_STL_r3_t *v, float eps, bool_t even) { int rem = (even ? 0 : -1); /* Desired remainder, or {-1} is any. */ i3_t qv; int k; for (k = 0; k < 3; k++) { qv.c[k] = (int32_t)iround(v->c[k], eps, 2, rem, INT32_MAX); } return qv; }