star-stl

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

commit bcf67f162019e8ffcd42e01851f679c6561e198f
parent 549e1b3e2b791a6fd8f5ee34185db75138440195
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 10 Apr 2025 16:42:26 +0200

Add binary support

The generic load functions detect whether the input data is ASCII or
binary and, depending on the type of data, call the ASCII or binary load
procedure respectively. However, to determine whether the data is ASCII
or binary, the input file must be seekable. A certain amount of data
must be read to determine the file type before executing the load
procedure. A simple yet robust heuristic is to read up to 1KB of data.
Therefore, input data from a pipe, FIFO or socket cannot be loaded
blindly. The caller must define its type when loading.

In addition to the untyped loading functions, this commit therefore adds
functions for loading ASCII and binary data only. The caller can then
use them to load data from, for example, standard input as long as it
knows the type of input data, either because its application supports
only one type of data, or because the interface it provides to the user
allows it to determine their type.

It should be noted that it would be possible to duplicate the input data
without affecting the input stream and, in so doing, be able to define
the type of data even when it is read from a pipe, a FIFO or a socket
(for instance with pipes and fork). However, the solutions are too
complicated to justify their merits here. In other words, such an
implementation would suck far more than the simple and stupid solution
chosen here.

Diffstat:
Msrc/sstl.c | 169++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/sstl.h | 41+++++++++++++++++++++++++++++++----------
Msrc/sstl_ascii.c | 2++
Msrc/sstl_binary.c | 2++
Msrc/sstl_c.h | 4++++
5 files changed, 185 insertions(+), 33 deletions(-)

