star-stl

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

sstl_writer.c (12765B)


      1 /* Copyright (C) 2015, 2016, 2019, 2021, 2023, 2025 |Méso|Star> (contact@meso-star.com)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU Lesser General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU Lesser General Public License
     14  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #include "sstl.h"
     17 #include "sstl_c.h"
     18 
     19 #include <rsys/cstr.h>
     20 #include <rsys/mem_allocator.h>
     21 #include <rsys/ref_count.h>
     22 #include <rsys/str.h>
     23 
     24 #include <errno.h>
     25 #include <stdio.h>
     26 #include <string.h>
     27 
     28 #define WRITING_ERROR(Writer) \
     29  ERROR((Writer), "%s: writing error -- %s\n", \
     30   str_cget(&(Writer)->filename), strerror(errno));
     31 
     32 struct sstl_writer {
     33   struct str filename;
     34   FILE* fp;
     35 
     36   int is_fp_intern; /* Define whether fp is internally opened or not */
     37   int is_init; /* Define whether the writer should be finalised or not */
     38 
     39   long ntris; /* Number of triangles. <0 <=> not defined a priori */
     40   long ntris_written; /* Number of effectively written triangles */
     41   long ntris_offset; /* >= 0 <=> file offset in binary StL for #triangles */
     42 
     43   enum sstl_type type;
     44 
     45   struct mem_allocator* allocator;
     46   struct logger* logger;
     47   int verbose; /* Verbosity level */
     48   ref_T ref;
     49 };
     50 
     51 /*******************************************************************************
     52  * Helper functions
     53  ******************************************************************************/
     54 static res_T
     55 check_sstl_writer_create_args(const struct sstl_writer_create_args* args)
     56 {
     57   if(!args) return RES_BAD_ARG;
     58 
     59   if(args->type != SSTL_ASCII && args->type != SSTL_BINARY)
     60     return RES_BAD_ARG;
     61 
     62   if(!args->filename)
     63     return RES_BAD_ARG;
     64 
     65   if(args->triangles_count > 0 && args->triangles_count > UINT32_MAX)
     66     return RES_BAD_ARG;
     67 
     68   return RES_OK;
     69 }
     70 
     71 static res_T
     72 setup_filename
     73   (struct sstl_writer* writer,
     74    const struct sstl_writer_create_args* args)
     75 {
     76   res_T res = RES_OK;
     77   ASSERT(writer && args);
     78 
     79   if((res = str_set(&writer->filename, args->filename)) != RES_OK) {
     80     ERROR(writer, "Error copying filen name '%s' -- %s\n",
     81       args->filename, res_to_cstr(res));
     82     goto error;
     83   }
     84 
     85 exit:
     86   return res;
     87 error:
     88   goto exit;
     89 }
     90 
     91 static res_T
     92 setup_stream
     93   (struct sstl_writer* writer,
     94    const struct sstl_writer_create_args* args)
     95 {
     96   res_T res = RES_OK;
     97   ASSERT(writer && args);
     98 
     99   if(args->stream) {
    100     writer->fp = args->stream;
    101 
    102   } else if((writer->fp = fopen(args->filename, "w")) == NULL) {
    103     ERROR(writer, "Error opening file %s -- %s\n",
    104       args->filename, strerror(errno));
    105     res = RES_IO_ERR;
    106     goto error;
    107   }
    108 
    109   writer->is_fp_intern = args->stream != writer->fp;
    110 
    111   /* if the data written is binary and the definition of the number of triangles
    112    * is left to the author, check that the file is seekable. This is because the
    113    * number of triangles must be written at the beginning of the file, whereas
    114    * this number will be known after all the triangles have been written. One
    115    * therefore need to be able to position the file at the correct offset once
    116    * the total number of triangles is known */
    117   if(args->type == SSTL_BINARY
    118   && args->triangles_count < 0
    119   && !file_is_seekable(writer->fp)) {
    120     ERROR(writer,
    121       "%s: invalid file. A binary StL can only be written to a pipe, FIFO or "
    122       "socket if the total number of triangles to be written is known in "
    123       "advance.\n", args->filename);
    124     res = RES_BAD_ARG;
    125     goto error;
    126   }
    127 
    128 exit:
    129   return res;
    130 error:
    131   if(writer->fp && writer->is_fp_intern) {
    132     CHK(fclose(writer->fp) == 0);
    133     writer->fp = NULL;
    134   }
    135   goto exit;
    136 }
    137 
    138 static res_T
    139 write_header_ascii
    140   (struct sstl_writer* writer,
    141    const struct sstl_writer_create_args* args)
    142 {
    143   res_T res = RES_OK;
    144   int n = 0;
    145   ASSERT(writer && args);
    146 
    147   if(args->solid_name) {
    148     n = fprintf(writer->fp, "solid %s\n", args->solid_name);
    149   } else {
    150     n = fprintf(writer->fp, "solid\n");
    151   }
    152 
    153   if(n < 0) {
    154     WRITING_ERROR(writer);
    155     res = RES_IO_ERR;
    156     goto error;
    157   }
    158 
    159 exit:
    160   return res;
    161 error:
    162   goto exit;
    163 }
    164 
    165 static res_T
    166 write_ntriangles(const struct sstl_writer* writer, const uint32_t ntris)
    167 {
    168   uint8_t bytes[4] = {0};
    169   res_T res = RES_OK;
    170   ASSERT(writer);
    171 
    172   bytes[0] = (uint8_t)((ntris >> 0) & 0xFF);
    173   bytes[1] = (uint8_t)((ntris >> 8) & 0xFF);
    174   bytes[2] = (uint8_t)((ntris >> 16) & 0xFF);
    175   bytes[3] = (uint8_t)((ntris >> 24) & 0xFF);
    176 
    177   if(fwrite(bytes, 1, 4, writer->fp) != 4) {
    178     res = RES_IO_ERR;
    179     goto error;
    180   }
    181 
    182 exit:
    183   return res;
    184 error:
    185   WRITING_ERROR(writer);
    186   goto exit;
    187 }
    188 
    189 static res_T
    190 write_header_binary
    191   (struct sstl_writer* writer,
    192    const struct sstl_writer_create_args* args)
    193 {
    194   uint8_t bytes[80] = {0};
    195   uint32_t ntris = 0;
    196   res_T res = RES_OK;
    197   ASSERT(writer && args);
    198 
    199   if(fwrite(bytes, 1, 80, writer->fp) != 80) {
    200     res = RES_IO_ERR;
    201     goto error;
    202   }
    203 
    204   writer->ntris = args->triangles_count;
    205 
    206   if(args->triangles_count < 0) {
    207     ASSERT(file_is_seekable(writer->fp));
    208     writer->ntris_offset = ftell(writer->fp);
    209   } else {
    210     ntris = (uint32_t)args->triangles_count;
    211   }
    212 
    213   res = write_ntriangles(writer, ntris);
    214   if(res != RES_OK) goto error;
    215 
    216 exit:
    217   return res;
    218 error:
    219   WRITING_ERROR(writer);
    220   goto exit;
    221 }
    222 
    223 static res_T
    224 write_header
    225   (struct sstl_writer* writer,
    226    const struct sstl_writer_create_args* args)
    227 {
    228   res_T res = RES_OK;
    229   ASSERT(writer);
    230 
    231   switch(writer->type) {
    232     case SSTL_ASCII: res = write_header_ascii(writer, args); break;
    233     case SSTL_BINARY: res = write_header_binary(writer, args); break;
    234     default: FATAL("Unreachable code\n"); break;
    235   }
    236   if(res != RES_OK) goto error;
    237 
    238 exit:
    239   return res;
    240 error:
    241   goto exit;
    242 }
    243 
    244 static res_T
    245 write_facet_ascii(struct sstl_writer* writer, const struct sstl_facet* facet)
    246 {
    247   float N[3] = {0,0,0};
    248   res_T res = RES_OK;
    249   ASSERT(writer && facet);
    250 
    251   /* If necessary, automatically calculate the surface normal. */
    252   if(!f3_is_normalized(f3_set(N, facet->normal))) {
    253     calculate_normal
    254       (N, facet->vertices[0], facet->vertices[1], facet->vertices[2]);
    255   }
    256 
    257   #define PRINTF(...) { \
    258     if(fprintf(writer->fp, __VA_ARGS__) < 0) { \
    259       WRITING_ERROR(writer); \
    260       res = RES_IO_ERR; \
    261       goto error; \
    262     } \
    263   } (void)0
    264 
    265   PRINTF("\tfacet normal %f %f %f\n", SPLIT3(N));
    266   PRINTF("\t\touter loop\n");
    267   PRINTF("\t\t\tvertex %f %f %f\n", SPLIT3(facet->vertices[0]));
    268   PRINTF("\t\t\tvertex %f %f %f\n", SPLIT3(facet->vertices[1]));
    269   PRINTF("\t\t\tvertex %f %f %f\n", SPLIT3(facet->vertices[2]));
    270   PRINTF("\t\tendloop\n");
    271   PRINTF("\tendfacet\n");
    272 
    273   #undef PRINTF
    274 
    275 exit:
    276   return res;
    277 error:
    278   goto exit;
    279 }
    280 
    281 static res_T
    282 write_facet_binary(struct sstl_writer* writer, const struct sstl_facet* facet)
    283 {
    284   float N[3] = {0,0,0};
    285   uint8_t bytes[4] = {0};
    286   int i = 0;
    287   res_T res = RES_OK;
    288   ASSERT(writer && facet);
    289 
    290   /* If necessary, automatically calculate the surface normal. */
    291   if(!f3_is_normalized(f3_set(N, facet->normal))) {
    292     calculate_normal
    293       (N, facet->vertices[0], facet->vertices[1], facet->vertices[2]);
    294   }
    295 
    296   #define WRITE(Float) { \
    297     union { uint32_t ui32; float f; } ucast = { .f = Float }; \
    298     bytes[0] = (uint8_t)((ucast.ui32 >> 0) & 0xFF); \
    299     bytes[1] = (uint8_t)((ucast.ui32 >> 8) & 0xFF); \
    300     bytes[2] = (uint8_t)((ucast.ui32 >> 16) & 0xFF); \
    301     bytes[3] = (uint8_t)((ucast.ui32 >> 24) & 0xFF); \
    302     if(fwrite(bytes, 1, 4, writer->fp) != 4) { \
    303       res = RES_IO_ERR; \
    304       goto error; \
    305     } \
    306   } (void)0
    307 
    308   WRITE(N[0]);
    309   WRITE(N[1]);
    310   WRITE(N[2]);
    311 
    312   FOR_EACH(i, 0, 3) {
    313     WRITE(facet->vertices[i][0]);
    314     WRITE(facet->vertices[i][1]);
    315     WRITE(facet->vertices[i][2]);
    316   }
    317 
    318   #undef WRITE
    319 
    320   /* #attribs (not used) */
    321   bytes[0] = 0;
    322   bytes[1] = 0;
    323   if(fwrite(bytes, 1, 2, writer->fp) != 2) {
    324     res = RES_IO_ERR;
    325     goto error;
    326   }
    327 
    328 exit:
    329   return res;
    330 error:
    331   WRITING_ERROR(writer);
    332   goto exit;
    333 }
    334 
    335 static res_T
    336 finalize_ascii(struct sstl_writer* writer)
    337 {
    338   res_T res = RES_OK;
    339   ASSERT(writer);
    340 
    341   if(fprintf(writer->fp, "endsolid\n") < 0) {
    342     res = RES_IO_ERR;
    343     goto error;
    344   }
    345 
    346   if(fflush(writer->fp) != 0) {
    347     res = RES_IO_ERR;
    348     goto error;
    349   }
    350 
    351 exit:
    352   return res;
    353 error:
    354   WRITING_ERROR(writer);
    355   goto exit;
    356 }
    357 
    358 static res_T
    359 finalize_binary(struct sstl_writer* writer)
    360 {
    361   res_T res = RES_OK;
    362   ASSERT(writer);
    363 
    364   /* Check that the number of triangles written is as expected.  Note that it
    365    * cannot be greater than the number supplied by the user when the writer was
    366    * created; an error must have been detected before writing a facet that
    367    * should not exist */
    368   ASSERT(writer->ntris < 0 || writer->ntris_written <= writer->ntris);
    369   if(writer->ntris >= 0 && writer->ntris_written < writer->ntris) {
    370     WARN(writer, "%s: triangles are missing\n", str_cget(&writer->filename));
    371   }
    372 
    373   if(writer->ntris_offset >= 0) {
    374     if(fseek(writer->fp, writer->ntris_offset, SEEK_SET) != 0) {
    375       WRITING_ERROR(writer);
    376       res = RES_IO_ERR;
    377       goto error;
    378     }
    379 
    380     res = write_ntriangles(writer, (uint32_t)writer->ntris_written);
    381     if(res != RES_OK) goto error;
    382   }
    383 
    384   if(fflush(writer->fp) != 0) {
    385     WRITING_ERROR(writer);
    386     res = RES_IO_ERR;
    387     goto error;
    388   }
    389 
    390 exit:
    391   return res;
    392 error:
    393   goto exit;
    394 }
    395 
    396 static res_T
    397 finalize(struct sstl_writer* writer)
    398 {
    399   res_T res = RES_OK;
    400   ASSERT(writer);
    401 
    402   switch(writer->type) {
    403     case SSTL_ASCII: res = finalize_ascii(writer); break;
    404     case SSTL_BINARY: res = finalize_binary(writer); break;
    405     default: FATAL("Unreachable code\n"); break;
    406   }
    407   return res;
    408 }
    409 
    410 static void
    411 release_writer(ref_T* ref)
    412 {
    413   struct sstl_writer* writer = CONTAINER_OF(ref, struct sstl_writer, ref);
    414   ASSERT(ref);
    415 
    416   if(writer->is_init) CHK(finalize(writer) == RES_OK);
    417   if(writer->is_fp_intern) CHK(fclose(writer->fp) == 0);
    418   str_release(&writer->filename);
    419   MEM_RM(writer->allocator, writer);
    420 }
    421 
    422 /*******************************************************************************
    423  * Exported symbols
    424  ******************************************************************************/
    425 res_T
    426 sstl_writer_create
    427   (const struct sstl_writer_create_args* args,
    428    struct sstl_writer** out_writer)
    429 {
    430   struct sstl_writer* writer = NULL;
    431   struct mem_allocator* allocator = NULL;
    432   struct logger* logger = NULL;
    433   res_T res = RES_OK;
    434 
    435   if(!out_writer) { res = RES_BAD_ARG; goto error; }
    436   if((res = check_sstl_writer_create_args(args)) != RES_OK) goto error;
    437 
    438   allocator = args->allocator ? args->allocator : &mem_default_allocator;
    439   logger = args->logger ? args->logger : LOGGER_DEFAULT;
    440 
    441   writer = MEM_CALLOC(allocator, 1, sizeof(*writer));
    442   if(!writer) {
    443     if(args->verbose) {
    444       logger_print(logger, LOG_ERROR, "Couldn't allocate the Star-StL writer\n");
    445     }
    446     res = RES_MEM_ERR;
    447     goto error;
    448   }
    449 
    450   ref_init(&writer->ref);
    451   writer->allocator = allocator;
    452   writer->logger = logger;
    453   writer->verbose = args->verbose;
    454   writer->type = args->type;
    455   writer->ntris = args->triangles_count;
    456   writer->ntris_offset = -1;
    457   str_init(writer->allocator, &writer->filename);
    458 
    459   if((res = setup_filename(writer, args)) != RES_OK) goto error;
    460   if((res = setup_stream(writer, args)) != RES_OK) goto error;
    461   if((res = write_header(writer, args)) != RES_OK) goto error;
    462 
    463   writer->is_init = 1;
    464 
    465 exit:
    466   if(out_writer) *out_writer = writer;
    467   return res;
    468 error:
    469   if(writer) { SSTL(writer_ref_put(writer)); writer = NULL; }
    470   goto exit;
    471 }
    472 
    473 res_T
    474 sstl_writer_ref_get(struct sstl_writer* writer)
    475 {
    476   if(!writer) return RES_BAD_ARG;
    477   ref_get(&writer->ref);
    478   return RES_OK;
    479 }
    480 
    481 res_T
    482 sstl_writer_ref_put(struct sstl_writer* writer)
    483 {
    484   if(!writer) return RES_BAD_ARG;
    485   ref_put(&writer->ref, release_writer);
    486   return RES_OK;
    487 }
    488 
    489 res_T
    490 sstl_write_facet(struct sstl_writer* writer, const struct sstl_facet* facet)
    491 {
    492   res_T res = RES_OK;
    493 
    494   if(!writer || !facet) {
    495     res = RES_BAD_ARG;
    496     goto error;
    497   }
    498 
    499   if(writer->ntris == writer->ntris_written
    500   || writer->ntris_written == UINT32_MAX) {
    501     ERROR(writer, "%s: the number of facets is greater than expected\n",
    502       str_cget(&writer->filename));
    503     res = RES_BAD_OP;
    504     goto error;
    505   }
    506 
    507   switch(writer->type) {
    508     case SSTL_ASCII: res = write_facet_ascii(writer, facet); break;
    509     case SSTL_BINARY: res = write_facet_binary(writer, facet); break;
    510     default: FATAL("Unreachable code\n"); break;
    511   }
    512   if(res != RES_OK) goto error;
    513 
    514   ++writer->ntris_written;
    515 
    516 exit:
    517   return res;
    518 error:
    519   goto exit;
    520 }