commit 267b609fe2323a3f41874534da175558f4e2a41f
parent f943527737b2d3ce01ac3634ce7a6e6c95274300
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Tue, 22 Apr 2025 16:59:28 +0200
Add Writer API
It is designed for streamed output, i.e. without assuming that data is
in memory. Binary or ASCII data formatting is supported, as is output to
a PIPE, fifo or socket.
Diffstat:
5 files changed, 586 insertions(+), 13 deletions(-)
diff --git a/Makefile b/Makefile
@@ -28,7 +28,7 @@ all: library tests util
################################################################################
# Library building
################################################################################
-SRC = src/sstl.c src/sstl_ascii.c src/sstl_binary.c
+SRC = src/sstl.c src/sstl_ascii.c src/sstl_binary.c src/sstl_writer.c
OBJ = $(SRC:.c=.o)
DEP = $(SRC:.c=.d)
diff --git a/src/sstl.c b/src/sstl.c
@@ -29,18 +29,6 @@
/*******************************************************************************
* 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,
diff --git a/src/sstl.h b/src/sstl.h
@@ -60,12 +60,41 @@ struct sstl_desc {
#define SSTL_DESC_NULL__ {0}
static const struct sstl_desc SSTL_DESC_NULL = SSTL_DESC_NULL__;
+struct sstl_writer_create_args {
+ const char* filename; /* Name of the file to write or of the provided stream */
+ FILE* stream; /* NULL <=> write data to the file "name" */
+
+ enum sstl_type type; /* Written data is either ASCII or binary */
+
+ const char* solid_name; /* Can be NULL. Not used for binary StL */
+
+ /* <0 <=> The number of triangles is calculated automatically.
+ * Must be set when writing binary data to a non-searchable stream */
+ long triangles_count;
+
+ struct logger* logger; /* NULL <=> use default logger */
+ struct mem_allocator* allocator; /* NULL <=> use default allocator */
+ const int verbose; /* verbose level */
+};
+#define SSTL_WRITER_CREATE_ARGS_DEFAULT__ \
+ {NULL, NULL, SSTL_ASCII, NULL, -1, NULL, NULL, 0}
+static const struct sstl_writer_create_args SSTL_WRITER_CREATE_ARGS_DEFAULT =
+ SSTL_WRITER_CREATE_ARGS_DEFAULT__;
+
+struct sstl_facet {
+ float normal[3];
+ float vertices[3][3];
+};
+#define SSTL_FACET_NULL__ {0}
+static const struct sstl_facet SSTL_FACET_NULL = SSTL_FACET_NULL__;
+
/* Forward declaration of external types */
struct logger;
struct mem_allocator;
/* Forward declaration of opaque data types */
struct sstl;
+struct sstl_writer;
/*******************************************************************************
* Star-STL API
@@ -132,6 +161,27 @@ sstl_get_desc
(struct sstl* sstl,
struct sstl_desc* desc);
+/*******************************************************************************
+ * Writer API
+ ******************************************************************************/
+SSTL_API res_T
+sstl_writer_create
+ (const struct sstl_writer_create_args* args,
+ struct sstl_writer** writer);
+
+SSTL_API res_T
+sstl_writer_ref_get
+ (struct sstl_writer* writer);
+
+SSTL_API res_T
+sstl_writer_ref_put
+ (struct sstl_writer* writer);
+
+SSTL_API res_T
+sstl_write_facet
+ (struct sstl_writer* writer,
+ const struct sstl_facet* facet);
+
END_DECLS
#endif /* SSTL_H */
diff --git a/src/sstl_c.h b/src/sstl_c.h
@@ -25,6 +25,9 @@
#include <rsys/str.h>
#include <rsys/stretchy_array.h>
+#include <errno.h>
+#include <stdio.h>
+
/* Helper macros for logging */
#define LOG__(Dev, Lvl, Type, ...) { \
if ((Dev)->verbose >= (Lvl)) \
@@ -89,6 +92,18 @@ clear(struct sstl* sstl)
htable_vertex_clear(&sstl->vertex2id);
}
+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 INLINE float*
calculate_normal
(float N[3],
diff --git a/src/sstl_writer.c b/src/sstl_writer.c
@@ -0,0 +1,520 @@
+/* Copyright (C) 2015, 2016, 2019, 2021, 2023, 2025 |Méso|Star> (contact@meso-star.com)
+ *
+ * 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 3 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "sstl.h"
+#include "sstl_c.h"
+
+#include <rsys/cstr.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+#include <rsys/str.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#define WRITING_ERROR(Writer) \
+ ERROR((Writer), "%s: writing error -- %s\n", \
+ str_cget(&(Writer)->filename), strerror(errno));
+
+struct sstl_writer {
+ struct str filename;
+ FILE* fp;
+
+ int is_fp_intern; /* Define whether fp is internally opened or not */
+ int is_init; /* Define whether the writer should be finalised or not */
+
+ long ntris; /* Number of triangles. <0 <=> not defined a priori */
+ long ntris_written; /* Number of effectively written triangles */
+ long ntris_offset; /* >= 0 <=> file offset in binary StL for #triangles */
+
+ enum sstl_type type;
+
+ struct mem_allocator* allocator;
+ struct logger* logger;
+ int verbose; /* Verbosity level */
+ ref_T ref;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static res_T
+check_sstl_writer_create_args(const struct sstl_writer_create_args* args)
+{
+ if(!args) return RES_BAD_ARG;
+
+ if(args->type != SSTL_ASCII && args->type != SSTL_BINARY)
+ return RES_BAD_ARG;
+
+ if(!args->filename)
+ return RES_BAD_ARG;
+
+ if(args->triangles_count > 0 && args->triangles_count > UINT32_MAX)
+ return RES_BAD_ARG;
+
+ return RES_OK;
+}
+
+static res_T
+setup_filename
+ (struct sstl_writer* writer,
+ const struct sstl_writer_create_args* args)
+{
+ res_T res = RES_OK;
+ ASSERT(writer && args);
+
+ if((res = str_set(&writer->filename, args->filename)) != RES_OK) {
+ ERROR(writer, "Error copying filen name '%s' -- %s\n",
+ args->filename, res_to_cstr(res));
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+setup_stream
+ (struct sstl_writer* writer,
+ const struct sstl_writer_create_args* args)
+{
+ res_T res = RES_OK;
+ ASSERT(writer && args);
+
+ if(args->stream) {
+ writer->fp = args->stream;
+
+ } else if((writer->fp = fopen(args->filename, "w")) == NULL) {
+ ERROR(writer, "Error opening file %s -- %s\n",
+ args->filename, strerror(errno));
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+ writer->is_fp_intern = args->stream != writer->fp;
+
+ /* if the data written is binary and the definition of the number of triangles
+ * is left to the author, check that the file is seekable. This is because the
+ * number of triangles must be written at the beginning of the file, whereas
+ * this number will be known after all the triangles have been written. One
+ * therefore need to be able to position the file at the correct offset once
+ * the total number of triangles is known */
+ if(args->type == SSTL_BINARY
+ && args->triangles_count < 0
+ && !file_is_seekable(writer->fp)) {
+ ERROR(writer,
+ "%s: invalid file. A binary StL can only be written to a pipe, FIFO or"
+ "socket if the total number of triangles to be written is known in"
+ "advance.\n", args->filename);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ if(writer->fp && writer->is_fp_intern) {
+ CHK(fclose(writer->fp) == 0);
+ writer->fp = NULL;
+ }
+ goto exit;
+}
+
+static res_T
+write_header_ascii
+ (struct sstl_writer* writer,
+ const struct sstl_writer_create_args* args)
+{
+ res_T res = RES_OK;
+ int n = 0;
+ ASSERT(writer && args);
+
+ if(args->solid_name) {
+ n = fprintf(writer->fp, "solid %s\n", args->solid_name);
+ } else {
+ n = fprintf(writer->fp, "solid\n");
+ }
+
+ if(n < 0) {
+ WRITING_ERROR(writer);
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+write_ntriangles(const struct sstl_writer* writer, const uint32_t ntris)
+{
+ uint8_t bytes[4] = {0};
+ res_T res = RES_OK;
+ ASSERT(writer);
+
+ bytes[0] = (uint8_t)((ntris >> 0) & 0xFF);
+ bytes[1] = (uint8_t)((ntris >> 8) & 0xFF);
+ bytes[2] = (uint8_t)((ntris >> 16) & 0xFF);
+ bytes[3] = (uint8_t)((ntris >> 24) & 0xFF);
+
+ if(fwrite(bytes, 1, 4, writer->fp) != 4) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ WRITING_ERROR(writer);
+ goto exit;
+}
+
+static res_T
+write_header_binary
+ (struct sstl_writer* writer,
+ const struct sstl_writer_create_args* args)
+{
+ uint8_t bytes[80] = {0};
+ res_T res = RES_OK;
+ ASSERT(writer && args);
+
+ if(fwrite(bytes, 1, 80, writer->fp) != 80) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+ writer->ntris = args->triangles_count;
+
+ if(args->triangles_count < 0) {
+ ASSERT(file_is_seekable(writer->fp));
+ writer->ntris_offset = ftell(writer->fp);
+
+ } else {
+ res = write_ntriangles(writer, (uint32_t)args->triangles_count);
+ if(res != RES_OK) goto error;
+ }
+
+exit:
+ return res;
+error:
+ WRITING_ERROR(writer);
+ goto exit;
+}
+
+static res_T
+write_header
+ (struct sstl_writer* writer,
+ const struct sstl_writer_create_args* args)
+{
+ res_T res = RES_OK;
+ ASSERT(writer);
+
+ switch(writer->type) {
+ case SSTL_ASCII: res = write_header_ascii(writer, args); break;
+ case SSTL_BINARY: res = write_header_binary(writer, args); break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ if(res != RES_OK) goto error;
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+write_facet_ascii(struct sstl_writer* writer, const struct sstl_facet* facet)
+{
+ float N[3] = {0,0,0};
+ res_T res = RES_OK;
+ ASSERT(writer && facet);
+
+ /* If necessary, automatically calculate the surface normal. */
+ if(!f3_is_normalized(f3_set(N, facet->normal))) {
+ calculate_normal
+ (N, facet->vertices[0], facet->vertices[1], facet->vertices[2]);
+ }
+
+ #define PRINTF(...) { \
+ if(fprintf(writer->fp, __VA_ARGS__) < 0) { \
+ WRITING_ERROR(writer); \
+ res = RES_IO_ERR; \
+ goto error; \
+ } \
+ } (void)0
+
+ PRINTF("\tfacet normal %f %f %f\n", SPLIT3(N));
+ PRINTF("\t\touter loop\n");
+ PRINTF("\t\t\tvertex %f %f %f\n", SPLIT3(facet->vertices[0]));
+ PRINTF("\t\t\tvertex %f %f %f\n", SPLIT3(facet->vertices[1]));
+ PRINTF("\t\t\tvertex %f %f %f\n", SPLIT3(facet->vertices[2]));
+ PRINTF("\t\tendloop\n");
+ PRINTF("\tendfacet\n");
+
+ #undef PRINTF
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+write_facet_binary(struct sstl_writer* writer, const struct sstl_facet* facet)
+{
+ float N[3] = {0,0,0};
+ uint8_t bytes[4] = {0};
+ int i = 0;
+ res_T res = RES_OK;
+ ASSERT(writer && facet);
+
+ /* If necessary, automatically calculate the surface normal. */
+ if(!f3_is_normalized(f3_set(N, facet->normal))) {
+ calculate_normal
+ (N, facet->vertices[0], facet->vertices[1], facet->vertices[2]);
+ }
+
+ #define WRITE(Float) { \
+ union { uint32_t ui32; float f; } ucast = { .f = Float }; \
+ bytes[0] = (uint8_t)((ucast.ui32 >> 0) & 0xFF); \
+ bytes[1] = (uint8_t)((ucast.ui32 >> 8) & 0xFF); \
+ bytes[2] = (uint8_t)((ucast.ui32 >> 16) & 0xFF); \
+ bytes[3] = (uint8_t)((ucast.ui32 >> 24) & 0xFF); \
+ if(fwrite(bytes, 1, 4, writer->fp) != 4) { \
+ res = RES_IO_ERR; \
+ goto error; \
+ } \
+ } (void)0
+
+ WRITE(N[0]);
+ WRITE(N[1]);
+ WRITE(N[2]);
+
+ FOR_EACH(i, 0, 3) {
+ WRITE(facet->vertices[i][0]);
+ WRITE(facet->vertices[i][1]);
+ WRITE(facet->vertices[i][2]);
+ }
+
+ #undef WRITE
+
+ /* #attribs (not used) */
+ bytes[0] = 0;
+ bytes[1] = 0;
+ if(fwrite(bytes, 1, 2, writer->fp) != 2) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ WRITING_ERROR(writer);
+ goto exit;
+}
+
+static res_T
+finalize_ascii(struct sstl_writer* writer)
+{
+ res_T res = RES_OK;
+ ASSERT(writer);
+
+ if(fprintf(writer->fp, "endsolid\n") < 0) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+ if(fflush(writer->fp) != 0) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ WRITING_ERROR(writer);
+ goto exit;
+}
+
+static res_T
+finalize_binary(struct sstl_writer* writer)
+{
+ res_T res = RES_OK;
+ ASSERT(writer);
+
+ /* Check that the number of triangles written is as expected. Note that it
+ * cannot be greater than the number supplied by the user when the writer was
+ * created; an error must have been detected before writing a facet that
+ * should not exist */
+ ASSERT(writer->ntris < 0 || writer->ntris_written <= writer->ntris);
+ if(writer->ntris >= 0 && writer->ntris_written < writer->ntris) {
+ ERROR(writer, "%s: triangles are missing\n", str_cget(&writer->filename));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(writer->ntris_offset >= 0) {
+ if(fseek(writer->fp, writer->ntris_offset, SEEK_SET) != 0) {
+ WRITING_ERROR(writer);
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+ res = write_ntriangles(writer, (uint32_t)writer->ntris_written);
+ if(res != RES_OK) goto error;
+ }
+
+ if(fflush(writer->fp) != 0) {
+ WRITING_ERROR(writer);
+ res = RES_IO_ERR;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+finalize(struct sstl_writer* writer)
+{
+ res_T res = RES_OK;
+ ASSERT(writer);
+
+ switch(writer->type) {
+ case SSTL_ASCII: res = finalize_ascii(writer); break;
+ case SSTL_BINARY: res = finalize_binary(writer); break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ return res;
+}
+
+static void
+release_writer(ref_T* ref)
+{
+ struct sstl_writer* writer = CONTAINER_OF(ref, struct sstl_writer, ref);
+ ASSERT(ref);
+
+ if(writer->is_init) finalize(writer);
+ if(writer->is_fp_intern) CHK(fclose(writer->fp) == 0);
+ str_release(&writer->filename);
+ MEM_RM(writer->allocator, writer);
+}
+
+/*******************************************************************************
+ * Exported symbols
+ ******************************************************************************/
+res_T
+sstl_writer_create
+ (const struct sstl_writer_create_args* args,
+ struct sstl_writer** out_writer)
+{
+ struct sstl_writer* writer = NULL;
+ struct mem_allocator* allocator = NULL;
+ struct logger* logger = NULL;
+ res_T res = RES_OK;
+
+ if(!out_writer) { res = RES_BAD_ARG; goto error; }
+ if((res = check_sstl_writer_create_args(args)) != RES_OK) goto error;
+
+ allocator = args->allocator ? args->allocator : &mem_default_allocator;
+ logger = args->logger ? args->logger : LOGGER_DEFAULT;
+
+ writer = MEM_CALLOC(allocator, 1, sizeof(*writer));
+ if(!writer) {
+ if(args->verbose) {
+ logger_print(logger, LOG_ERROR, "Couldn't allocate the Star-StL writer\n");
+ }
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+ ref_init(&writer->ref);
+ writer->allocator = allocator;
+ writer->logger = logger;
+ writer->verbose = args->verbose;
+ writer->type = args->type;
+ writer->ntris = args->triangles_count;
+ writer->ntris_offset = -1;
+ str_init(writer->allocator, &writer->filename);
+
+ if((res = setup_filename(writer, args)) != RES_OK) goto error;
+ if((res = setup_stream(writer, args)) != RES_OK) goto error;
+ if((res = write_header(writer, args)) != RES_OK) goto error;
+
+ writer->is_init = 1;
+
+exit:
+ if(out_writer) *out_writer = writer;
+ return res;
+error:
+ if(writer) { SSTL(writer_ref_put(writer)); writer = NULL; }
+ goto exit;
+}
+
+res_T
+sstl_writer_ref_get(struct sstl_writer* writer)
+{
+ if(!writer) return RES_BAD_ARG;
+ ref_get(&writer->ref);
+ return RES_OK;
+}
+
+res_T
+sstl_writer_ref_put(struct sstl_writer* writer)
+{
+ if(!writer) return RES_BAD_ARG;
+ ref_put(&writer->ref, release_writer);
+ return RES_OK;
+}
+
+res_T
+sstl_write_facet(struct sstl_writer* writer, const struct sstl_facet* facet)
+{
+ res_T res = RES_OK;
+
+ if(!writer || !facet) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(writer->ntris == writer->ntris_written
+ || writer->ntris_written == UINT32_MAX) {
+ ERROR(writer, "%s: the number of facets is greater than expected\n",
+ str_cget(&writer->filename));
+ res = RES_BAD_OP;
+ goto error;
+ }
+
+ switch(writer->type) {
+ case SSTL_ASCII: res = write_facet_ascii(writer, facet); break;
+ case SSTL_BINARY: res = write_facet_binary(writer, facet); break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ if(res != RES_OK) goto error;
+
+ ++writer->ntris_written;
+
+exit:
+ return res;
+error:
+ goto exit;
+}