/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include #include #include #include "stl.h" #ifndef SEEK_SET #error "SEEK_SET not defined" #endif void stl_open(stl_file *stl, const char *file) { stl_initialize(stl); stl_count_facets(stl, file); stl_allocate(stl); stl_read(stl, 0, true); if (!stl->error) fclose(stl->fp); } void stl_initialize(stl_file *stl) { memset(stl, 0, sizeof(stl_file)); stl->stats.volume = -1.0; } #ifndef BOOST_LITTLE_ENDIAN extern void stl_internal_reverse_quads(char *buf, size_t cnt); #endif /* BOOST_LITTLE_ENDIAN */ void stl_count_facets(stl_file *stl, const char *file) { long file_size; uint32_t header_num_facets; uint32_t num_facets; int i; size_t s; unsigned char chtest[128]; int num_lines = 1; char *error_msg; if (stl->error) return; /* Open the file in binary mode first */ stl->fp = boost::nowide::fopen(file, "rb"); if(stl->fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); stl->error = 1; return; } /* Find size of file */ fseek(stl->fp, 0, SEEK_END); file_size = ftell(stl->fp); /* Check for binary or ASCII file */ fseek(stl->fp, HEADER_SIZE, SEEK_SET); if (!fread(chtest, sizeof(chtest), 1, stl->fp)) { perror("The input is an empty file"); stl->error = 1; return; } stl->stats.type = ascii; for(s = 0; s < sizeof(chtest); s++) { if(chtest[s] > 127) { stl->stats.type = binary; break; } } rewind(stl->fp); /* Get the header and the number of facets in the .STL file */ /* If the .STL file is binary, then do the following */ if(stl->stats.type == binary) { /* Test if the STL file has the right size */ if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) { fprintf(stderr, "The file %s has the wrong size.\n", file); stl->error = 1; return; } num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; /* Read the header */ if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) { stl->stats.header[80] = '\0'; } /* Read the int following the header. This should contain # of facets */ bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp); #ifndef BOOST_LITTLE_ENDIAN // Convert from little endian to big endian. stl_internal_reverse_quads((char*)&header_num_facets, 4); #endif /* BOOST_LITTLE_ENDIAN */ if (! header_num_faces_read || num_facets != header_num_facets) { fprintf(stderr, "Warning: File size doesn't match number of facets in the header\n"); } } /* Otherwise, if the .STL file is ASCII, then do the following */ else { /* Reopen the file in text mode (for getting correct newlines on Windows) */ // fix to silence a warning about unused return value. // obviously if it fails we have problems.... stl->fp = boost::nowide::freopen(file, "r", stl->fp); // do another null check to be safe if(stl->fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); stl->error = 1; return; } /* Find the number of facets */ char linebuf[100]; while (fgets(linebuf, 100, stl->fp) != NULL) { /* don't count short lines */ if (strlen(linebuf) <= 4) continue; /* skip solid/endsolid lines as broken STL file generators may put several of them */ if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue; ++num_lines; } rewind(stl->fp); /* Get the header */ for(i = 0; (i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++); stl->stats.header[i] = '\0'; /* Lose the '\n' */ stl->stats.header[80] = '\0'; num_facets = num_lines / ASCII_LINES_PER_FACET; } stl->stats.number_of_facets += num_facets; stl->stats.original_num_facets = stl->stats.number_of_facets; } void stl_allocate(stl_file *stl) { if (stl->error) return; /* Allocate memory for the entire .STL file */ stl->facet_start = (stl_facet*)calloc(stl->stats.number_of_facets, sizeof(stl_facet)); if(stl->facet_start == NULL) perror("stl_initialize"); stl->stats.facets_malloced = stl->stats.number_of_facets; /* Allocate memory for the neighbors list */ stl->neighbors_start = (stl_neighbors*) calloc(stl->stats.number_of_facets, sizeof(stl_neighbors)); if(stl->facet_start == NULL) perror("stl_initialize"); } void stl_open_merge(stl_file *stl, char *file_to_merge) { int num_facets_so_far; stl_type origStlType; FILE *origFp; stl_file stl_to_merge; if (stl->error) return; /* Record how many facets we have so far from the first file. We will start putting facets in the next position. Since we're 0-indexed, it'l be the same position. */ num_facets_so_far = stl->stats.number_of_facets; /* Record the file type we started with: */ origStlType=stl->stats.type; /* Record the file pointer too: */ origFp=stl->fp; /* Initialize the sturucture with zero stats, header info and sizes: */ stl_initialize(&stl_to_merge); stl_count_facets(&stl_to_merge, file_to_merge); /* Copy what we need to into stl so that we can read the file_to_merge directly into it using stl_read: Save the rest of the valuable info: */ stl->stats.type=stl_to_merge.stats.type; stl->fp=stl_to_merge.fp; /* Add the number of facets we already have in stl with what we we found in stl_to_merge but haven't read yet. */ stl->stats.number_of_facets=num_facets_so_far+stl_to_merge.stats.number_of_facets; /* Allocate enough room for stl->stats.number_of_facets facets and neighbors: */ stl_reallocate(stl); /* Read the file to merge directly into stl, adding it to what we have already. Start at num_facets_so_far, the index to the first unused facet. Also say that this isn't our first time so we should augment stats like min and max instead of erasing them. */ stl_read(stl, num_facets_so_far, false); /* Restore the stl information we overwrote (for stl_read) so that it still accurately reflects the subject part: */ stl->stats.type=origStlType; stl->fp=origFp; } extern void stl_reallocate(stl_file *stl) { if (stl->error) return; /* Reallocate more memory for the .STL file(s) */ stl->facet_start = (stl_facet*)realloc(stl->facet_start, stl->stats.number_of_facets * sizeof(stl_facet)); if(stl->facet_start == NULL) perror("stl_initialize"); stl->stats.facets_malloced = stl->stats.number_of_facets; /* Reallocate more memory for the neighbors list */ stl->neighbors_start = (stl_neighbors*) realloc(stl->neighbors_start, stl->stats.number_of_facets * sizeof(stl_neighbors)); if(stl->facet_start == NULL) perror("stl_initialize"); } /* Reads the contents of the file pointed to by stl->fp into the stl structure, starting at facet first_facet. The second argument says if it's our first time running this for the stl and therefore we should reset our max and min stats. */ void stl_read(stl_file *stl, int first_facet, bool first) { stl_facet facet; int i; if (stl->error) return; if(stl->stats.type == binary) { fseek(stl->fp, HEADER_SIZE, SEEK_SET); } else { rewind(stl->fp); } char normal_buf[3][32]; for(i = first_facet; i < stl->stats.number_of_facets; i++) { if(stl->stats.type == binary) /* Read a single facet from a binary .STL file */ { /* we assume little-endian architecture! */ if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) { stl->error = 1; return; } #ifndef BOOST_LITTLE_ENDIAN // Convert the loaded little endian data to big endian. stl_internal_reverse_quads((char*)&facet, 48); #endif /* BOOST_LITTLE_ENDIAN */ } else /* Read a single facet from an ASCII .STL file */ { // skip solid/endsolid // (in this order, otherwise it won't work when they are paired in the middle of a file) fscanf(stl->fp, "endsolid\n"); fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. int res_normal = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); assert(res_normal == 3); int res_outer_loop = fscanf(stl->fp, " outer loop"); assert(res_outer_loop == 0); int res_vertex1 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); assert(res_vertex1 == 3); int res_vertex2 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); assert(res_vertex2 == 3); int res_vertex3 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); assert(res_vertex3 == 3); int res_endloop = fscanf(stl->fp, " endloop"); assert(res_endloop == 0); // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines. int res_endfacet = fscanf(stl->fp, " endfacet "); if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { perror("Something is syntactically very wrong with this ASCII STL!"); stl->error = 1; return; } // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { // Normal was mangled. Maybe denormals or "not a number" were stored? // Just reset the normal and silently ignore it. memset(&facet.normal, 0, sizeof(facet.normal)); } } #if 0 // Report close to zero vertex coordinates. Due to the nature of the floating point numbers, // close to zero values may be represented with singificantly higher precision than the rest of the vertices. // It may be worth to round these numbers to zero during loading to reduce the number of errors reported // during the STL import. for (size_t j = 0; j < 3; ++ j) { if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); } #endif /* Write the facet into memory. */ stl->facet_start[i] = facet; stl_facet_stats(stl, facet, first); } stl->stats.size = stl->stats.max - stl->stats.min; stl->stats.bounding_diameter = stl->stats.size.norm(); } void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) { if (stl->error) return; // While we are going through all of the facets, let's find the // maximum and minimum values for x, y, and z if (first) { // Initialize the max and min values the first time through stl->stats.min = facet.vertex[0]; stl->stats.max = facet.vertex[0]; stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); first = false; } // Now find the max and min values. for (size_t i = 0; i < 3; ++ i) { stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); stl->stats.max = stl->stats.max.cwiseMin(facet.vertex[i]); } } void stl_close(stl_file *stl) { if (stl->error) return; if(stl->neighbors_start != NULL) free(stl->neighbors_start); if(stl->facet_start != NULL) free(stl->facet_start); if(stl->v_indices != NULL) free(stl->v_indices); if(stl->v_shared != NULL) free(stl->v_shared); }