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 }