diff --git a/src/sstl.c b/src/sstl.c @@ -41,6 +41,128 @@ enum read_type { /******************************************************************************* * Helper functions ******************************************************************************/ +static INLINE int +file_is_seekable(FILE* fp) +{ + ASSERT(fp); + if(fseek(fp, 0, SEEK_CUR) >= 0) { + return 1; /* File is seekable */ + } else { + CHK(errno == ESPIPE); + return 0; /* File is not seekable */ + } +} + +static res_T +file_type + (struct sstl* sstl, + FILE* fp, + const char* name, + enum sstl_read_type* type) +{ + char buf[1024]; + size_t sz = 0; + long fpos = 0; + res_T res = RES_OK; + + ASSERT(sstl && fp && name && type); + + if(!file_is_seekable(fp)) { + ERROR(sstl, + "%s: the file refers to a pipe, a FIFO or a socket. " + "Its type (i.e. ASCII or binary) cannot be defined on the fly.\n", + name); + res = RES_BAD_ARG; + goto error; + } + + if((fpos = ftell(fp)) < 0) { + ERROR(sstl, "%s: unable to query file position -- %s\n", + name, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + + /* Search for the NUL character in the first bytes of the file. If there is + * one, the file is assumed to be binary. This is a 'simple and stupid', yet + * robust method, used for example by some grep implementations */ + sz = fread(buf, 1, sizeof(buf), fp); + if(memchr(buf, '\0', sz)) { + *type = SSTL_BINARY; + } else { + *type = SSTL_ASCII; + } + + if(fseek(fp, fpos, SEEK_SET) < 0) { + ERROR(sstl, "%s: unable to set file position -- %s\n", + name, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + +exit: + return res; +error: + goto exit; +} + +static res_T +load_stream + (struct sstl* sstl, + FILE* fp, + const char* name, + enum sstl_read_type type) +{ + res_T res = RES_OK; + ASSERT((unsigned)type <= SSTL_NONE__); + + if(!sstl || !fp || !name) { res = RES_BAD_ARG; goto error; } + + if(type == SSTL_NONE__) { + res = file_type(sstl, fp, name, &type); + if(res != RES_OK) goto error; + } + + switch(type) { + case SSTL_ASCII: res = load_stream_ascii(sstl, fp, name); break; + case SSTL_BINARY: res = load_stream_binary(sstl, fp, name); break; + default: FATAL("Unreachable code\n"); break; + } + if(res != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +} + +static res_T +load(struct sstl* sstl, const char* filename, const enum sstl_read_type type) +{ + FILE* stream = NULL; + res_T res = RES_OK; + + ASSERT((unsigned)type <= SSTL_NONE__); + + if(!sstl || !filename) { res = RES_BAD_ARG; goto error; } + + stream = fopen(filename, "r"); + if(!stream) { + ERROR(sstl, "Error opening file %s -- %s\n", filename, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + + res = load_stream(sstl, stream, filename, type); + if(res != RES_OK) goto error; + +exit: + if(stream) CHK(fclose(stream) == 0); + return res; +error: + goto exit; +} + static void sstl_release(ref_T* ref) { @@ -162,36 +284,37 @@ sstl_ref_put(struct sstl* sstl) res_T sstl_load(struct sstl* sstl, const char* filename) { - FILE* stream = NULL; - res_T res = RES_OK; + return load(sstl, filename, SSTL_NONE__); +} - if(!sstl || !filename) { res = RES_BAD_ARG; goto error; } +res_T +sstl_load_ascii(struct sstl* sstl, const char* filename) +{ + return load(sstl, filename, SSTL_ASCII); +} - stream = fopen(filename, "r"); - if(!stream) { - ERROR(sstl, "Error opening file %s -- %s\n", filename, strerror(errno)); - res = RES_IO_ERR; - goto error; - } +res_T +sstl_load_binary(struct sstl* sstl, const char* filename) +{ + return load(sstl, filename, SSTL_BINARY); +} - res = load_stream_ascii(sstl, stream, filename); - if(res != RES_OK) goto error; +res_T +sstl_load_stream(struct sstl* sstl, FILE* fp, const char* name) +{ + return load_stream(sstl, fp, name, SSTL_NONE__); +} -exit: - if(stream) CHK(fclose(stream) == 0); - return res; -error: - goto exit; +res_T +sstl_load_stream_ascii(struct sstl* sstl, FILE* fp, const char* name) +{ + return load_stream(sstl, fp, name, SSTL_ASCII); } res_T -sstl_load_stream - (struct sstl* sstl, - FILE* stream, - const char* stream_name) +sstl_load_stream_binary(struct sstl* sstl, FILE* fp, const char* name) { - if(!sstl || !stream || !stream_name) return RES_BAD_ARG; - return load_stream_ascii(sstl, stream, stream_name); + return load_stream(sstl, fp, name, SSTL_BINARY); } res_T @@ -209,6 +332,6 @@ sstl_get_desc(struct sstl* sstl, struct sstl_desc* desc) desc->vertices = sstl->vertices; desc->indices = sstl->indices; desc->normals = sstl->normals; - desc->read_type = SSTL_ASCII; + desc->read_type = sstl->read_type; return RES_OK; } diff --git a/src/sstl.h b/src/sstl.h @@ -38,8 +38,9 @@ /* The type of a read file */ enum sstl_read_type { - SSTL_ASCII = 1, - SSTL_BINARY = 2 + SSTL_ASCII, + SSTL_BINARY, + SSTL_NONE__ }; /* Descriptor of a loaded STL */ @@ -85,25 +86,45 @@ SSTL_API res_T sstl_ref_put (struct sstl* sstl); -/* 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. */ +/* The type of StL (ASCII or binary) is defined from the contents of the file. + * The file must therefore be seekable, i.e. it cannot be a pipe, a FIFO or a + * socket */ 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_ascii + (struct sstl* sstl, + const char* filename); + +SSTL_API res_T +sstl_load_binary + (struct sstl* sstl, + const char* filename); + +/* The type of StL (Binary or ASCII) is defined from the contents of the file. + * The file pointer must therefore be seekable, i.e. it cannot be a pipe, a FIFO + * or a socket */ SSTL_API res_T sstl_load_stream (struct sstl* sstl, FILE* stream, const char* stream_name); +SSTL_API res_T +sstl_load_stream_ascii + (struct sstl* sstl, + FILE* stream, + const char* stream_name); + +SSTL_API res_T +sstl_load_stream_binary + (struct sstl* sstl, + FILE* stream, + const char* stream_name); + /* The returned descriptor is valid until a new load process */ SSTL_API res_T sstl_get_desc diff --git a/src/sstl_ascii.c b/src/sstl_ascii.c @@ -286,6 +286,8 @@ load_stream_ascii if((res = parse_solid(sstl, txtrdr)) != RES_OK) goto error; } + sstl->read_type = SSTL_ASCII; + exit: htable_vertex_purge(&sstl->vertex2id); /* Purge the helper structure */ if(txtrdr) txtrdr_ref_put(txtrdr); diff --git a/src/sstl_binary.c b/src/sstl_binary.c @@ -135,6 +135,8 @@ load_stream_binary(struct sstl* sstl, FILE* fp, const char* name) if((res = parse_triangle(sstl, fp, name, i)) != RES_OK) goto error; } + sstl->read_type = SSTL_BINARY; + exit: htable_vertex_purge(&sstl->vertex2id); /* Purge the helper structure */ return res; diff --git a/src/sstl_c.h b/src/sstl_c.h @@ -16,6 +16,8 @@ #ifndef SSTL_C_H #define SSTL_C_H +#include "sstl.h" + #include <rsys/float3.h> #include <rsys/hash_table.h> #include <rsys/logger.h> @@ -57,6 +59,7 @@ struct mem_allocator; struct sstl { int verbose; struct str name; + enum sstl_read_type read_type; /* Temporary structure used to map a vertex to its id */ struct htable_vertex vertex2id; @@ -81,6 +84,7 @@ clear(struct sstl* sstl) sstl->indices = NULL; sstl->vertices = NULL; sstl->normals = NULL; + sstl->read_type = SSTL_NONE__; htable_vertex_clear(&sstl->vertex2id); }