star-stl

Load STereo Lithography (StL) file format
git clone git://git.meso-star.fr/star-stl.git
Log | Files | Refs | README | LICENSE

commit dc981e8c59accd42fc74bd804a2e4706365827e1
parent 8aacfd06304ea28481a8b864b139ee35b2a9bd7d
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Mon, 25 Apr 2022 15:32:17 +0200

Add STL write feature, add binary format

Diffstat:
Msrc/sstl.c | 548+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/sstl.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_sstl_load.c | 264++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 813 insertions(+), 62 deletions(-)

diff --git a/src/sstl.c b/src/sstl.c @@ -30,6 +30,7 @@ #include "sstl.h" +#include <rsys/rsys.h> #include <rsys/cstr.h> #include <rsys/float3.h> #include <rsys/hash_table.h> @@ -39,6 +40,7 @@ #include <rsys/stretchy_array.h> #include <string.h> +#include <stdio.h> #ifdef COMPILER_CL #pragma warning(push) @@ -47,6 +49,8 @@ #define strtok_r strtok_s #endif +#define OK(Expr) if((res = (Expr)) != RES_OK) goto error; else (void)0 + struct solid { char* name; unsigned* indices; @@ -87,6 +91,18 @@ struct sstl { ref_T ref; }; +/* A type for binary files I/O */ +struct tmp { + float normal[3]; + union u { + float vertices[3][3]; + struct vertex vtx[3]; + } u; + unsigned short attrib, foo; +}; + +STATIC_ASSERT(sizeof(struct tmp) == 52, Invalid_struct_size); + /******************************************************************************* * Helper functions ******************************************************************************/ @@ -109,25 +125,61 @@ streamer_release(struct streamer* streamer) sa_release(streamer->buf); } +/* Large enough to read "solid\0" */ +#define CHECK_SOLID_LEN 6 + static char* -streamer_read_line(struct streamer* streamer) +streamer_read_line + (struct streamer* streamer, + int checking_solid) { const size_t buf_chunk = 256; + size_t read_sz; char* line; ASSERT(streamer); - for(;(line=fgets(streamer->buf, (int)sa_size(streamer->buf), streamer->stream)); + if(checking_solid) { + size_t current = sa_size(streamer->buf); + if(current < CHECK_SOLID_LEN) { + char* remain = sa_add(streamer->buf, CHECK_SOLID_LEN - current); + if(!remain) { + FATAL("Not enough memory: couldn't resize the stream buffer.\n"); + } + } + read_sz = CHECK_SOLID_LEN; + } else { + read_sz = sa_size(streamer->buf); + } + + for(;(line=fgets(streamer->buf, (int)read_sz, streamer->stream)); ++streamer->iline) { - /* Ensure that the whole line is read */ + /* If checking for a line starting with "solid", stop reading early if the + * line does not. If no error, ensure that the whole line is read */ while(!strrchr(line, '\n')) { - char* remain = sa_add(streamer->buf, buf_chunk); - if(!remain) { - FATAL("Not enough memory: couldn't resize the stream buffer.\n"); + char* remain; + size_t remain_sz; + if(checking_solid) { + if(0 != strncmp("solid", line, 5)) { + /* Dont read further */ + return line; + } + remain = line + 5; + ASSERT(*remain == '\0'); + remain_sz = sa_size(streamer->buf) - 5; + } else { + remain = sa_add(streamer->buf, buf_chunk); + if(!remain) { + FATAL("Not enough memory: couldn't resize the stream buffer.\n"); + } + remain_sz = buf_chunk; } line = streamer->buf; - if(!fgets(remain, (int)buf_chunk, streamer->stream)) /* EOF */ + if(!fgets(remain, (int)remain_sz, streamer->stream)) /* EOF */ break; + + read_sz = sa_size(streamer->buf); + checking_solid = 0; } if(strspn(streamer->buf, " \t\r\n") != strlen(streamer->buf)) { /* Not empty */ @@ -275,9 +327,8 @@ parse_solid_name goto error; } - res = parse_name_string - (sstl, strtok_r(NULL, "\0", tok_ctx), &solid->name, filename, iline, tok_ctx); - if(res != RES_OK) goto error; + OK(parse_name_string(sstl, strtok_r(NULL, "\0", tok_ctx), &solid->name, + filename, iline, tok_ctx)); exit: return res; @@ -392,32 +443,31 @@ parse_directive } static res_T -load_stream +load_ascii_stream (struct sstl* sstl, FILE* stream, - const char* stream_name, - char** tok_ctx) + const char* stream_name) { res_T res = RES_OK; struct streamer streamer; struct solid solid = SOLID_NULL; char* line = NULL; + char* tok_ctx; char* tk; - ASSERT(sstl && stream); + ASSERT(sstl && stream && stream_name); streamer_init(&streamer, stream, stream_name); clear(sstl); - line = streamer_read_line(&streamer); - res = parse_solid_name(sstl, &solid, line, streamer.name, streamer.iline, tok_ctx); - if(res != RES_OK) goto error; + line = streamer_read_line(&streamer, 1); + OK(parse_solid_name(sstl, &solid, line, streamer.name, streamer.iline, &tok_ctx)); for(;;) { /* Parse the solid facets */ float normal[3]; unsigned facet[3]; int ivertex; - line = streamer_read_line(&streamer); + line = streamer_read_line(&streamer, 0); if(!line) { print_log(sstl, LOG_ERROR, "%s:%lu: missing directive.\n", streamer.name, (unsigned long)streamer.iline); @@ -425,7 +475,7 @@ load_stream goto error; } - tk = strtok_r(line, " \t", tok_ctx); + tk = strtok_r(line, " \t", &tok_ctx); /* Stop on "endsolid" directive */ if(!strcmp(tk, "endsolid")) @@ -433,7 +483,7 @@ load_stream /* Parse the facet normal directive */ if(strcmp(tk, "facet") - || !(tk = strtok_r(NULL, " \t", tok_ctx)) + || !(tk = strtok_r(NULL, " \t", &tok_ctx)) || strcmp(tk, "normal")) { print_log(sstl, LOG_ERROR, "%s:%lu: missing or malformed \"facet normal X Y Z\" directive.\n", @@ -441,21 +491,18 @@ load_stream res = RES_BAD_ARG; goto error; } - res = parse_float3 - (sstl, strtok_r(NULL, "\0", tok_ctx), normal, streamer.name, streamer.iline, tok_ctx); - if(res != RES_OK) goto error; + OK(parse_float3(sstl, strtok_r(NULL, "\0", &tok_ctx), normal, streamer.name, + streamer.iline, &tok_ctx)); /* Parse the Outer loop directive */ - line = streamer_read_line(&streamer); - res = parse_outer_loop(sstl, line, streamer.name, streamer.iline, tok_ctx); - if(res != RES_OK) goto error; + line = streamer_read_line(&streamer, 0); + OK(parse_outer_loop(sstl, line, streamer.name, streamer.iline, &tok_ctx)); /* Parse the facet vertices. Assume that only 3 vertices are submitted */ FOR_EACH(ivertex, 0, 3) { - line = streamer_read_line(&streamer); - res = parse_solid_vertex - (sstl, &solid, facet+ivertex, line, streamer.name, streamer.iline, tok_ctx); - if(res != RES_OK) goto error; + line = streamer_read_line(&streamer, 0); + OK(parse_solid_vertex(sstl, &solid, facet+ivertex, line, streamer.name, + streamer.iline, &tok_ctx)); } if(!f3_is_normalized(normal)) { /* Use geometry normal */ @@ -468,21 +515,18 @@ load_stream } f3_set(sa_add(solid.normals, 3), normal); - line = streamer_read_line(&streamer); - res = parse_directive(sstl, "endloop", line, streamer.name, streamer.iline, tok_ctx); - if(res != RES_OK) goto error; + line = streamer_read_line(&streamer, 0); + OK(parse_directive(sstl, "endloop", line, streamer.name, streamer.iline, &tok_ctx)); - line = streamer_read_line(&streamer); - res = parse_directive(sstl, "endfacet", line, streamer.name, streamer.iline, tok_ctx); - if(res != RES_OK) goto error; + line = streamer_read_line(&streamer, 0); + OK(parse_directive(sstl, "endfacet", line, streamer.name, streamer.iline, &tok_ctx)); } /* Check the solid/endsolid name consistency */ if(sstl->verbose && solid.name) { char* name = NULL; - res = parse_name_string - (sstl, strtok_r(NULL, "\0", tok_ctx), &name, streamer.name, streamer.iline, tok_ctx); - if(res != RES_OK) goto error; + OK(parse_name_string(sstl, strtok_r(NULL, "\0", &tok_ctx), &name, + streamer.name, streamer.iline, &tok_ctx)); /* Compare the "endsolid" name with the one of the "solid" directive */ if(name && strcmp(name, solid.name)) { @@ -504,6 +548,354 @@ error: goto exit; } +static res_T +load_binary_stream + (struct sstl* sstl, + FILE* stream, + const char* stream_name, + const size_t already_read_count) +{ + res_T res = RES_OK; + char header[80]; + unsigned triangles_count; + struct solid solid = SOLID_NULL; + unsigned i; + + ASSERT(sstl && stream && stream_name && already_read_count <= 80); + clear(sstl); + + /* Read header; the first 80 bytes are meaningless and can be safely + * (partially) dropped */ + if(1 != fread(header+already_read_count, sizeof(header)-already_read_count, 1, + stream)) + { + print_log(sstl, LOG_ERROR, "%s: missing header.\n", stream_name); + res = RES_BAD_ARG; + goto error; + } + if(1 != fread(&triangles_count, 4, 1, stream)) { + print_log(sstl, LOG_ERROR, "%s: missing triangle count.\n", stream_name); + res = RES_BAD_ARG; + goto error; + } + for(i = 0; i < triangles_count; i++) { + struct tmp tmp; + int n; + if(1 != fread(&tmp, 50, 1, stream)) { + print_log(sstl, LOG_ERROR, "%s: missing triangle data.\n", stream_name); + res = RES_BAD_ARG; + goto error; + } + + f3_set(sa_add(solid.normals, 3), tmp.normal); + for(n = 0; n < 3; n++) { + /* Look for an already registered vertex position */ + unsigned* found_id = htable_vertex_find(&sstl->vertex2id, &tmp.u.vtx[n]); + unsigned index; + + if(found_id) { + index = *found_id; + } else { + /* Add a new vertex */ + index = (unsigned)sa_size(solid.vertices) / 3; + res = htable_vertex_set(&sstl->vertex2id, &tmp.u.vtx[n], &index); + if(res != RES_OK) { + print_log(sstl, LOG_ERROR, + "%s:%u: couldn't register a vertex position.\n", + stream_name, i); + } + f3_set(sa_add(solid.vertices, 3), tmp.u.vtx[n].xyz); + } + sa_push(solid.indices, index); + } + } + i = 0; + while (1 == fread(header, 1, 1, stream)) i++; + if(i) { + print_log(sstl, LOG_WARNING, + "%s: %u unexpected trailing bytes.\n", + stream_name, i); + } + + /* Register the solid */ + sstl->solid = solid; + +exit: + return res; +error: + solid_clear(&solid); + goto exit; +} + +#if defined(COMPILER_GCC) + #define FTELL(P, S) \ + if(-1L == ((P) = ftell(S))) { res = RES_IO_ERR; goto error; } +#elif deined(COMPILER_CL) + #define FTELL(S) \ + if(-1L == ((P) = _ftelli64(S))) { res = RES_IO_ERR; goto error; } +#else + #error Undefined FTELL macro +#endif + +enum allowed_load_steps { + TRY_READ_ASCII = 1, + TRY_READ_BINARY = 2, + TRY_READ_ALL = 3 +}; + +static res_T +load_stream + (struct sstl* sstl, + FILE* stream, + const char* stream_name, + int allowed) +{ + res_T res = RES_OK; + long long int idx1, idx2; + int log = (allowed == TRY_READ_ALL); + size_t count; + + ASSERT(sstl && stream && stream_name && allowed); + + /* Try reading as an ASCII file; if the file doesn't start with "solid" the + * file is not read past the first 5 characters, so that we can continue + * reading as a binary file, exploiting the fact that binary files' first 80 + * characters are meaningless */ + FTELL(idx1, stream); + if(allowed & TRY_READ_ASCII) { + if(log) { + print_log(sstl, LOG_WARNING, + "%s: attempt to read as ASCII file.\n", stream_name); + } + res = load_ascii_stream(sstl, stream, stream_name); + if(res == RES_OK) { + if(log) print_log(sstl, LOG_WARNING, "Attempt successful.\n"); + goto exit; + } + } + if(!(allowed & TRY_READ_BINARY)) goto exit; + FTELL(idx2, stream); + ASSERT(idx2 >= idx1); + count = (size_t)(idx2 - idx1); + if(count > CHECK_SOLID_LEN) + /* "solid" was found: was an ill-formed ASCII file */ + return res; + if(log) { + print_log(sstl, LOG_WARNING, + "%s: attempt to read as binary file.\n", stream_name); + } + res = load_binary_stream(sstl, stream, stream_name, count); + if(res != RES_OK) goto error; + if(log) print_log(sstl, LOG_WARNING, "Attempt successful.\n"); + +exit: + return res; +error: + goto exit; +} + +#undef FTELL + +static res_T +load_base(struct sstl* sstl, const char* filename, int allowed) +{ + FILE* file; + res_T res = RES_OK; + + file = fopen(filename, "r"); + if(!file) { + print_log(sstl, LOG_ERROR, "Error opening `%s'.\n", filename); + return RES_IO_ERR; + } + res = load_stream(sstl, file, filename, allowed); + fclose(file); + return res; +} + +static res_T +get_sstl_triangle_normal + (const unsigned idx, + const void* data, + float normal[3]) +{ + res_T res = RES_OK; + const struct sstl* sstl = (struct sstl*)data; + struct sstl_desc desc; + + if(!sstl || !normal) { + res = RES_BAD_ARG; + goto error; + } + + OK(sstl_get_desc(sstl, &desc)); + if(idx >= desc.triangles_count) { + res = RES_BAD_ARG; + goto error; + } + + ASSERT(3*idx+2 < sa_size(desc.normals)); + f3_set(normal, desc.normals + 3*idx); + +exit: + return res; +error: + goto exit; +} + +static res_T +get_sstl_triangle_vertices + (const unsigned idx, + const void* data, + float vtx[3][3]) +{ + res_T res = RES_OK; + const struct sstl* sstl = (struct sstl*)data; + struct sstl_desc desc; + unsigned n; + + if(!sstl || !vtx) { + res = RES_BAD_ARG; + goto error; + } + + OK(sstl_get_desc(sstl, &desc)); + if(idx >= desc.triangles_count) { + res = RES_BAD_ARG; + goto error; + } + + for(n = 0; n < 3; n++) { + size_t vtx_idx = desc.indices[3*idx + n]; + ASSERT(3*vtx_idx+2 < sa_size(desc.vertices)); + f3_set(vtx[n], desc.vertices + 3*vtx_idx); + } + +exit: + return res; +error: + goto exit; +} + +static void +print_sstl_error_log + (const void* data, + const char* msg, + ...) +{ + const struct sstl_write_data* sstld = (struct sstl_write_data*)data; + va_list vargs_list; + va_start(vargs_list, msg); + print_log(sstld->data, LOG_ERROR, msg, vargs_list); + va_end(vargs_list); +} + +res_T +sstl_pack_write_data + (const struct sstl* sstl, + struct sstl_write_data* out) +{ + size_t sz; + if(!sstl || !out) return RES_BAD_ARG; + if(!sstl->solid.indices || !sstl->solid.normals || !sstl->solid.vertices) + return RES_BAD_ARG; + sz = sa_size(sstl->solid.indices); + if(sz % 3 != 0) return RES_BAD_ARG; + sz /= 3; + if(sz > UINT_MAX) return RES_BAD_ARG; + out->name = sstl->solid.name; + out->data = sstl; + out->get_triangle_normal = get_sstl_triangle_normal; + out->get_triangle_vertices = get_sstl_triangle_vertices; + out->print_error_log = print_sstl_error_log; + out->triangles_count = (unsigned)sz; + return RES_OK; +} + +#define OKP(Expr) if((Expr) < 0) { res=RES_IO_ERR; goto error; } + +static res_T +write_stream + (const struct sstl_write_data* data, + FILE* stream, + const char* stream_name, + const int binary) +{ + res_T res = RES_OK; + unsigned i; + + ASSERT(data && stream && stream_name); + + if(data->get_triangle_normal == NULL) { + if(data->print_error_log) { + data->print_error_log(data, + "%s: no getter defined for triangles' normals.\n", stream_name); + } + res = RES_BAD_ARG; + goto error; + } + + if(data->get_triangle_vertices == NULL) { + if(data->print_error_log) { + data->print_error_log(data, + "%s: no getter defined for triangles' vertices.\n", stream_name); + } + res = RES_BAD_ARG; + goto error; + } + + if(binary) { + char header[80] = "Binary STL written by the star-stl library"; + if(1 != fwrite(header, sizeof(header), 1, stream)) { + res = RES_IO_ERR; + goto error; + } + ASSERT(sizeof(data->triangles_count == 4)); + if(1 != fwrite(&data->triangles_count, 4, 1, stream)) { + res = RES_IO_ERR; + goto error; + } + for(i = 0; i < data->triangles_count; i++) { + struct tmp tmp; + res = data->get_triangle_normal(i, data->data, tmp.normal); + res = data->get_triangle_vertices(i, data->data, tmp.u.vertices); + tmp.attrib = 0; + if(1 != fwrite(&tmp, 50, 1, stream)) { + res = RES_IO_ERR; + goto error; + } + } + } else { + if(data->name) { + OKP(fprintf(stream, "solid %s\n", data->name)); + } else { + OKP(fprintf(stream, "solid \n")); + } + for(i = 0; i < data->triangles_count; i++) { + float normal[3], vtx[3][3]; + int n; + + res = data->get_triangle_normal(i, data->data, normal); + OKP(fprintf(stream, " facet normal %g %g %g\n", SPLIT3(normal))); + + res = data->get_triangle_vertices(i, data->data, vtx); + OKP(fprintf(stream, " outer loop\n")); + for(n = 0; n < 3; n++) { + OKP(fprintf(stream, " vertex %g %g %g\n", SPLIT3(vtx[n]))); + } + OKP(fprintf(stream, " endloop\n")); + OKP(fprintf(stream, " endfacet\n")); + } + OKP(fprintf(stream, "endsolid \n")); + } + +exit: + return res; +error: + goto exit; +} + +#undef OKP + static void sstl_release(ref_T* ref) { @@ -583,31 +975,45 @@ sstl_ref_put(struct sstl* sstl) } res_T +sstl_load_ascii(struct sstl* sstl, const char* filename) +{ + if(!sstl || !filename) return RES_BAD_ARG; + return load_base(sstl, filename, TRY_READ_ASCII); +} + +res_T +sstl_load_binary(struct sstl* sstl, const char* filename) +{ + if(!sstl || !filename) return RES_BAD_ARG; + return load_base(sstl, filename, TRY_READ_BINARY); +} + +res_T sstl_load(struct sstl* sstl, const char* filename) { - FILE* file; - char* tok_ctx; - res_T res = RES_OK; + if(!sstl || !filename) return RES_BAD_ARG; + return load_base(sstl, filename, TRY_READ_ALL); +} - if(!sstl || !filename) - return RES_BAD_ARG; +res_T +sstl_load_stream_ascii(struct sstl* sstl, FILE* stream) +{ + if(!sstl || !stream) return RES_BAD_ARG; + return load_stream(sstl, stream, "STREAM", TRY_READ_ASCII); +} - file = fopen(filename, "r"); - if(!file) { - print_log(sstl, LOG_ERROR, "Error opening `%s'.\n", filename); - return RES_IO_ERR; - } - res = load_stream(sstl, file, filename, &tok_ctx); - fclose(file); - return res; +res_T +sstl_load_stream_binary(struct sstl* sstl, FILE* stream) +{ + if(!sstl || !stream) return RES_BAD_ARG; + return load_stream(sstl, stream, "STREAM", TRY_READ_BINARY); } res_T sstl_load_stream(struct sstl* sstl, FILE* stream) { - char* tok_ctx; if(!sstl || !stream) return RES_BAD_ARG; - return load_stream(sstl, stream, "STREAM", &tok_ctx); + return load_stream(sstl, stream, "STREAM", TRY_READ_ALL); } res_T @@ -627,6 +1033,40 @@ sstl_get_desc(const struct sstl* sstl, struct sstl_desc* desc) return RES_OK; } +res_T +sstl_write + (const struct sstl_write_data* data, + const int binary, + const char* filename) +{ + FILE* file; + res_T res = RES_OK; + + if(!data || !filename) + return RES_BAD_ARG; + + file = fopen(filename, (binary ? "wb" : "w")); + if(!file) { + if(data->print_error_log) { + data->print_error_log(data, "Error opening `%s'.\n", filename); + } + return RES_IO_ERR; + } + res = write_stream(data, file, filename, binary); + fclose(file); + return res; +} + +res_T +sstl_write_stream + (const struct sstl_write_data* data, + const int binary, + FILE* stream) +{ + if(!data || !stream) return RES_BAD_ARG; + return write_stream(data, stream, "STREAM", binary); +} + #ifdef COMPILER_CL #pragma warning(pop) #endif diff --git a/src/sstl.h b/src/sstl.h @@ -62,6 +62,22 @@ struct sstl_desc { size_t vertices_count; }; +/* Data descriptor used to write a bunch of triangles to a file in the STL + * format. + * - name can be NULL, + * - print_error_log can be NULL: no log, + * - data can be NULL if get_triangle_xxx functions do not use it. */ +struct sstl_write_data { + const char* name; + const void* data; + res_T (*get_triangle_vertices) + (const unsigned idx, const void* data, float vtx[3][3]); + res_T (*get_triangle_normal) + (const unsigned idx, const void* data, float normal[3]); + void (*print_error_log)(const void* data, const char* msg, ...); + unsigned triangles_count; +}; + /* Forward declaration of external types */ struct logger; struct mem_allocator; @@ -90,15 +106,62 @@ sstl_ref_put (struct sstl* sstl); SSTL_API res_T +sstl_load_ascii + (struct sstl* sstl, + const char* filename); + +SSTL_API res_T +sstl_load_stream_ascii + (struct sstl* sstl, + FILE* stream); + +SSTL_API res_T +sstl_load_binary + (struct sstl* sstl, + const char* filename); + +SSTL_API res_T +sstl_load_stream_binary + (struct sstl* sstl, + FILE* stream); + +/* Try first loading as an ASCII file, then as a binary file if ASCII failed. + * Note that a binary file starting with "solid" will be wrongly identified as + * ASCII, thus leading to a failure without trying a binary load. + * Also warning and error messages will possibly report on both attempts. */ +SSTL_API res_T sstl_load (struct sstl* sstl, const char* filename); +/* Try first loading as an ASCII stream, then as a binary stream if ASCII failed. + * Note that a binary stream starting with "solid" will be wrongly identified as + * ASCII, thus leading to a failure without trying a binary load. + * Also warning and error messages will possibly report on both attempts. */ SSTL_API res_T sstl_load_stream (struct sstl* sstl, FILE* stream); +/* Create a sstl_write_data from a sstl. + * The returned descriptor is valid until a new load process */ +SSTL_API res_T +sstl_pack_write_data + (const struct sstl* sstl, + struct sstl_write_data* out); + +SSTL_API res_T +sstl_write + (const struct sstl_write_data* data, + const int binary, + const char* filename); + +SSTL_API res_T +sstl_write_stream + (const struct sstl_write_data* data, + const int binary, + FILE* stream); + /* The returned descriptor is valid until a new load process */ SSTL_API res_T sstl_get_desc diff --git a/src/test_sstl_load.c b/src/test_sstl_load.c @@ -32,6 +32,7 @@ #include <rsys/clock_time.h> #include <rsys/float3.h> #include <rsys/logger.h> +#include <rsys/rsys.h> static void test_basic(struct sstl* sstl) @@ -141,6 +142,7 @@ test_basic(struct sstl* sstl) struct sstl_desc desc; FILE* file; size_t i; + struct sstl_write_data wd; CHK(sstl != NULL); @@ -149,6 +151,18 @@ test_basic(struct sstl* sstl) fwrite(test0, sizeof(char), strlen(test0), file); fclose(file); + CHK(sstl_load_ascii(NULL, NULL) == RES_BAD_ARG); + CHK(sstl_load_ascii(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_load_ascii(NULL, "test_basic.stl") == RES_BAD_ARG); + CHK(sstl_load_ascii(sstl, "none.stl") == RES_IO_ERR); + CHK(sstl_load_ascii(sstl, "test_basic.stl") == RES_OK); + + CHK(sstl_load_binary(NULL, NULL) == RES_BAD_ARG); + CHK(sstl_load_binary(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_load_binary(NULL, "test_basic.stl") == RES_BAD_ARG); + CHK(sstl_load_binary(sstl, "none.stl") == RES_IO_ERR); + CHK(sstl_load_binary(sstl, "test_basic.stl") == RES_BAD_ARG); + CHK(sstl_load(NULL, NULL) == RES_BAD_ARG); CHK(sstl_load(sstl, NULL) == RES_BAD_ARG); CHK(sstl_load(NULL, "test_basic.stl") == RES_BAD_ARG); @@ -174,15 +188,59 @@ test_basic(struct sstl* sstl) file = tmpfile(); CHK(file != NULL); fwrite(test1, sizeof(char), strlen(test1), file); + + rewind(file); + CHK(sstl_load_stream_ascii(NULL, NULL) == RES_BAD_ARG); + CHK(sstl_load_stream_ascii(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_load_stream_ascii(NULL, file) == RES_BAD_ARG); + CHK(sstl_load_stream_ascii(sstl, file) == RES_OK); + + rewind(file); + CHK(sstl_load_stream_binary(NULL, NULL) == RES_BAD_ARG); + CHK(sstl_load_stream_binary(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_load_stream_binary(NULL, file) == RES_BAD_ARG); + CHK(sstl_load_stream_binary(sstl, file) == RES_BAD_ARG); + rewind(file); CHK(sstl_load_stream(NULL, NULL) == RES_BAD_ARG); CHK(sstl_load_stream(sstl, NULL) == RES_BAD_ARG); CHK(sstl_load_stream(NULL, file) == RES_BAD_ARG); - CHK(sstl_load_stream(sstl, file) == RES_OK); + CHK(sstl_load_stream_ascii(sstl, file) == RES_OK); fclose(file); + CHK(sstl_pack_write_data(NULL, &wd) == RES_BAD_ARG); + CHK(sstl_pack_write_data(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_pack_write_data(NULL, NULL) == RES_BAD_ARG); + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_OK); + CHK(sstl_load_binary(sstl, "test_basic.stl") == RES_OK); + + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + wd.data = NULL; + CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG); + + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + wd.get_triangle_normal = NULL; + CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG); + + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + wd.get_triangle_vertices = NULL; + CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG); + + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + wd.triangles_count += 1; + CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG); + + file = tmpfile(); + CHK(file != NULL); + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + CHK(sstl_write_stream(&wd, 1, file) == RES_OK); + rewind(file); + CHK(sstl_load_stream_binary(sstl,file) == RES_OK); + fclose(file); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); - CHK(strcmp(desc.solid_name, "my_solid") == 0); + CHK(desc.solid_name == NULL); CHK(desc.vertices_count == 3); CHK(desc.triangles_count == 1); CHK(desc.indices[0] == 0); @@ -207,14 +265,9 @@ test_basic(struct sstl* sstl) file = tmpfile(); fwrite(test3, sizeof(char), strlen(test3), file); rewind(file); - CHK(sstl_load_stream(sstl, file) == RES_OK); + CHK(sstl_load_stream_binary(sstl, file) == RES_BAD_ARG); fclose(file); - CHK(sstl_get_desc(sstl, &desc) == RES_OK); - CHK(desc.vertices_count == 3); - CHK(desc.triangles_count == 1); - CHK(f3_eq(desc.normals, f3(tmp, 0.f, -1.f, 0.f)) == 1); - FOR_EACH(i, 0, nbads) { file = tmpfile(); fwrite(bad[i], sizeof(char), strlen(bad[i]), file); @@ -222,6 +275,60 @@ test_basic(struct sstl* sstl) CHK(sstl_load_stream(sstl, file) == RES_BAD_ARG); fclose(file); } + + CHK(sstl_pack_write_data(NULL, NULL) == RES_BAD_ARG); + CHK(sstl_pack_write_data(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_pack_write_data(NULL, &wd) == RES_BAD_ARG); + + CHK(sstl_write(NULL, 0, NULL) == RES_BAD_ARG); + CHK(sstl_write(&wd, 0, NULL) == RES_BAD_ARG); + CHK(sstl_write(NULL, 0, "test_basic.stl") == RES_BAD_ARG); + CHK(sstl_write(&wd, 0, "") == RES_IO_ERR); + CHK(sstl_write(&wd, 1, "") == RES_IO_ERR); + + file = fopen("test_basic.stl", "w"); + CHK(file != NULL); + fwrite(test0, sizeof(char), strlen(test0), file); + fclose(file); + + CHK(sstl_load_ascii(sstl, "test_basic.stl") == RES_OK); + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + CHK(sstl_write(&wd, 0, "test_basic.stl") == RES_OK); + CHK(sstl_load(sstl, "test_basic.stl") == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + + CHK(desc.solid_name == NULL); + CHK(desc.vertices_count == 3); + CHK(desc.triangles_count == 1); + CHK(desc.indices[0] == 0); + CHK(desc.indices[1] == 1); + CHK(desc.indices[2] == 2); + CHK(f3_eq(desc.vertices + 0*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.normals, f3(tmp, 0.f, -1.f, 0.f)) == 1); + + file = fopen("test_basic.stl", "w"); + CHK(file != NULL); + fwrite(test0, sizeof(char), strlen(test0), file); + fclose(file); + + CHK(sstl_load(sstl, "test_basic.stl") == RES_OK); + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + CHK(sstl_write(&wd, 1, "test_basic2.stl") == RES_OK); + CHK(sstl_load(sstl, "test_basic2.stl") == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + + CHK(desc.solid_name == NULL); + CHK(desc.vertices_count == 3); + CHK(desc.triangles_count == 1); + CHK(desc.indices[0] == 0); + CHK(desc.indices[1] == 1); + CHK(desc.indices[2] == 2); + CHK(f3_eq(desc.vertices + 0*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.normals, f3(tmp, 0.f, -1.f, 0.f)) == 1); } static void @@ -264,6 +371,7 @@ test_tetrahedron(struct sstl* sstl) struct sstl_desc desc; float tmp[3]; size_t i; + struct sstl_write_data wd; CHK(sstl != NULL); @@ -298,6 +406,146 @@ test_tetrahedron(struct sstl* sstl) CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1); f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f)); CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1); + + rewind(file); + CHK(sstl_load_stream_binary(sstl, file) == RES_BAD_ARG); + rewind(file); + CHK(sstl_load_stream_ascii(sstl, file) == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(strcmp(desc.solid_name, "cube_corner") == 0); + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1); + f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1); + + CHK(sstl_pack_write_data(sstl, &wd) == RES_OK); + CHK(sstl_write(&wd, 0, "corner.stl") == RES_OK); + CHK(sstl_write(&wd, 1, "corner_bin.stl") == RES_OK); + + CHK(sstl_load_ascii(sstl, "corner_bin.stl") == RES_BAD_ARG); + CHK(sstl_load_binary(sstl, "corner.stl") == RES_BAD_ARG); + + CHK(sstl_load(sstl, "corner.stl") == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(strcmp(desc.solid_name, "cube_corner") == 0); + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1); + f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1); + + CHK(sstl_load_ascii(sstl, "corner.stl") == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(strcmp(desc.solid_name, "cube_corner") == 0); + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1); + f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1); + + CHK(sstl_load(sstl, "corner_bin.stl") == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.solid_name == NULL); /* binary files don't store a name */ + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1); + f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1); + + CHK(sstl_load_binary(sstl, "corner_bin.stl") == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.solid_name == NULL); /* binary files don't store a name */ + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1); + f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1); } int