star-stl

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

commit 658f2a58a7123d7f4c8143e94182468d4b8878a6
parent 374b770d998c2a67bfa2785044ed6628d8dd066f
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Fri,  9 May 2025 14:59:37 +0200

Merge branch 'release_0.6'

Diffstat:
M.gitignore | 19++++++++++---------
MMakefile | 162++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
MREADME.md | 32++++++++++++++++++++++++++++----
Mconfig.mk | 14++++++++++----
Dmake.sh | 70----------------------------------------------------------------------
Msrc/sstl.c | 1026++++++++++---------------------------------------------------------------------
Msrc/sstl.h | 137++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Asrc/sstl_ascii.c | 298+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sstl_binary.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sstl_c.h | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sstl_main.c | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sstl_writer.c | 520+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_sstl.c | 14++++++++++++--
Dsrc/test_sstl_load.c | 582------------------------------------------------------------------------------
Asrc/test_sstl_load_ascii.c | 483+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_sstl_load_binary.c | 413+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/test_sstl_utils.h | 33---------------------------------
Asrc/test_sstl_writer.c | 345+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asstl.1 | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
19 files changed, 3033 insertions(+), 1718 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,13 +1,14 @@ -.gitignore -[Bb]uild* -*.sw[po] -*.[aod] -*.so *~ -test* -!test*.[ch] +*.[aod] +[Bb]uild* .config -.test -tags +.gitignore *.pc +*.so +sstl *.stl +*.sw[po] +tags +.test +test* +!test*.[ch] diff --git a/Makefile b/Makefile @@ -1,4 +1,4 @@ -# Copyright (C) 2015, 2016, 2019, 2021, 2023 |Méso|Star> (contact@meso-star.com) +# 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 @@ -22,14 +22,20 @@ LIBNAME_STATIC = libsstl.a LIBNAME_SHARED = libsstl.so LIBNAME = $(LIBNAME_$(LIB_TYPE)) +default: library +all: library tests util + ################################################################################ # Library building ################################################################################ -SRC = src/sstl.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) -build_library: .config $(DEP) +CFLAGS_LIB = -std=c99 $(CFLAGS_SO) $(INCS) -DSSTL_SHARED_BUILD +LDFLAGS_LIB = $(LDFLAGS_SO) $(LIBS) -lm + +library: .config $(DEP) @$(MAKE) -fMakefile $$(for i in $(DEP); do echo -f $${i}; done) \ $$(if [ -n "$(LIBNAME)" ]; then\ echo "$(LIBNAME)";\ @@ -40,7 +46,7 @@ build_library: .config $(DEP) $(DEP) $(OBJ): config.mk $(LIBNAME_SHARED): $(OBJ) - $(CC) -std=c99 $(CFLAGS_SO) $(RSYS_CFLAGS) -o $@ $(OBJ) $(LDFLAGS_SO) $(RSYS_LIBS) -lm + $(CC) $(CFLAGS_LIB) -o $@ $(OBJ) $(LDFLAGS_LIB) $(LIBNAME_STATIC): libsstl.o $(AR) -rc $@ $? @@ -51,16 +57,42 @@ libsstl.o: $(OBJ) $(OBJCOPY) $(OCPFLAGS) $@ .config: config.mk - @if ! $(PKG_CONFIG) --atleast-version $(RSYS_VERSION) rsys; then \ - echo "rsys $(RSYS_VERSION) not found"; exit 1; fi - @echo "config done" > $@ + $(PKG_CONFIG) --atleast-version $(RSYS_VERSION) rsys + echo "config done" > $@ .SUFFIXES: .c .d .o .c.d: - @$(CC) -std=c99 $(CFLAGS_SO) $(RSYS_CFLAGS) -MM -MT "$(@:.d=.o) $@" $< -MF $@ + @$(CC) $(CFLAGS_LIB) -MM -MT "$(@:.d=.o) $@" $< -MF $@ .c.o: - $(CC) -std=c99 $(CFLAGS_SO) $(RSYS_CFLAGS) -DSSTL_SHARED_BUILD -c $< -o $@ + $(CC) $(CFLAGS_LIB) -c $< -o $@ + +################################################################################ +# Utils +################################################################################ +UTIL_SRC = src/sstl_main.c +UTIL_OBJ = $(UTIL_SRC:.c=.o) +UTIL_DEP = $(UTIL_SRC:.c=.d) + +PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) + +INCS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rsys sstl-local) +LIBS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys sstl-local) + +CFLAGS_UTIL = -std=c99 $(CFLAGS_EXE) $(INCS_UTIL) +LDFLAGS_UTIL = $(LDFLAGS_EXE) $(LIBS_UTIL) + +util: library $(UTIL_DEP) + @$(MAKE) -fMakefile -f"$(UTIL_DEP)" sstl + +sstl: config.mk sstl-local.pc $(UTIL_OBJ) $(LIBNAME) + $(CC) $(CFLAGS_UTIL) -o $@ $(UTIL_OBJ) $(LDFLAGS_UTIL) + +$(UTIL_DEP): config.mk sstl-local.pc + @$(CC) $(CFLAGS_UTIL) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ + +$(UTIL_OBJ): config.mk sstl-local.pc + $(CC) $(CFLAGS_UTIL) -c $(@:.o=.c) -o $@ ################################################################################ # Installation @@ -73,72 +105,102 @@ pkg: sstl-local.pc: sstl.pc.in sed -e '1d'\ - -e 's#^includedir=.*#includedir=./src/#'\ - -e 's#^libdir=.*#libdir=./#'\ - -e 's#@VERSION@#$(VERSION)#g'\ + -e 's#^includedir=.*#includedir=./src/#' \ + -e 's#^libdir=.*#libdir=./#' \ + -e 's#@VERSION@#$(VERSION)#g' \ -e 's#@RSYS_VERSION@#$(RSYS_VERSION)#g' \ sstl.pc.in > $@ -install: build_library pkg - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/lib" $(LIBNAME) - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/lib/pkgconfig" sstl.pc - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/include/star" src/sstl.h - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/share/doc/star-stl" COPYING README.md +install: library util pkg + install() { mode="$$1"; prefix="$$2"; shift 2; \ + mkdir -p "$${prefix}"; \ + cp "$$@" "$${prefix}"; \ + chmod "$${mode}" "$$@"; \ + }; \ + install 755 "$(DESTDIR)$(LIBPREFIX)" $(LIBNAME); \ + install 644 "$(DESTDIR)$(LIBPREFIX)/pkgconfig" sstl.pc; \ + install 644 "$(DESTDIR)$(INCPREFIX)/star" src/sstl.h; \ + install 755 "$(DESTDIR)$(BINPREFIX)" sstl; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man1/" sstl.1; \ + install 644 "$(DESTDIR)$(PREFIX)/share/doc/star-stl" COPYING README.md uninstall: - rm -f "$(DESTDIR)$(PREFIX)/lib/$(LIBNAME)" - rm -f "$(DESTDIR)$(PREFIX)/lib/pkgconfig/sstl.pc" + rm -f "$(DESTDIR)$(LIBPREFIX)/$(LIBNAME)" + rm -f "$(DESTDIR)$(LIBPREFIX)/pkgconfig/sstl.pc" + rm -f "$(DESTDIR)$(INCPREFIX)/star/sstl.h" + rm -f "$(DESTDIR)$(BINPREFIX)/sstl" + rm -f "$(DESTDIR)$(MANPREFIX)/man1/sstl.1" rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-stl/COPYING" rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-stl/README.md" - rm -f "$(DESTDIR)$(PREFIX)/include/star/sstl.h" - -################################################################################ -# Miscellaneous targets -################################################################################ -all: build_library build_tests clean: clean_test - rm -f $(OBJ) $(TEST_OBJ) $(LIBNAME) - rm -f .config .test libsstl.o sstl.pc sstl-local.pc - -distclean: clean - rm -f $(DEP) $(TEST_DEP) + rm -f $(OBJ) $(DEP) $(LIBNAME) + rm -f $(UTIL_OBJ) $(UTIL_DEP) sstl + rm -f .config libsstl.o sstl.pc sstl-local.pc lint: - shellcheck -o all make.sh + mandoc -Tlint sstl.1 ################################################################################ # Tests ################################################################################ TEST_SRC =\ src/test_sstl.c\ - src/test_sstl_load.c + src/test_sstl_load_ascii.c\ + src/test_sstl_load_binary.c\ + src/test_sstl_writer.c TEST_OBJ = $(TEST_SRC:.c=.o) TEST_DEP = $(TEST_SRC:.c=.d) +TEST_TGT = $(TEST_SRC:.c=.t) -PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) -SSTL_CFLAGS = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags sstl-local.pc) -SSTL_LIBS = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs sstl-local.pc) - -build_tests: build_library $(TEST_DEP) .test - @$(MAKE) -fMakefile -f.test $$(for i in $(TEST_DEP); do echo -f"$${i}"; done) test_bin +INCS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rsys sstl-local) +LIBS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys sstl-local) -test: build_tests - @$(SHELL) make.sh run_test $(TEST_SRC) +CFLAGS_TEST = -std=c89 $(CFLAGS_EXE) $(INCS_TEST) +LDFLAGS_TEST = $(LDFLAGS_EXE) $(LIBS_TEST) -lm -.test: Makefile - @$(SHELL) make.sh config_test $(TEST_SRC) > $@ +tests: library $(TEST_DEP) $(TEST_TGT) + @$(MAKE) -fMakefile \ + $$(for i in $(TEST_DEP); do echo -f"$${i}"; done) \ + $$(for i in $(TEST_TGT); do echo -f"$${i}"; done) \ + test_list -clean_test: - rm -f corner.stl corner_bin.stl test_basic.stl test_basic2.stl - $(SHELL) make.sh clean_test $(TEST_SRC) +$(TEST_TGT): + @{ \ + exe="$$(basename "$@" ".t")"; \ + printf '%s: %s\n' "$${exe}" $(@:.t=.o); \ + printf 'test_list: %s\n' "$${exe}"; \ + } > $@ $(TEST_DEP): config.mk sstl-local.pc - @$(CC) -std=c89 $(CFLAGS_EXE) $(RSYS_CFLAGS) $(SSTL_CFLAGS) \ - -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ + @$(CC) $(CFLAGS_TEST) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ $(TEST_OBJ): config.mk sstl-local.pc - $(CC) -std=c89 $(CFLAGS_EXE) $(RSYS_CFLAGS) $(SSTL_CFLAGS) -c $(@:.o=.c) -o $@ + $(CC) $(CFLAGS_TEST) -c $(@:.o=.c) -o $@ -test_sstl test_sstl_load: config.mk sstl-local.pc $(LIBNAME) - $(CC) -std=c89 $(CFLAGS_EXE) -o $@ src/$@.o $(LDFLAGS_EXE) $(SSTL_LIBS) $(RSYS_LIBS) -lm +test_sstl \ +test_sstl_load_ascii \ +test_sstl_load_binary \ +test_sstl_writer \ +: config.mk sstl-local.pc $(LIBNAME) + $(CC) $(CFLAGS_TEST) -o $@ src/$@.o $(LDFLAGS_TEST) + +clean_test: + rm -f $(TEST_DEP) $(TEST_OBJ) $(TEST_TGT) + rm -f 1_triangle.stl 1_triangle_no_normal.stl + rm -f 1_triangle_with_noise.stl empty.stl test.stl + for i in $(TEST_SRC); do rm -f "$$(basename "$${i}" ".c")"; done + +test: tests + @err=0; \ + for i in $(TEST_SRC); do \ + test="$$(basename "$${i}" ".c")"; \ + printf '%s' "$${test}"; \ + if "./$${test}" > /dev/null 2>&1; then \ + printf '\n'; \ + else \ + printf ': error %s\n' "$$?"; \ + err=$$((err+1)); \ + fi \ + done; \ + [ "$${err}" -eq 0 ] diff --git a/README.md b/README.md @@ -1,6 +1,6 @@ # Star STereo Lithography -Star-STL loads STL file format +Star-StL loads StL file format ## Requirements @@ -8,6 +8,7 @@ Star-STL loads STL file format - POSIX make - pkg-config - [RSys](https://gitlab.com/vaplv/rsys/) +- [mandoc](https://mandoc.bsd.lv) ## Installation @@ -17,11 +18,34 @@ Edit config.mk as needed, then run: ## Release notes +### Version 0.6 + +- Improve code readability and simplicity by rewriting the internal + architecture and implementation. +- Improve detection of the type of StL loaded (binary or ASCII). +- Prohibit the loading of untyped StL on non-searchable streams such as + pipes, FIFOs or sockets. + This would require duplicating input data, a complex implementation + for a marginal feature, in other words, "it would suck". +- Rewrite the "write" API. + It's designed to simplify streamed output, i.e. to write StLs whose + data is not in memory. +- Make binary loading agnostic to the endianness of the host + architecture. +- Added the sstl utility, which loads StL files and prints information + about them. +- Improve the build system. + Simplify it by doing everything in one place (the Makefile). + Add macros to control installation sub-directories. + ### Version 0.5.1 + - Fix issues when reading on stdin -- Add a warning if trailing chars detected after the solid in ascii files. +- Add a warning if trailing chars detected after the solid in ascii + files. - Add the read data type in the descriptor. -- Fix a false error log message (should have been an information message). +- Fix a false error log message (should have been an information + message). ### Version 0.5 @@ -56,7 +80,7 @@ Correction of a compilation error highlighted in particular by GCC 4.9.2 ## License -Copyright (C) 2015, 2016, 2019, 2021, 2023 |Méso|Star> (contact@meso-star.com) +Copyright (C) 2015, 2016, 2019, 2021, 2023, 2025 |Méso|Star> (contact@meso-star.com) Star-STL is free software released under GPL v3+ license: GNU GPL version 3 or later. You are welcome to redistribute it under certain diff --git a/config.mk b/config.mk @@ -1,4 +1,4 @@ -VERSION = 0.5.1 +VERSION = 0.6 PREFIX = /usr/local LIB_TYPE = SHARED @@ -7,6 +7,11 @@ LIB_TYPE = SHARED BUILD_TYPE = RELEASE #BUILD_TYPE = DEBUG +BINPREFIX = $(PREFIX)/bin +LIBPREFIX = $(PREFIX)/lib +INCPREFIX = $(PREFIX)/include +MANPREFIX = $(PREFIX)/share/man + ################################################################################ # Tools ################################################################################ @@ -24,8 +29,9 @@ PCFLAGS_STATIC = --static PCFLAGS = $(PCFLAGS_$(LIB_TYPE)) RSYS_VERSION = 0.14 -RSYS_CFLAGS = $$($(PKG_CONFIG) $(PCFLAGS) --cflags rsys) -RSYS_LIBS = $$($(PKG_CONFIG) $(PCFLAGS) --libs rsys) + +INCS = $$($(PKG_CONFIG) $(PCFLAGS) --cflags rsys) +LIBS = $$($(PKG_CONFIG) $(PCFLAGS) --libs rsys) ################################################################################ # Compilation options @@ -72,4 +78,4 @@ LDFLAGS_EXE = $(LDFLAGS) -pie OCPFLAGS_DEBUG = --localize-hidden OCPFLAGS_RELEASE = --localize-hidden --strip-unneeded -OCPFLAGS = $(OCPFLAGS_$(BUILD_TYPE)) +BOCPFLAGS = $(OCPFLAGS_$(BUILD_TYPE)) diff --git a/make.sh b/make.sh @@ -1,70 +0,0 @@ -#!/bin/sh - -# Copyright (C) 2015, 2016, 2019, 2021, 2023 |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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -set -e - -config_test() -{ - for i in "$@"; do - test=$(basename "${i}" ".c") - test_list="${test_list} ${test}" - printf "%s: src/%s.o\n" "${test}" "${test}" - done - printf "test_bin: %s\n" "${test_list}" -} - -run_test() -{ - for i in "$@"; do - test=$(basename "${i}" ".c") - - printf "%s " "${test}" - if ./"${test}" > /dev/null 2>&1; then - printf "\033[1;32mOK\033[m\n" - else - printf "\033[1;31mError\033[m\n" - fi - done 2> /dev/null -} - -clean_test() -{ - for i in "$@"; do - rm -f "$(basename "${i}" ".c")" - done -} - -install() -{ - prefix=$1 - shift 1 - - mkdir -p "${prefix}" - - for i in "$@"; do - dst="${prefix}/${i##*/}" - - if cmp -s "${i}" "${dst}"; then - printf "Up to date %s\n" "${dst}" - else - printf "Installing %s\n" "${dst}" - cp "${i}" "${prefix}" - fi - done -} - -"$@" diff --git a/src/sstl.c b/src/sstl.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2015, 2016, 2019, 2021, 2023 |Méso|Star> (contact@meso-star.com) +/* 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 @@ -16,752 +16,101 @@ #define _POSIX_C_SOURCE 200112L /* strtok_r support */ #include "sstl.h" +#include "sstl_c.h" -#include <rsys/rsys.h> #include <rsys/cstr.h> +#include <rsys/rsys.h> #include <rsys/float3.h> -#include <rsys/hash_table.h> -#include <rsys/logger.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> #include <rsys/stretchy_array.h> -#include <string.h> +#include <errno.h> #include <stdio.h> -#include <ctype.h> - -#ifdef COMPILER_CL - #pragma warning(push) - #pragma warning(disable:4706) /* Assignment within a condition */ - - #define strtok_r strtok_s -#endif - -#define OK(Expr) if((res = (Expr)) != RES_OK) goto error; else (void)0 - -enum allowed_load_steps { - TRY_READ_ASCII = 1, - TRY_READ_BINARY = 2, - TRY_READ_ALL = 3 -}; - -enum read_type { - STARTED_ASCII = BIT(0), - STARTED_BIN = BIT(1), - OK_ASCII = BIT(2), - OK_BINARY = BIT(3) -}; - -struct solid { - char* name; - unsigned* indices; - float* vertices; - float* normals; - enum read_type read_type; -}; -static const struct solid SOLID_NULL = { NULL, NULL, NULL, NULL, 0 }; - -struct streamer { - FILE* stream; - const char* name; - size_t iline; - char* buf; -}; - -struct vertex { float xyz[3]; }; - -static INLINE char -eq_vertex(const struct vertex* a, const struct vertex* b) -{ - return (char)f3_eq(a->xyz, b->xyz); -} - -/* Declare the hash table that map a vertex to its index */ -#define HTABLE_NAME vertex -#define HTABLE_DATA unsigned -#define HTABLE_KEY struct vertex -#define HTABLE_KEY_FUNCTOR_EQ eq_vertex -#include <rsys/hash_table.h> - -struct sstl { - int verbose; - struct htable_vertex vertex2id; - struct solid solid; - - struct logger* logger; - struct mem_allocator* allocator; - 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 ******************************************************************************/ -static void -streamer_init(struct streamer* streamer, FILE* stream, const char* name) -{ - ASSERT(streamer && stream && name); - memset(streamer, 0, sizeof(struct streamer)); - streamer->stream = stream; - streamer->name = name; - streamer->iline = 0; - streamer->buf = sa_add(streamer->buf, 128); - ASSERT(streamer->buf); -} - -static void -streamer_release(struct streamer* streamer) -{ - ASSERT(streamer); - sa_release(streamer->buf); -} - -/* Large enough to read "solid\0" */ -#define CHECK_SOLID_LEN 6 - -static char* -streamer_read_line - (struct streamer* streamer, - int checking_solid, - size_t* count) -{ - const size_t buf_chunk = 256; - size_t read_sz, read_count = 0; - char* line; - ASSERT(streamer && count); - - 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) { - size_t l; - - /* 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; - size_t remain_sz; - if(checking_solid) { - if(0 != strncmp("solid", line, 5)) { - l = strlen(line); - read_count += l; - *count = read_count; - /* 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)remain_sz, streamer->stream)) /* EOF */ - break; - - read_sz = sa_size(streamer->buf); - checking_solid = 0; - } - - l = strlen(streamer->buf); - read_count += l; - if(strspn(streamer->buf, " \t\r\n") != l) { /* Not empty */ - /* Remove newline character(s) */ - size_t last_char = strlen(line); - while(last_char-- && (line[last_char]=='\n' || line[last_char]=='\r')); - line[last_char + 1] = '\0'; - break; - } - } - ++streamer->iline; - *count += read_count; - return line; -} - -static void -solid_clear(struct solid* solid) -{ - ASSERT(solid); - sa_release(solid->name); - sa_release(solid->vertices); - sa_release(solid->indices); - sa_release(solid->normals); -} - -static void -clear(struct sstl* sstl) -{ - ASSERT(sstl); - solid_clear(&sstl->solid); - sstl->solid = SOLID_NULL; - htable_vertex_clear(&sstl->vertex2id); -} - -static void -print_log - (const struct sstl* sstl, const enum log_type type, const char* msg, ...) -{ - va_list vargs_list; - ASSERT(sstl && msg); - if(sstl->verbose) { - res_T res; (void)res; - va_start(vargs_list, msg); - res = logger_vprint(sstl->logger, type, msg, vargs_list); - ASSERT(res == RES_OK); - va_end(vargs_list); - } -} - -static INLINE res_T -parse_float3 - (struct sstl* sstl, - char* str, - float vert[3], - const char* filename, - const size_t iline, - char** tok_ctx) -{ - char* tk; - int i; - res_T res = RES_OK; - ASSERT(str && vert && filename); - - FOR_EACH(i, 0, 3) { - tk = strtok_r(i==0 ? str : NULL, " \t", tok_ctx); - if(!tk) { - print_log(sstl, LOG_ERROR, "%s:%lu: expecting 3D coordinates.\n", - filename, (unsigned long)iline); - return RES_BAD_ARG; - } - - res = cstr_to_float(tk, vert + i); - if(res != RES_OK) { - print_log(sstl, LOG_ERROR, "%s:%lu: invalid coordinate \"%s\".\n", - filename, (unsigned long)iline, tk); - return res; - } - } - tk = strtok_r(NULL, "\0", tok_ctx); - if(tk) { /* Unexpected remaining chars */ - print_log(sstl, LOG_WARNING, "%s:%lu: unexpected directive \"%s\".\n", - filename, (unsigned long)iline, tk); - } - return RES_OK; -} - -static INLINE res_T -parse_name_string +static res_T +file_type (struct sstl* sstl, - char* str, - char** out_name, - const char* filename, - const size_t iline, - char** tok_ctx) + FILE* fp, + const char* name, + enum sstl_type* type) { - char* name = NULL; - char* tk; + char buf[1024]; + size_t sz = 0; + long fpos = 0; res_T res = RES_OK; - ASSERT(sstl && out_name); - if(!str) goto exit; + ASSERT(sstl && fp && name && type); - /* Handle name with spaces */ - for(tk = strtok_r(str, " \t", tok_ctx); tk; tk = strtok_r(NULL, " \t", tok_ctx)) { - char* remain = NULL; - if(name) name[strlen(name)] = ' '; /* Replace '\0' by ' ' */ - remain = sa_add(name, strlen(tk) + 1/*NULL char*/); - if(!remain) { - print_log(sstl, LOG_ERROR, - "%s:%lu: not enough memory: couldn't allocate the name of the solid.\n", - filename, (unsigned long)iline); - res = RES_MEM_ERR; - goto error; - } - strcpy(remain, tk); - } - -exit: - *out_name = name; - return res; -error: - if(name) { - sa_release(name); - name = NULL; - } - goto exit; -} - -static INLINE res_T -parse_solid_name - (struct sstl* sstl, - struct solid* solid, - char* line, - const char* filename, - const size_t iline, - int allowed, - char** tok_ctx) -{ - res_T res = RES_OK; - ASSERT(sstl && solid && !solid->name); - - if(!line || strcmp(strtok_r(line, " \t", tok_ctx), "solid")) { - enum log_type lt = (allowed & TRY_READ_BINARY) ? LOG_OUTPUT : LOG_ERROR; - print_log(sstl, lt, - "%s:%lu: missing the \"solid [NAME]\" directive.\n", - filename, (unsigned long)iline); + 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; } - solid->read_type |= STARTED_ASCII; - - OK(parse_name_string(sstl, strtok_r(NULL, "\0", tok_ctx), &solid->name, - filename, iline, tok_ctx)); - -exit: - return res; -error: - if(solid->name) sa_release(solid->name); - goto exit; -} - -static INLINE res_T -parse_solid_vertex - (struct sstl* sstl, - struct solid* solid, - unsigned* const index, - char* line, - const char* filename, - const size_t iline, - char** tok_ctx) -{ - struct vertex vertex; - unsigned* found_id; - res_T res = RES_OK; - ASSERT(sstl && solid && index); - - if(!line || strcmp(strtok_r(line, " \t", tok_ctx), "vertex")) { - print_log(sstl, LOG_ERROR, - "%s:%lu: missing a \"vertex X Y Z\" directive.\n", - filename, (unsigned long)iline); - return RES_BAD_ARG; + if((fpos = ftell(fp)) < 0) { + ERROR(sstl, "%s: unable to query file position -- %s\n", + name, strerror(errno)); + res = RES_IO_ERR; + goto error; } - res = parse_float3(sstl, strtok_r(NULL, "\0", tok_ctx), vertex.xyz, filename, iline, tok_ctx); - if(res != RES_OK) return res; - - /* Look for an already registered vertex position */ - found_id = htable_vertex_find(&sstl->vertex2id, &vertex); - - if(found_id) { - *index = *found_id; + /* 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 { - /* Add a new vertex */ - *index = (unsigned)sa_size(solid->vertices) / 3; - res = htable_vertex_set(&sstl->vertex2id, &vertex, index); - if(res != RES_OK) { - print_log(sstl, LOG_ERROR, - "%s:%lu: couldn't register a vertex position.\n", - filename, (unsigned long)iline); - } - f3_set(sa_add(solid->vertices, 3), vertex.xyz); - } - sa_push(solid->indices, *index); - - return RES_OK; -} - -static INLINE res_T -parse_outer_loop - (struct sstl* sstl, - char* line, - const char* filename, - const size_t iline, - char** tok_ctx) -{ - char* tk; - ASSERT(sstl); - - if(!line - || strcmp(strtok_r(line, " \t", tok_ctx), "outer") - || !(tk = strtok_r(NULL, " \t", tok_ctx)) - || strcmp(tk, "loop")) { - print_log(sstl, LOG_ERROR, - "%s:%lu: missing the \"outer loop\" directive.\n", - filename, (unsigned long)iline); - return RES_BAD_ARG; + *type = SSTL_ASCII; } - tk = strtok_r(NULL, "\0", tok_ctx); - if(tk && strspn(tk, " \t\r\n") != strlen(tk)) { /* Invalid remaining chars */ - print_log(sstl, LOG_WARNING, - "%s:%lu: malformed \"outer loop\" directive.\n", - filename, (unsigned long)iline); - } - return RES_OK; -} - -static INLINE res_T -parse_directive - (struct sstl* sstl, - const char* directive, - char* line, - const char* filename, - const size_t iline, - char** tok_ctx) -{ - char* tk; - ASSERT(sstl && directive); - - if(!line || strcmp(strtok_r(line, " \t", tok_ctx), directive)) { - print_log(sstl, LOG_ERROR, - "%s:%lu: missing the \"%s\" directive.\n", - filename, (unsigned long)iline, directive); - return RES_BAD_ARG; - } - - tk = strtok_r(NULL, " \0", tok_ctx); - if(tk && strspn(tk, " \t\r\n") != strlen(tk)) { /* Invalid remaining chars */ - print_log(sstl, LOG_WARNING, - "%s:%lu: malformed \"%s\" directive.\n", - filename, (unsigned long)iline, directive); - } - - return RES_OK; -} - -static res_T -load_ascii_stream - (struct sstl* sstl, - FILE* stream, - const char* stream_name, - int allowed, - size_t* count) -{ - res_T res = RES_OK; - struct streamer streamer; - struct solid solid = SOLID_NULL; - char* line = NULL; - char* tok_ctx; - char* tk; - - ASSERT(sstl && stream && stream_name && count); - streamer_init(&streamer, stream, stream_name); - clear(sstl); - - line = streamer_read_line(&streamer, 1, count); - OK(parse_solid_name(sstl, &solid, line, streamer.name, streamer.iline, - allowed, &tok_ctx)); - - for(;;) { /* Parse the solid facets */ - float normal[3]; - unsigned facet[3]; - int ivertex; - - line = streamer_read_line(&streamer, 0, count); - if(!line) { - print_log(sstl, LOG_ERROR, "%s:%lu: missing directive.\n", - streamer.name, (unsigned long)streamer.iline); - res = RES_BAD_ARG; - goto error; - } - - tk = strtok_r(line, " \t", &tok_ctx); - - /* Stop on "endsolid" directive */ - if(!strcmp(tk, "endsolid")) - break; - - /* Parse the facet normal directive */ - if(strcmp(tk, "facet") - || !(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", - streamer.name, (unsigned long)streamer.iline); - res = RES_BAD_ARG; - 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, 0, count); - 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, 0, count); - OK(parse_solid_vertex(sstl, &solid, facet+ivertex, line, streamer.name, - streamer.iline, &tok_ctx)); - } - - if(!f3_is_normalized(normal)) { /* Use geometry normal */ - float v0[3], v1[3]; - /* Vertices are CCW ordered and the normal follows the right handed rule */ - f3_sub(v0, solid.vertices + facet[1]*3, solid.vertices + facet[0]*3); - f3_sub(v1, solid.vertices + facet[2]*3, solid.vertices + facet[0]*3); - f3_cross(normal, v0, v1); - f3_normalize(normal, normal); - } - f3_set(sa_add(solid.normals, 3), normal); - - line = streamer_read_line(&streamer, 0, count); - OK(parse_directive(sstl, "endloop", line, streamer.name, streamer.iline, &tok_ctx)); - - line = streamer_read_line(&streamer, 0, count); - 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; - 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)) { - print_log(sstl, LOG_WARNING, - "%s:%lu: inconsistent \"endsolid\" name.\n", - streamer.name, (unsigned long)streamer.iline); - } - sa_release(name); - } - if(sstl->verbose) { - int tmp, non_space = 0; - size_t i = 0; - while (EOF != (tmp = fgetc(stream))) { - i++; - if(!isspace(tmp)) non_space = 1; - } - if(non_space) { - print_log(sstl, LOG_WARNING, - "%s: %u unexpected trailing characters.\n", - stream_name, i); - } - } - - /* Register the solid */ - solid.read_type |= OK_ASCII; - sstl->solid = solid; - -exit: - streamer_release(&streamer); - return res; -error: - solid_clear(&solid); - 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; - } - - solid.read_type |= STARTED_BIN; - - if(1 != fread(&triangles_count, 4, 1, stream)) { - print_log(sstl, LOG_ERROR, "%s: missing triangle count.\n", stream_name); - res = RES_BAD_ARG; + 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; } - 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); - } - } - if(sstl->verbose) { - 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 */ - solid.read_type |= OK_BINARY; - sstl->solid = solid; exit: return res; error: - solid_clear(&solid); goto exit; } static res_T load_stream (struct sstl* sstl, - FILE* stream, - const char* stream_name, - int allowed) + FILE* fp, + const char* name, + enum sstl_type type) { res_T res = RES_OK; - int log = (allowed == TRY_READ_ALL); - size_t count = 0; + ASSERT((unsigned)type <= SSTL_NONE__); - ASSERT(sstl && stream && allowed); + if(!sstl || !fp || !name) { res = RES_BAD_ARG; goto error; } - /* 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 */ - if(allowed & TRY_READ_ASCII) { - if(log) { - print_log(sstl, LOG_OUTPUT, - "%s: attempt to read as ASCII file.\n", stream_name); - } - res = load_ascii_stream(sstl, stream, stream_name, allowed, &count); - if(res == RES_OK) { - if(log) print_log(sstl, LOG_OUTPUT, "Attempt successful.\n"); - goto exit; - } - } - /* If here the stream could not be read as ASCII */ - if(!(allowed & TRY_READ_BINARY)) goto exit; - if(sstl->solid.read_type & STARTED_ASCII) { - /* "solid" was found: was an ill-formed ASCII file */ - return res; + if(type == SSTL_NONE__) { + res = file_type(sstl, fp, name, &type); + if(res != RES_OK) goto error; } - if(count > 80) { - /* Don't know if can happen, but further code cannot handle this */ - print_log(sstl, LOG_ERROR, - "%s: cannot attempt to read as binary file" - " (too many bytes read while trying ascii read).\n", - stream_name); - res = RES_BAD_ARG; - goto error; - } - if(log) { - print_log(sstl, LOG_OUTPUT, - "%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_OUTPUT, "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; - struct sstl* sstl = (struct sstl*)data; - struct sstl_desc desc; - - if(!sstl || !normal) { - res = RES_BAD_ARG; + if((res = str_set(&sstl->filename, name)) != RES_OK) { + ERROR(sstl, "Error copying file name '%s' -- %s\n", + name, res_to_cstr(res)); goto error; } - OK(sstl_get_desc(sstl, &desc)); - if(idx >= desc.triangles_count) { - res = RES_BAD_ARG; - 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; } - - ASSERT(3*idx+2 < sa_size(desc.normals)); - f3_set(normal, desc.normals + 3*idx); + if(res != RES_OK) goto error; exit: return res; @@ -770,170 +119,84 @@ error: } static res_T -get_sstl_triangle_vertices - (const unsigned idx, - const void* data, - float vtx[3][3]) +load(struct sstl* sstl, const char* filename, const enum sstl_type type) { + FILE* stream = NULL; res_T res = RES_OK; - struct sstl* sstl = (struct sstl*)data; - struct sstl_desc desc; - unsigned n; - if(!sstl || !vtx) { - res = RES_BAD_ARG; - goto error; - } + ASSERT((unsigned)type <= SSTL_NONE__); - OK(sstl_get_desc(sstl, &desc)); - if(idx >= desc.triangles_count) { - res = RES_BAD_ARG; + 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; } - 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); - } + 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 -print_sstl_error_log - (const void* data, - const char* msg, - ...) +sstl_release(ref_T* ref) { - 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); + struct sstl* sstl; + ASSERT(ref); + sstl = CONTAINER_OF(ref, struct sstl, ref); + str_release(&sstl->filename); + str_release(&sstl->name); + htable_vertex_release(&sstl->vertex2id); + sa_release(sstl->vertices); + sa_release(sstl->normals); + sa_release(sstl->indices); + MEM_RM(sstl->allocator, sstl); } +/******************************************************************************* + * Local functions + ******************************************************************************/ 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) +register_vertex(struct sstl* sstl, const float v[3]) { + struct vertex vtx; + unsigned* found = NULL; + unsigned id = 0; res_T res = RES_OK; - unsigned i; + ASSERT(sstl && v); - ASSERT(data && stream && stream_name); + /* Check if the input vertex is already registered */ + f3_set(vtx.xyz, v); + found = htable_vertex_find(&sstl->vertex2id, &vtx); - 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(found) { /* The vertex already exists */ + id = *found; - 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; - } + } else { /* The vertex is a new one */ + id = (unsigned)sa_size(sstl->vertices)/3; + res = htable_vertex_set(&sstl->vertex2id, &vtx, &id); + if(res != RES_OK) 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 %.16g %.16g %.16g\n", SPLIT3(vtx[n]))); - } - OKP(fprintf(stream, " endloop\n")); - OKP(fprintf(stream, " endfacet\n")); - } - OKP(fprintf(stream, "endsolid \n")); + /* Add a new vertex */ + f3_set(sa_add(sstl->vertices, 3), vtx.xyz); } + /* Register the vertex index */ + sa_push(sstl->indices, id); + exit: return res; error: goto exit; } -#undef OKP - -static void -sstl_release(ref_T* ref) -{ - struct sstl* sstl; - ASSERT(ref); - sstl = CONTAINER_OF(ref, struct sstl, ref); - clear(sstl); - htable_vertex_release(&sstl->vertex2id); - MEM_RM(sstl->allocator, sstl); -} - /******************************************************************************* * Exported functions ******************************************************************************/ @@ -972,6 +235,8 @@ sstl_create sstl->logger = logger; sstl->verbose = verbose; htable_vertex_init(allocator, &sstl->vertex2id); + str_init(allocator, &sstl->filename); + str_init(allocator, &sstl->name); exit: if(out_sstl) *out_sstl = sstl; @@ -998,107 +263,60 @@ sstl_ref_put(struct sstl* sstl) if(!sstl) return RES_BAD_ARG; ref_put(&sstl->ref, sstl_release); return RES_OK; - } res_T -sstl_load_ascii(struct sstl* sstl, const char* filename) +sstl_load(struct sstl* sstl, const char* filename) { - if(!sstl || !filename) return RES_BAD_ARG; - return load_base(sstl, filename, TRY_READ_ASCII); + return load(sstl, filename, SSTL_NONE__); } res_T -sstl_load_binary(struct sstl* sstl, const char* filename) +sstl_load_ascii(struct sstl* sstl, const char* filename) { - if(!sstl || !filename) return RES_BAD_ARG; - return load_base(sstl, filename, TRY_READ_BINARY); + return load(sstl, filename, SSTL_ASCII); } res_T -sstl_load(struct sstl* sstl, const char* filename) +sstl_load_binary(struct sstl* sstl, const char* filename) { - if(!sstl || !filename) return RES_BAD_ARG; - return load_base(sstl, filename, TRY_READ_ALL); + return load(sstl, filename, SSTL_BINARY); } res_T -sstl_load_stream_ascii(struct sstl* sstl, FILE* stream) +sstl_load_stream(struct sstl* sstl, FILE* fp, const char* name) { - if(!sstl || !stream) return RES_BAD_ARG; - return load_stream(sstl, stream, "STREAM", TRY_READ_ASCII); + return load_stream(sstl, fp, name, SSTL_NONE__); } res_T -sstl_load_stream_binary(struct sstl* sstl, FILE* stream) +sstl_load_stream_ascii(struct sstl* sstl, FILE* fp, const char* name) { - if(!sstl || !stream) return RES_BAD_ARG; - return load_stream(sstl, stream, "STREAM", TRY_READ_BINARY); + return load_stream(sstl, fp, name, SSTL_ASCII); } res_T -sstl_load_stream(struct sstl* sstl, FILE* stream) +sstl_load_stream_binary(struct sstl* sstl, FILE* fp, const char* name) { - if(!sstl || !stream) return RES_BAD_ARG; - return load_stream(sstl, stream, "STREAM", TRY_READ_ALL); + return load_stream(sstl, fp, name, SSTL_BINARY); } res_T sstl_get_desc(struct sstl* sstl, struct sstl_desc* desc) { - if(!sstl || !desc - /* OK_ASCII xor OK_BIN */ - || (sstl->solid.read_type & OK_BINARY) == (sstl->solid.read_type & OK_ASCII)) - return RES_BAD_ARG; - desc->solid_name = sstl->solid.name; - desc->read_type = (sstl->solid.read_type & OK_ASCII) ? SSTL_ASCII : SSTL_BINARY; - desc->vertices_count = sa_size(sstl->solid.vertices); - ASSERT(desc->vertices_count % 3 == 0); - desc->vertices_count /= 3/* # coords per vertex */; - desc->triangles_count = sa_size(sstl->solid.indices); - ASSERT(desc->triangles_count % 3 == 0); - desc->triangles_count /= 3/* # indices per triange */; - desc->vertices = sstl->solid.vertices; - desc->indices = sstl->solid.indices; - desc->normals = sstl->solid.normals; - return RES_OK; -} + if(!sstl || !desc) return RES_BAD_ARG; -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; + ASSERT(sa_size(sstl->vertices) % 3 == 0); + ASSERT(sa_size(sstl->normals) % 3 == 0); + ASSERT(sa_size(sstl->indices) % 3 == 0); - 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); + desc->filename = str_cget(&sstl->filename); + desc->solid_name = str_len(&sstl->name) ? str_cget(&sstl->name) : NULL; + desc->vertices_count = sa_size(sstl->vertices) / 3/*#coords*/; + desc->triangles_count = sa_size(sstl->indices) / 3/*#ids*/; + desc->vertices = sstl->vertices; + desc->indices = sstl->indices; + desc->normals = sstl->normals; + desc->type = sstl->type; + return RES_OK; } - -#ifdef COMPILER_CL - #pragma warning(pop) -#endif - diff --git a/src/sstl.h b/src/sstl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015, 2016, 2019, 2021, 2023 |Méso|Star> (contact@meso-star.com) +/* 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 @@ -37,42 +37,56 @@ #endif /* The type of a read file */ -enum sstl_read_type { - SSTL_ASCII = 1, - SSTL_BINARY = 2 +enum sstl_type { + SSTL_ASCII, + SSTL_BINARY, + SSTL_NONE__ }; /* Descriptor of a loaded STL */ struct sstl_desc { + const char* filename; const char* solid_name; /* May be NULL <=> no name */ - enum sstl_read_type read_type; /* The type of the file */ + enum sstl_type type; /* The type of the file */ /* Front faces are CCW ordered and the normals follow the right handed rule */ - float* vertices; /* triangles_count * 3 coordinates */ - unsigned* indices; /* triangles_count * 3 indices */ - float* normals; /* Per triangle normalized normal */ + const float* vertices; /* triangles_count * 3 coordinates */ + const unsigned* indices; /* triangles_count * 3 indices */ + const float* normals; /* Per triangle normalized normal */ size_t triangles_count; size_t vertices_count; }; +#define SSTL_DESC_NULL__ {0} +static const struct sstl_desc SSTL_DESC_NULL = SSTL_DESC_NULL__; -/* 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; +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 */ + int verbose; /* verbosity level */ }; -#define SSTL_WRITE_DATA_NULL__ { NULL, NULL, NULL, NULL, NULL, 0 } -static const struct sstl_write_data SSTL_WRITE_DATA_NULL = SSTL_WRITE_DATA_NULL__; +#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; @@ -80,6 +94,7 @@ struct mem_allocator; /* Forward declaration of opaque data types */ struct sstl; +struct sstl_writer; /******************************************************************************* * Star-STL API @@ -88,7 +103,7 @@ BEGIN_DECLS SSTL_API res_T sstl_create - (struct logger* logger, /* NULL <=> use default logger*/ + (struct logger* logger, /* NULL <=> use default logger */ struct mem_allocator* allocator, /* NULL <=> use default allocator */ const int verbose, /* Verbosity level */ struct sstl** sstl); @@ -101,70 +116,72 @@ SSTL_API res_T sstl_ref_put (struct sstl* sstl); +/* 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_ascii +sstl_load (struct sstl* sstl, const char* filename); SSTL_API res_T -sstl_load_stream_ascii +sstl_load_ascii (struct sstl* sstl, - FILE* stream); + 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_binary +sstl_load_stream (struct sstl* sstl, - FILE* stream); + FILE* stream, + const char* stream_name); -/* 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 +sstl_load_stream_ascii (struct sstl* sstl, - const char* filename); + FILE* stream, + const char* stream_name); -/* 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 +sstl_load_stream_binary (struct sstl* sstl, - FILE* stream); + FILE* stream, + const char* stream_name); -/* Create a sstl_write_data from a sstl. - * The result is valid as long as the input sstl is valid */ +/* 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_get_desc + (struct sstl* sstl, + struct sstl_desc* desc); +/******************************************************************************* + * Writer API + ******************************************************************************/ SSTL_API res_T -sstl_write - (const struct sstl_write_data* data, - const int binary, - const char* filename); +sstl_writer_create + (const struct sstl_writer_create_args* args, + struct sstl_writer** writer); SSTL_API res_T -sstl_write_stream - (const struct sstl_write_data* data, - const int binary, - FILE* stream); +sstl_writer_ref_get + (struct sstl_writer* writer); -/* The returned descriptor is valid until a new load process */ SSTL_API res_T -sstl_get_desc - (struct sstl* sstl, - struct sstl_desc* desc); +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_ascii.c b/src/sstl_ascii.c @@ -0,0 +1,298 @@ +/* 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* strtok_r support */ + +#include "sstl_c.h" + +#include <rsys/cstr.h> +#include <rsys/stretchy_array.h> +#include <rsys/text_reader.h> + +#include <string.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static res_T +parse_float3 + (struct sstl* sstl, + const struct txtrdr* txtrdr, + char* str, + char** ctx, + float vec[3]) +{ + char* x = NULL; + char* y = NULL; + char* z = NULL; + res_T res = RES_OK; + ASSERT(sstl && txtrdr && vec); + + if(!(x = strtok_r(str, " \t", ctx)) + || !(y = strtok_r(NULL, " \t", ctx)) + || !(z = strtok_r(NULL, " \t", ctx))) { + res = RES_BAD_ARG; + goto error; + } + + if((res = cstr_to_float(x, vec+0)) != RES_OK) goto error; + if((res = cstr_to_float(y, vec+1)) != RES_OK) goto error; + if((res = cstr_to_float(z, vec+2)) != RES_OK) goto error; + +exit: + return res; +error: + ERROR(sstl, "%s:%lu: invalid vector component\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr)); + goto exit; +} + +static res_T +parse_word + (struct sstl* sstl, + const struct txtrdr* txtrdr, + const char* word, + char* str, + char** ctx) +{ + char* tk = NULL; + ASSERT(sstl && txtrdr && ctx); + + tk = strtok_r(str, " \t", ctx); + if(!tk || strcmp(tk, word) != 0) { + ERROR(sstl, "%s:%lu: expect the \"%s\" keyword\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + word); + return RES_BAD_ARG; + } + + return RES_OK; +} + +static INLINE res_T +parse_nothing + (struct sstl* sstl, + const struct txtrdr* txtrdr, + char* str, + char** ctx) +{ + char* tk = NULL; + + ASSERT(txtrdr && ctx); + (void)sstl; /* Avoid "unused variable" warning */ + + tk = strtok_r(str, " \t", ctx); + if(tk != NULL) { + WARN(sstl, "%s:%lu: unexpected text \"%s\"\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk); + } + return RES_OK; +} + +static res_T +parse_vec3 + (struct sstl* sstl, + struct txtrdr* txtrdr, + const char* name, + char* str, + char** ctx, + float vec[3]) +{ + res_T res = RES_BAD_ARG; + ASSERT(sstl && txtrdr && name && ctx); + + if((res = parse_word(sstl, txtrdr, name, str, ctx)) != RES_OK) goto error; + if((res = parse_float3(sstl, txtrdr, NULL, ctx, vec)) != RES_OK) goto error; + if((res = parse_nothing(sstl, txtrdr, NULL, ctx)) != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_facet(struct sstl* sstl, struct txtrdr* txtrdr, char* normal) +{ + float v[3][3] = {0}; + float* N = NULL; + char* line = NULL; + char* ctx = NULL; + int i = 0; + res_T res = RES_OK; + ASSERT(sstl && txtrdr && normal); + + N = sa_add(sstl->normals, 3); + if((res = parse_vec3(sstl, txtrdr, "normal", normal, &ctx, N)) != RES_OK) goto error; + + #define READ_LINE { \ + if((res = txtrdr_read_line(txtrdr)) != RES_OK) { \ + ERROR(sstl, "%s: error reading line -- %s\n", \ + txtrdr_get_name(txtrdr), res_to_cstr(res)); \ + goto error; \ + } \ + if(!(line = txtrdr_get_line(txtrdr))) { \ + ERROR(sstl, "%s: unexpected end of file\n", txtrdr_get_name(txtrdr)); \ + res = RES_BAD_ARG; \ + goto error; \ + } \ + } (void)0 + + READ_LINE; + if((res = parse_word(sstl, txtrdr, "outer", line, &ctx)) != RES_OK) goto error; + if((res = parse_word(sstl, txtrdr, "loop", NULL, &ctx)) != RES_OK) goto error; + if((res = parse_nothing(sstl, txtrdr, NULL, &ctx)) != RES_OK) goto error; + + FOR_EACH(i, 0, 3) { + READ_LINE; + res = parse_vec3(sstl, txtrdr, "vertex", line, &ctx, v[i]); + if(res != RES_OK) goto error; + + res = register_vertex(sstl, v[i]); + if(res != RES_OK) { + ERROR(sstl, "%s:%lu: vertex registration error -- %s\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + res_to_cstr(res)); + goto error; + } + } + + READ_LINE; + if((res = parse_word(sstl, txtrdr, "endloop", line, &ctx)) != RES_OK) goto error; + if((res = parse_nothing(sstl, txtrdr, NULL, &ctx)) != RES_OK) goto error; + + READ_LINE; + if((res = parse_word(sstl, txtrdr, "endfacet", line, &ctx)) != RES_OK) goto error; + if((res = parse_nothing(sstl, txtrdr, NULL, &ctx)) != RES_OK) goto error; + + #undef READ_LINE + + /* If necessary, automatically calculate the surface normal. */ + if(!f3_is_normalized(N)) calculate_normal(N, v[0], v[1], v[2]); + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_solid(struct sstl* sstl, struct txtrdr* txtrdr) +{ + char* line = NULL; + char* tk = NULL; + char* tk_ctx = NULL; + res_T res = RES_OK; + ASSERT(sstl && txtrdr); + + line = txtrdr_get_line(txtrdr); + ASSERT(line != NULL); + + tk = strtok_r(line, " \t", &tk_ctx); + ASSERT(tk); + if(strcmp(tk, "solid")) { + ERROR(sstl, "%s:%lu: the \"solid [name]\" directive is missing\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr)); + res = RES_BAD_ARG; + goto error; + } + + tk = strtok_r(NULL, "", &tk_ctx); + if(tk != NULL && (res = str_set(&sstl->name, tk)) != RES_OK) { + ERROR(sstl, "%s:%lu: error duplicating solid name -- %s\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + res_to_cstr(res)); + goto error; + } + + for(;;) { + if((res = txtrdr_read_line(txtrdr)) != RES_OK) { + ERROR(sstl, "%s: error reading line -- %s\n", + str_cget(&sstl->name), res_to_cstr(res)); + goto error; + } + + if((line = txtrdr_get_line(txtrdr)) == NULL) { + ERROR(sstl, "%s: the \"endsolid [name]\" directive is missing\n", + txtrdr_get_name(txtrdr)); + res = RES_BAD_ARG; + goto error; + } + + tk = strtok_r(line, " \t", &tk_ctx); + + if(!strcmp(tk, "facet")) { + res = parse_facet(sstl, txtrdr, strtok_r(NULL, "", &tk_ctx)); + if(res != RES_OK) goto error; + + } else if(!strcmp(tk, "endsolid")) { + break; /* Stop on "endsolid" directive */ + + } else { + ERROR(sstl, "%s:%lu: invalid directive \"%s\"\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk); + res = RES_BAD_ARG; + goto error; + } + } + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +load_stream_ascii + (struct sstl* sstl, + FILE* stream, + const char* stream_name) +{ + struct txtrdr* txtrdr = NULL; + res_T res = RES_OK; + + ASSERT(sstl && stream && stream_name); + + clear(sstl); + + res = txtrdr_stream(sstl->allocator, stream, stream_name, '#', &txtrdr); + if(res != RES_OK) { + ERROR(sstl, "%s: error creating text reader -- %s\n", + stream_name, res_to_cstr(res)); + goto error; + } + + if((res = txtrdr_read_line(txtrdr)) != RES_OK) { + ERROR(sstl, "%s: error reading line -- %s\n", stream_name, res_to_cstr(res)); + goto error; + } + + if(txtrdr_get_cline(txtrdr) != NULL) { /* File is not empty */ + if((res = parse_solid(sstl, txtrdr)) != RES_OK) goto error; + } + + sstl->type = SSTL_ASCII; + +exit: + htable_vertex_purge(&sstl->vertex2id); /* Purge the helper structure */ + if(txtrdr) txtrdr_ref_put(txtrdr); + return res; +error: + clear(sstl); + goto exit; +} diff --git a/src/sstl_binary.c b/src/sstl_binary.c @@ -0,0 +1,146 @@ +/* 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_c.h" + +#include <rsys/cstr.h> +#include <rsys/stretchy_array.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE res_T +parse_header(struct sstl* sstl, FILE* fp, const char* name) +{ + char header[80]; + ASSERT(sstl && fp && fp); + + if(fread(header, sizeof(header), 1, fp) != 1) { + ERROR(sstl, "%s: invalid header\n", name); + return RES_BAD_ARG; + } + return RES_OK; +} + +static INLINE res_T +parse_triangle_count + (struct sstl* sstl, + FILE* fp, + const char* name, + uint32_t* ntri) +{ + uint8_t bytes[4]; /* Little endian */ + ASSERT(sstl && fp && name && ntri); + + if(fread(bytes, sizeof(bytes), 1, fp) != 1) { + ERROR(sstl, "%s: invalid triangle count\n", name); + return RES_BAD_ARG; + } + + /* Ensure encoding in host byte order */ + *ntri = + (uint32_t)(bytes[0]<<0) + | (uint32_t)(bytes[1]<<8) + | (uint32_t)(bytes[2]<<16) + | (uint32_t)(bytes[3]<<24); + return RES_OK; +} + +static INLINE res_T +parse_triangle + (struct sstl* sstl, + FILE* fp, + const char* name, + const uint32_t itri) /* Triangle identifier */ +{ + uint8_t bytes[4/*#bytes*/*12/*#vectors*/+2/*attribute*/]; + union { uint32_t ui32; float f; } ucast; + float* N = NULL; /* Normal */ + float v[3][3] = {0}; /* Vertices */ + int i = 0; + res_T res = RES_OK; + ASSERT(sstl && fp && name); + + if(fread(bytes, sizeof(bytes), 1, fp) != 1) { + ERROR(sstl, "%s: invalid triangle %i\n", name, itri); + res = RES_BAD_ARG; + goto error; + } + + #define CAST(Bytes) \ + ((ucast.ui32 = \ + (uint32_t)((Bytes)[0]<<0) \ + | (uint32_t)((Bytes)[1]<<8) \ + | (uint32_t)((Bytes)[2]<<16) \ + | (uint32_t)((Bytes)[3]<<24)), \ + ucast.f) + + N = sa_add(sstl->normals, 3); + N[0] = CAST(bytes+0); + N[1] = CAST(bytes+4); + N[2] = CAST(bytes+8); + + FOR_EACH(i, 0, 3) { + v[i][0] = CAST(bytes+(i+1)*12+0); + v[i][1] = CAST(bytes+(i+1)*12+4); + v[i][2] = CAST(bytes+(i+1)*12+8); + + res = register_vertex(sstl, v[i]); + if(res != RES_OK) { + ERROR(sstl, "%s: triangle %i: vertex registration error -- %s\n", + name, itri, res_to_cstr(res)); + res = RES_BAD_ARG; + goto error; + } + } + + /* If necessary, automatically calculate the surface normal. */ + if(!f3_is_normalized(N)) calculate_normal(N, v[0], v[1], v[2]); + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +load_stream_binary(struct sstl* sstl, FILE* fp, const char* name) +{ + uint32_t ntris = 0; + uint32_t i = 0; + res_T res = RES_OK; + ASSERT(sstl && fp && name); + + clear(sstl); + + if((res = parse_header(sstl, fp, name)) != RES_OK) goto error; + if((res = parse_triangle_count(sstl, fp, name, &ntris)) != RES_OK) goto error; + + FOR_EACH(i, 0, ntris) { + if((res = parse_triangle(sstl, fp, name, i)) != RES_OK) goto error; + } + + sstl->type = SSTL_BINARY; + +exit: + htable_vertex_purge(&sstl->vertex2id); /* Purge the helper structure */ + return res; +error: + clear(sstl); + goto exit; +} diff --git a/src/sstl_c.h b/src/sstl_c.h @@ -0,0 +1,143 @@ +/* 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/>. */ + +#ifndef SSTL_C_H +#define SSTL_C_H + +#include "sstl.h" + +#include <rsys/float3.h> +#include <rsys/hash_table.h> +#include <rsys/logger.h> +#include <rsys/ref_count.h> +#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)) \ + logger_print((Dev)->logger, Type, __VA_ARGS__); \ +} (void)0 +#define ERROR(Dev, ...) LOG__(Dev, 1, LOG_ERROR, "error: "__VA_ARGS__) +#define WARN(Dev, ...) LOG__(Dev, 2, LOG_WARNING, "warning: "__VA_ARGS__) +#define INFO(Dev, ...) LOG__(Dev, 3, LOG_OUTPUT, __VA_ARGS__) + +struct vertex { float xyz[3]; }; + +static INLINE char +eq_vertex(const struct vertex* a, const struct vertex* b) +{ + return (char) + ( a->xyz[0] == b->xyz[0] + && a->xyz[1] == b->xyz[1] + && a->xyz[2] == b->xyz[2]); +} + +/* Declare the hash table that map a vertex to its index */ +#define HTABLE_NAME vertex +#define HTABLE_DATA unsigned +#define HTABLE_KEY struct vertex +#define HTABLE_KEY_FUNCTOR_EQ eq_vertex +#include <rsys/hash_table.h> + +/* Forward declarations */ +struct logger; +struct mem_allocator; + +struct sstl { + struct str filename; + struct str name; + enum sstl_type type; + + /* Temporary structure used to map a vertex to its id */ + struct htable_vertex vertex2id; + + float* normals; + float* vertices; + unsigned* indices; + + struct logger* logger; + struct mem_allocator* allocator; + int verbose; + ref_T ref; +}; + +static INLINE void +clear(struct sstl* sstl) +{ + ASSERT(sstl); + str_clear(&sstl->name); + sa_release(sstl->indices); + sa_release(sstl->vertices); + sa_release(sstl->normals); + sstl->indices = NULL; + sstl->vertices = NULL; + sstl->normals = NULL; + sstl->type = SSTL_NONE__; + 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], + const float v0[3], + const float v1[3], + const float v2[3]) +{ + float E0[3], E1[3]; + ASSERT(N && v0 && v1 && v2); + + /* Vertices are CCW and the normal follows the right handed rule */ + f3_sub(E0, v1, v0); + f3_sub(E1, v2, v0); + f3_cross(N, E0, E1); + f3_normalize(N, N); + + return N; +} + +extern LOCAL_SYM res_T +load_stream_ascii + (struct sstl* sstl, + FILE* stream, + const char* stream_name); + +extern LOCAL_SYM res_T +load_stream_binary + (struct sstl* sstl, + FILE* stream, + const char* stream_name); + +extern LOCAL_SYM res_T +register_vertex + (struct sstl* sstl, + const float v[3]); + +#endif /* SSTL_C_H */ diff --git a/src/sstl_main.c b/src/sstl_main.c @@ -0,0 +1,213 @@ +/* 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* getopt support */ + +#include "sstl.h" + +#include <rsys/mem_allocator.h> +#include <unistd.h> /* getopt */ + +struct args { + /* List of input meshes. + * Empty list means that a mesh is read from standard */ + char* const* meshes; + unsigned nmeshes; + + enum sstl_type type; /* Type of input meshes */ + int verbose; /* Verbosity level */ + int quit; +}; +static const struct args ARGS_DEFAULT = {NULL, 0, SSTL_NONE__, 0, 0}; + +struct cmd { + struct args args; + struct sstl* sstl; +}; +static const struct cmd CMD_NULL = {0}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE void +usage(FILE* stream) +{ + fprintf(stream, "usage: sstl [-abhv] [file ...]\n"); +} + +static res_T +args_init(struct args* args, int argc, char** argv) +{ + int opt = 0; + res_T res = RES_OK; + + *args = ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "abhv")) != -1) { + switch(opt) { + case 'a': args->type = SSTL_ASCII; break; + case 'b': args->type = SSTL_BINARY; break; + case 'h': + usage(stdout); + args->quit = 1; + goto exit; + case 'v': args->verbose += (args->verbose < 3); break; + default: res = RES_BAD_ARG; break; + } + if(res != RES_OK) goto error; + } + + /* Setup the list of meshes */ + args->meshes = argv + optind; + args->nmeshes = (unsigned)(argc - optind); + + if(!args->nmeshes && args->type == SSTL_NONE__) { + fprintf(stderr, + "StL type must be defined for reading on stdin " + "-- options '-a' or -b'\n"); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + usage(stderr); + goto exit; +} + +static INLINE void +cmd_release(struct cmd* cmd) +{ + if(cmd->sstl) SSTL(ref_put(cmd->sstl)); +} + +static INLINE res_T +cmd_init(struct cmd* cmd, const struct args* args) +{ + res_T res = RES_OK; + ASSERT(cmd && args); + + cmd->args = *args; + + res = sstl_create(NULL, NULL, args->verbose, &cmd->sstl); + if(res != RES_OK) goto error; + +exit: + return res; +error: + cmd_release(cmd); + goto exit; +} + +static INLINE const char* +type_to_cstr(const enum sstl_type type) +{ + const char* cstr = NULL; + switch(type) { + case SSTL_ASCII: cstr = "ascii"; break; + case SSTL_BINARY: cstr = "binary"; break; + default: FATAL("Unreachable code"); break; + } + return cstr; +} + +static INLINE void +print_info(const struct cmd* cmd) +{ + struct sstl_desc desc = SSTL_DESC_NULL; + ASSERT(cmd); + + SSTL(get_desc(cmd->sstl, &desc)); + printf("%s\t%s\t%lu\t%lu\t%s\n", + type_to_cstr(desc.type), + desc.solid_name ? desc.solid_name : "null", + (unsigned long)desc.triangles_count, + (unsigned long)desc.vertices_count, + desc.filename); +} + +static INLINE res_T +cmd_run(const struct cmd* cmd) +{ + res_T res = RES_OK; + ASSERT(cmd); + + /* Read from the standard input */ + if(!cmd->args.nmeshes) { + switch(cmd->args.type) { + case SSTL_ASCII: + res = sstl_load_stream_ascii(cmd->sstl, stdin, "stdin (ASCII)"); + break; + case SSTL_BINARY: + res = sstl_load_stream_binary(cmd->sstl, stdin, "stdin (binary)"); + break; + default: FATAL("Unreachable code"); break; + } + if(res != RES_OK) goto error; + print_info(cmd); + + /* Load files */ + } else { + unsigned i; + FOR_EACH(i, 0, cmd->args.nmeshes) { + switch(cmd->args.type) { + case SSTL_ASCII: + res = sstl_load_ascii(cmd->sstl, cmd->args.meshes[i]); + break; + case SSTL_BINARY: + res = sstl_load_binary(cmd->sstl, cmd->args.meshes[i]); + break; + case SSTL_NONE__: + res = sstl_load(cmd->sstl, cmd->args.meshes[i]); + break; + default: FATAL("Unreachable code"); break; + } + if(res != RES_OK) goto error; + print_info(cmd); + } + } + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * The program + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct args args = ARGS_DEFAULT; + struct cmd cmd = CMD_NULL; + int err = 0; + res_T res = RES_OK; + + if((res = args_init(&args, argc, argv)) != RES_OK) goto error; + if(args.quit) goto exit; + + if((res = cmd_init(&cmd, &args)) != RES_OK) goto error; + if((res = cmd_run(&cmd)) != RES_OK) goto error; + +exit: + cmd_release(&cmd); + CHK(mem_allocated_size() == 0); + return err; +error: + err = 1; + goto exit; +} 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}; + uint32_t ntris = 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 { + ntris = (uint32_t)args->triangles_count; + } + + res = write_ntriangles(writer, ntris); + 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) { + WARN(writer, "%s: triangles are missing\n", str_cget(&writer->filename)); + } + + 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) CHK(finalize(writer) == RES_OK); + 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; +} diff --git a/src/test_sstl.c b/src/test_sstl.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2015, 2016, 2019, 2021, 2023 |Méso|Star> (contact@meso-star.com) +/* 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 @@ -14,11 +14,21 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "sstl.h" -#include "test_sstl_utils.h" #include <rsys/logger.h> static void +check_memory_allocator(struct mem_allocator* allocator) +{ + if(MEM_ALLOCATED_SIZE(allocator)) { + char dump[512]; + MEM_DUMP(allocator, dump, sizeof(dump)/sizeof(char)); + fprintf(stderr, "%s\n", dump); + FATAL("Memory leaks\n"); + } +} + +static void log_stream(const char* msg, void* ctx) { ASSERT(msg); diff --git a/src/test_sstl_load.c b/src/test_sstl_load.c @@ -1,582 +0,0 @@ -/* Copyright (C) 2015, 2016, 2019, 2021, 2023 |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 "test_sstl_utils.h" - -#include <rsys/clock_time.h> -#include <rsys/float3.h> -#include <rsys/logger.h> -#include <rsys/rsys.h> - -static void -test_basic(struct sstl* sstl) -{ - static const char* test0 = - "solid\n" - " facet normal 0.0 -1.0 0.0\n" - " outer loop\n" - " vertex 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - "endsolid"; - static const char* test1 = - "solid my_solid\n" - "\n" - " facet normal 0.0 -1.0 0.0\n" - " outer loop hophophophophop\n" - " vertex\t 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0 \taaa\n" - " vertex 0.0 0.0 1.0\n" - " endloop \n" - " endfacet \t\t\t noise\n" - "endsolid pouet\n"; - static const char* test2 = - "solid my_solid\n" - "endsolid my_solid\n"; - static const char* test3 = - "solid\n" - " facet normal 0.0 0.0 0.0\n" - " outer loop\n" - " vertex 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - "endsolid"; - static const char* bad[] = { - "solid\n" - " facet normal 0.0 -1.0 0.0\n" - " outer loop\n" - " vertex 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - , - " facet normal 0.0 -1.0 0.0\n" - " outer loop\n" - " vertex 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - "endsolid\n" - , - "solid\n" - " facet 0.0 -1.0 0.0\n" - " outer loop\n" - " vertex 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - "endsolid\n" - , - "solid\n" - " normal 0.0 -1.0 0.0\n" - " outer loop\n" - " vertex 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - "endsolid\n" - , - "solid\n" - " facet normal 0.0 -1.0 0.0\n" - " outer loop\n" - " vertex 0.0 0.0 a.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - "endsolid\n" - , - "solid\n" - " facet normal 0.0 -1.0 0.0\n" - " outer loop\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endloop\n" - " endfacet\n" - "endsolid\n" - , - "solid\n" - " facet normal 0.0 -1.0 0.0\n" - " vertex 0.0 0.0 0.0\n" - " vertex 1.0 0.0 0.0\n" - " vertex 0.0 0.0 1.0\n" - " endfacet\n" - "endsolid\n" - }; - const size_t nbads = sizeof(bad)/sizeof(const char*); - float tmp[3]; - struct sstl_desc desc; - FILE* file; - size_t i; - struct sstl_write_data wd; - - CHK(sstl != NULL); - - file = fopen("test_basic.stl", "w"); - CHK(file != NULL); - 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); - CHK(sstl_load(sstl, "none.stl") == RES_IO_ERR); - CHK(sstl_load(sstl, "test_basic.stl") == RES_OK); - - CHK(sstl_get_desc(NULL, NULL) == RES_BAD_ARG); - CHK(sstl_get_desc(sstl, NULL) == RES_BAD_ARG); - CHK(sstl_get_desc(NULL, &desc) == RES_BAD_ARG); - 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 = 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_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(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 = tmpfile(); - fwrite(test2, sizeof(char), strlen(test2), file); - rewind(file); - CHK(sstl_load_stream(sstl, file) == RES_OK); - fclose(file); - - CHK(sstl_get_desc(sstl, &desc) == RES_OK); - CHK(strcmp(desc.solid_name, "my_solid") == 0); - CHK(desc.vertices_count == 0); - CHK(desc.triangles_count == 0); - - file = tmpfile(); - fwrite(test3, sizeof(char), strlen(test3), file); - rewind(file); - CHK(sstl_load_stream_binary(sstl, file) == RES_BAD_ARG); - fclose(file); - - FOR_EACH(i, 0, nbads) { - file = tmpfile(); - fwrite(bad[i], sizeof(char), strlen(bad[i]), file); - rewind(file); - 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 -test_tetrahedron(struct sstl* sstl) -{ - static const char* tetrahedron[] = { - "solid cube_corner\n", - " facet normal 0.0 -1.0 0.0\n", - " outer loop\n", - " vertex 0.0 0.0 0.0\n", - " vertex 1.0 0.0 0.0\n", - " vertex 0.0 0.0 1.0\n", - " endloop\n", - " endfacet\n", - " facet normal 0.0 0.0 -1.0\n", - " outer loop\n", - " vertex 0.0 0.0 0.0\n", - " vertex 0.0 1.0 0.0\n", - " vertex 1.0 0.0 0.0\n", - " endloop\n", - " endfacet\n", - " facet normal -1.0 0.0 0.0\n", - " outer loop\n", - " vertex 0.0 0.0 0.0\n", - " vertex 0.0 0.0 1.0\n", - " vertex 0.0 1.0 0.0\n", - " endloop\n", - " endfacet\n", - " facet normal 0.577 0.577 0.577\n", - " outer loop\n", - " vertex 1.0 0.0 0.0\n", - " vertex 0.0 1.0 0.0\n", - " vertex 0.0 0.0 1.0\n", - " endloop\n", - " endfacet\n", - "endsolid\n" - }; - FILE* file; - const size_t nlines = sizeof(tetrahedron)/sizeof(const char*); - struct sstl_desc desc; - float tmp[3]; - size_t i; - struct sstl_write_data wd; - - CHK(sstl != NULL); - - file = tmpfile(); - CHK(file != NULL); - FOR_EACH(i, 0, nlines) - fwrite(tetrahedron[i], sizeof(char), strlen(tetrahedron[i]), file); - rewind(file); - - CHK(sstl_load_stream(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); - - 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 -main(int argc, char** argv) -{ - struct mem_allocator allocator; - struct sstl* sstl; - int i; - (void)argc, (void)argv; - - mem_init_proxy_allocator(&allocator, &mem_default_allocator); - - CHK(sstl_create(NULL, &allocator, 1, &sstl) == RES_OK); - - test_basic(sstl); - test_tetrahedron(sstl); - - FOR_EACH(i, 1, argc) { - struct sstl_desc desc; - char buf[512]; - struct time t0, t1; - - printf("loading %s", argv[i]); - fflush(stdout); - - time_current(&t0); - CHK(sstl_load(sstl, argv[i]) == RES_OK); - time_current(&t1); - time_sub(&t0, &t1, &t0); - time_dump(&t0, TIME_MIN|TIME_SEC|TIME_MSEC, NULL, buf, sizeof(buf)); - - CHK(sstl_get_desc(sstl, &desc) == RES_OK); - - printf(" - #vertices = %lu; #triangles = %lu - %s\n", - (unsigned long)desc.vertices_count, - (unsigned long)desc.triangles_count, - buf); - } - - CHK(sstl_ref_put(sstl) == RES_OK); - - check_memory_allocator(&allocator); - mem_shutdown_proxy_allocator(&allocator); - CHK(mem_allocated_size() == 0); - return 0; -} - diff --git a/src/test_sstl_load_ascii.c b/src/test_sstl_load_ascii.c @@ -0,0 +1,483 @@ +/* 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* fork */ + +#include "sstl.h" + +#include <rsys/float3.h> +#include <rsys/mem_allocator.h> + +#include <string.h> +#include <unistd.h> /* fork, pipe */ + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +check_api(struct sstl* sstl) +{ + const char* filename = "test.stl"; + FILE* fp = NULL; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK((fp = fopen(filename, "w+")) != NULL); + rewind(fp); + + #define CHECK_EMPTY_FILE { \ + CHK(sstl_get_desc(sstl, &desc) == RES_OK); \ + CHK(!strcmp(desc.filename, filename)); \ + CHK(desc.type == SSTL_ASCII); \ + CHK(desc.solid_name == NULL); \ + CHK(desc.vertices_count == 0); \ + CHK(desc.triangles_count == 0); \ + } (void)0 + + CHK(sstl_load(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_load(NULL, filename) == RES_BAD_ARG); + CHK(sstl_load(sstl, "none.stl") == RES_IO_ERR); + CHK(sstl_load(sstl, filename) == RES_OK); /* Empty file should be OK */ + + CHK(sstl_get_desc(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_get_desc(NULL, &desc) == RES_BAD_ARG); + CHECK_EMPTY_FILE; + + CHK(sstl_load_ascii(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_load_ascii(NULL, filename) == RES_BAD_ARG); + CHK(sstl_load_ascii(sstl, "none.stl") == RES_IO_ERR); + CHK(sstl_load_ascii(sstl, filename) == RES_OK); /* Empty file should be OK */ + CHECK_EMPTY_FILE; + + CHK(sstl_load_stream(NULL, fp, filename) == RES_BAD_ARG); + CHK(sstl_load_stream(sstl, NULL, filename) == RES_BAD_ARG); + CHK(sstl_load_stream(sstl, fp, NULL) == RES_BAD_ARG); + CHK(sstl_load_stream(sstl, fp, filename) == RES_OK); + CHECK_EMPTY_FILE; + + CHK(sstl_load_stream_ascii(NULL, fp, filename) == RES_BAD_ARG); + CHK(sstl_load_stream_ascii(sstl, NULL, filename) == RES_BAD_ARG); + CHK(sstl_load_stream_ascii(sstl, fp, NULL) == RES_BAD_ARG); + CHK(sstl_load_stream_ascii(sstl, fp, filename) == RES_OK); + CHECK_EMPTY_FILE; + + #undef CHECK_EMPTY_FILE + + CHK(fclose(fp) == 0); +} + +static void +check_no_triangle(struct sstl* sstl) +{ + static const char* stl = + "solid my_solid\n" + "endsolid my_solid\n"; + const char* filename = "empty.stl"; + FILE* fp = NULL; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK(sstl); + + CHK((fp = fopen(filename, "w+")) != NULL); + CHK(fwrite(stl, sizeof(char), strlen(stl), fp) == strlen(stl)); + rewind(fp); + + CHK(sstl_load_stream(sstl, fp, filename) == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_ASCII); + CHK(!strcmp(desc.filename, filename)); + CHK(!strcmp(desc.solid_name, "my_solid")); + CHK(desc.vertices_count == 0); + CHK(desc.triangles_count == 0); + + CHK(fclose(fp) == 0); +} + +static void +check_1_triangle(struct sstl* sstl) +{ + static const char* stl = + "solid\n" + " facet normal 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n" + "endsolid"; + const char* filename = "1_triangle.stl"; + FILE* fp = NULL; + float v[3] = {0,0,0}; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK(sstl); + + CHK((fp = fopen(filename, "w")) != NULL); + CHK(fwrite(stl, sizeof(char), strlen(stl), fp) == strlen(stl)); + CHK(fclose(fp) == 0); + + CHK(sstl_load_ascii(sstl, filename) == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + + CHK(desc.type == SSTL_ASCII); + CHK(!strcmp(desc.filename, filename)); + 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(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); +} + +static void +check_1_triangle_with_noise(struct sstl* sstl) +{ + static const char* stl = + "solid My Solid\n" + "\n" + " facet normal 0.0 -1.0 0.0\n" + " outer loop hophophophophop\n" + " vertex\t 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0 \taaa\n" + " vertex 0.0 0.0 1.0\n" + " endloop \n" + " endfacet \t\t\t noise\n" + "endsolid pouet\n"; + const char* filename = "1_triangle_with_noise.stl"; + FILE* fp = NULL; + float v[3] = {0,0,0}; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK(sstl); + + CHK((fp = fopen(filename, "w")) != NULL); + CHK(fwrite(stl, sizeof(char), strlen(stl), fp) == strlen(stl)); + CHK(fclose(fp) == 0); + + CHK(sstl_load(sstl, filename) == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_ASCII); + CHK(!strcmp(desc.filename, filename)); + CHK(!strcmp(desc.solid_name, "My Solid")); + 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(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); +} + +static void +check_1_triangle_no_normal(struct sstl* sstl) +{ + static const char* stl = + "solid\n" + " facet normal 0.0 0.0 0.0\n" + " outer loop\n" + " vertex 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n" + "endsolid"; + const char* filename = "1_triangle_no_normal.stl"; + FILE* fp = NULL; + float v[3] = {0,0,0}; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK((fp = fopen(filename, "w+")) != NULL); + CHK(fwrite(stl, sizeof(char), strlen(stl), fp) == strlen(stl)); + rewind(fp); + + CHK(sstl_load_stream(sstl, fp, filename) == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_ASCII); + CHK(!strcmp(desc.filename, filename)); + 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(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 1.f)) == 1); + + /* Normal is automatically calculated */ + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); + + CHK(fclose(fp) == 0); +} + +static void +check_invalid_file(struct sstl* sstl) +{ + static const char* bad[] = { + /* "endsolid" is missing */ + "solid\n" + " facet normal 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n", + + /* "solid" is missing */ + " facet normal 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n" + "endsolid\n", + + /* "normal" is missing */ + "solid\n" + " facet 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n" + "endsolid\n", + + /* "facet" is missing */ + "solid\n" + " normal 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n" + "endsolid\n", + + /* invalid vertex coordinate */ + "solid\n" + " facet normal 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 0.0 0.0 a.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n" + "endsolid\n", + + /* Not enough vertices */ + "solid\n" + " facet normal 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endloop\n" + " endfacet\n" + "endsolid\n", + + /* "outer loop" is missing */ + "solid\n" + " facet normal 0.0 -1.0 0.0\n" + " vertex 0.0 0.0 0.0\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " endfacet\n" + "endsolid\n" + }; + const size_t nbads = sizeof(bad)/sizeof(const char*); + FILE* fp = NULL; + size_t i; + + CHK(sstl != NULL); + + FOR_EACH(i, 0, nbads) { + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(bad[i], sizeof(char), strlen(bad[i]), fp) == strlen(bad[i])); + rewind(fp); + CHK(sstl_load_stream(sstl, fp, "invalid StL") == RES_BAD_ARG); + CHK(fclose(fp) == 0); + } +} + +static void +check_tetrahedron(struct sstl* sstl) +{ + static const char* tetrahedron[] = { + "solid cube corner\n", + " facet normal 0.0 -1.0 0.0\n", + " outer loop\n", + " vertex 0.0 0.0 0.0\n", + " vertex 0.1 0.0 0.0\n", + " vertex 0.0 0.0 0.1\n", + " endloop\n", + " endfacet\n", + " facet normal 0.0 0.0 -1.0\n", + " outer loop\n", + " vertex 0.0 0.0 0.0\n", + " vertex 0.0 0.1 0.0\n", + " vertex 0.1 0.0 0.0\n", + " endloop\n", + " endfacet\n", + " facet normal -1.0 0.0 0.0\n", + " outer loop\n", + " vertex 0.0 0.0 0.0\n", + " vertex 0.0 0.0 0.1\n", + " vertex 0.0 0.1 0.0\n", + " endloop\n", + " endfacet\n", + " facet normal 0.577 0.577 0.577\n", + " outer loop\n", + " vertex 0.1 0.0 0.0\n", + " vertex 0.0 0.1 0.0\n", + " vertex 0.0 0.0 0.1\n", + " endloop\n", + " endfacet\n", + "endsolid\n" + }; + FILE* fp = NULL; + const char* filename = "Tetrahedron"; + const size_t nlines = sizeof(tetrahedron)/sizeof(const char*); + struct sstl_desc desc = SSTL_DESC_NULL; + float v[3]; + size_t i; + + CHK(sstl != NULL); + + CHK((fp = tmpfile()) != NULL); + FOR_EACH(i, 0, nlines) { + const size_t n = strlen(tetrahedron[i]); + CHK(fwrite(tetrahedron[i], sizeof(char), n, fp) == n); + } + rewind(fp); + + CHK(sstl_load_stream_ascii(sstl, fp, filename) == RES_OK); + CHK(fclose(fp) == 0); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(!strcmp(desc.filename, filename)); + CHK(!strcmp(desc.solid_name, "cube corner")); + CHK(desc.type == SSTL_ASCII); + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(v, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(v, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(v,-1.f, 0.f, 0.f)) == 1); + f3_normalize(v, f3(v, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, v, 1.e-6f) == 1); +} + +static void +check_no_seekable_file(struct sstl* sstl) +{ + const char* stl = + "solid Triangle\n" + " facet normal 0.0 -1.0 0.0\n" + " outer loop\n" + " vertex 1.0 0.0 0.0\n" + " vertex 0.0 0.0 1.0\n" + " vertex 0.0 0.0 0.0\n" + " endloop\n" + " endfacet\n" + "endsolid"; + int fd[2] = {0,0}; + pid_t pid = 0; + + CHK(pipe(fd) == 0); + CHK((pid = fork()) != -1); + + if(pid == 0) { /* Child process */ + CHK(close(fd[0]) == 0); /* Close the unused input stream */ + CHK(sstl_ref_put(sstl) == RES_OK); /* Release the unused sstl */ + + CHK(write(fd[1], stl, strlen(stl)) == (int)strlen(stl)); + CHK(close(fd[1]) == 0); + exit(0); + + } else { /* Parent process */ + struct sstl_desc desc = SSTL_DESC_NULL; + float v[3]; + FILE* fp = NULL; + const char* filename = "Piped StL"; + CHK(close(fd[1]) == 0); /* Close the unused output stream */ + + CHK(fp = fdopen(fd[0], "r")); + CHK(sstl_load_stream(sstl, fp, filename) == RES_BAD_ARG); + CHK(sstl_load_stream_ascii(sstl, fp, filename) == RES_OK); + CHK(fclose(fp) == 0); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_ASCII); + CHK(!strcmp(desc.filename, filename)); + CHK(!strcmp(desc.solid_name, "Triangle")); + 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(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); + } +} + +/******************************************************************************* + * The test + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct sstl* sstl = NULL; + (void)argc, (void)argv; + + CHK(sstl_create(NULL, NULL, 3, &sstl) == RES_OK); + + check_api(sstl); + check_no_triangle(sstl); + check_1_triangle(sstl); + check_1_triangle_with_noise(sstl); + check_1_triangle_no_normal(sstl); + check_invalid_file(sstl); + check_tetrahedron(sstl); + check_no_seekable_file(sstl); + + CHK(sstl_ref_put(sstl) == RES_OK); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_sstl_load_binary.c b/src/test_sstl_load_binary.c @@ -0,0 +1,413 @@ +/* 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* fork */ + +#include "sstl.h" + +#include <rsys/float3.h> +#include <rsys/mem_allocator.h> + +#include <string.h> +#include <unistd.h> /* fork, pipe */ + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +check_api(struct sstl* sstl) +{ + const char header[80] = {0}; + const uint32_t ntris = 0; + const char* filename = "test.stl"; + FILE* fp = NULL; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK((fp = fopen(filename, "w+")) != NULL); + rewind(fp); + + CHK(sstl_load_binary(sstl, NULL) == RES_BAD_ARG); + CHK(sstl_load_binary(NULL, filename) == RES_BAD_ARG); + CHK(sstl_load_binary(sstl, "none.stl") == RES_IO_ERR); + /* A binary cannot be empty */ + CHK(sstl_load_binary(sstl, filename) == RES_BAD_ARG); + + CHK(sstl_load_stream_binary(NULL, fp, filename) == RES_BAD_ARG); + CHK(sstl_load_stream_binary(sstl, NULL, filename) == RES_BAD_ARG); + CHK(sstl_load_stream_binary(sstl, fp, NULL) == RES_BAD_ARG); + /* A binary cannot be empty */ + CHK(sstl_load_stream_binary(sstl, fp, filename) == RES_BAD_ARG); + + /* Write the minimum data required by a binary StL */ + CHK(fwrite(header, sizeof(header), 1, fp) == 1); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + rewind(fp); + + CHK(sstl_load_binary(sstl, filename) == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_BINARY); + CHK(!strcmp(desc.filename, filename)); + CHK(desc.solid_name == NULL); + CHK(desc.vertices_count == 0); + CHK(desc.triangles_count == 0); + + CHK(sstl_load_stream_binary(sstl, fp, filename) == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_BINARY); + CHK(!strcmp(desc.filename, filename)); + CHK(desc.solid_name == NULL); + CHK(desc.vertices_count == 0); + CHK(desc.triangles_count == 0); + + CHK(fclose(fp) == 0); +} + +static void +check_1_triangle(struct sstl* sstl) +{ + char header[80] = {0}; + const uint32_t ntris = 1; + const float normal[3] = {0.f, -1.f, 0.f}; + const float verts[9] = { + 0.f, 0.f, 0.f, + 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f + }; + const uint16_t nattrs = 0; + const char* filename = "1_triangle.stl"; + FILE* fp = NULL; + float v[3] = {0,0,0}; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK(sstl); + + CHK((fp = fopen(filename, "w")) != NULL); + CHK(fwrite(header, sizeof(header), 1, fp) == 1); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + CHK(fwrite(normal, sizeof(normal), 1, fp) == 1); + CHK(fwrite(verts, sizeof(verts), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + CHK(fclose(fp) == 0); + + CHK(sstl_load(sstl, filename) == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + + CHK(desc.type == SSTL_BINARY); + CHK(!strcmp(desc.filename, filename)); + 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(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); +} + +static void +check_1_triangle_no_normal(struct sstl* sstl) +{ + char header[80] = {0}; + const uint32_t ntris = 1; + const float normal[3] = {0.f, 0.f, 0.f}; + const float verts[9] = { + 0.f, 0.f, 0.f, + -1.f, 0.f, 0.f, + 0.f, 0.f,-1.f + }; + const uint16_t nattrs = 0; + const char* filename = "1_triangle_no_normal.stl"; + FILE* fp = NULL; + float v[3] = {0,0,0}; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK((fp = fopen(filename, "w+")) != NULL); + CHK(fwrite(header, sizeof(header), 1, fp) == 1); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + CHK(fwrite(normal, sizeof(normal), 1, fp) == 1); + CHK(fwrite(verts, sizeof(verts), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + rewind(fp); + + CHK(sstl_load_stream(sstl, fp, filename) == RES_OK); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_BINARY); + CHK(!strcmp(desc.filename, filename)); + 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(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v,-1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f,-1.f)) == 1); + + /* Normal is automatically calculated */ + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); + + CHK(fclose(fp) == 0); +} + +static void +check_invalid_file(struct sstl* sstl) +{ + const char header[80] = {0}; + const uint32_t ntris = 1; + const float N[3] = {0,0,0}; + const float v0[3] = {0,0,0}; + const float v1[3] = {1,0,0}; + const float v2[3] = {0,0,1}; + const uint16_t nattrs = 0; + FILE* fp = NULL; + float v[3] = {0,0,0}; + struct sstl_desc desc = SSTL_DESC_NULL; + + /* First, check that the file should be OK if all the data has been correctly + * written, to make sure that the tests really check what we expect, i.e. a + * bad formatting and not a faulty data set */ + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(&header, sizeof(char), sizeof(header), fp) == sizeof(header)); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + CHK(fwrite(N, sizeof(N), 1, fp) == 1); + CHK(fwrite(v0, sizeof(v0), 1, fp) == 1); + CHK(fwrite(v1, sizeof(v1), 1, fp) == 1); + CHK(fwrite(v2, sizeof(v2), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + rewind(fp); + CHK(sstl_load_stream(sstl, fp, "Valid StL") == RES_OK); + CHK(fclose(fp) == 0); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_BINARY); + CHK(!strcmp(desc.filename, "Valid StL")); + 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(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); + + /* Header is too small */ + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(&header, sizeof(char), sizeof(header)-1, fp) == sizeof(header)-1); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + rewind(fp); + CHK(sstl_load_stream(sstl, fp, "Invalid StL") == RES_BAD_ARG); + CHK(fclose(fp) == 0); + + /* Triangle is missing */ + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(&header, sizeof(char), sizeof(header), fp) == sizeof(header)); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + rewind(fp); + CHK(sstl_load_stream(sstl, fp, "Invalid StL") == RES_BAD_ARG); + CHK(fclose(fp) == 0); + + /* Triangle normal is missing */ + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(&header, sizeof(char), sizeof(header), fp) == sizeof(header)); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + CHK(fwrite(v0, sizeof(v0), 1, fp) == 1); + CHK(fwrite(v1, sizeof(v1), 1, fp) == 1); + CHK(fwrite(v2, sizeof(v2), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + rewind(fp); + CHK(sstl_load_stream_binary(sstl, fp, "Invalid StL") == RES_BAD_ARG); + CHK(fclose(fp) == 0); + + /* One vertex of the triangle is wrongly written */ + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(&header, sizeof(char), sizeof(header), fp) == sizeof(header)); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + CHK(fwrite(N, sizeof(N), 1, fp) == 1); + CHK(fwrite(v0, sizeof(v0), 1, fp) == 1); + CHK(fwrite(v1, sizeof(v1)-1/*One byte is missing*/, 1, fp) == 1); + CHK(fwrite(v2, sizeof(v2), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + rewind(fp); + CHK(sstl_load_stream(sstl, fp, "Invalid StL") == RES_BAD_ARG); + CHK(fclose(fp) == 0); + + /* The #attribs is missing */ + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(&header, sizeof(char), sizeof(header), fp) == sizeof(header)); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + CHK(fwrite(N, sizeof(N), 1, fp) == 1); + CHK(fwrite(v0, sizeof(v0), 1, fp) == 1); + CHK(fwrite(v1, sizeof(v1), 1, fp) == 1); + CHK(fwrite(v2, sizeof(v2), 1, fp) == 1); + rewind(fp); + CHK(sstl_load_stream(sstl, fp, "Invalid StL") == RES_BAD_ARG); + CHK(fclose(fp) == 0); +} + +static void +check_tetrahedron(struct sstl* sstl) +{ + const char header[80] = {0}; + float v[3] = {0,0,0}; + const uint32_t ntris = 4; + const uint16_t nattrs = 0; + FILE* fp = NULL; + struct sstl_desc desc = SSTL_DESC_NULL; + + CHK(sstl != NULL); + + CHK((fp = tmpfile()) != NULL); + CHK(fwrite(header, sizeof(header), 1, fp) == 1); + CHK(fwrite(&ntris, sizeof(ntris), 1, fp) == 1); + + CHK(fwrite(f3(v, 0.0f,-1.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.1f, 0.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.0f, 0.1f), sizeof(v), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + + CHK(fwrite(f3(v, 0.0f, 0.0f,-1.f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.1f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.1f, 0.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + + CHK(fwrite(f3(v,-1.0f, 0.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.0f, 0.1f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.1f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + + CHK(fwrite(f3(v, 0.577f, 0.577f, 0.577f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.1f, 0.0f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.1f, 0.0f), sizeof(v), 1, fp) == 1); + CHK(fwrite(f3(v, 0.0f, 0.0f, 0.1f), sizeof(v), 1, fp) == 1); + CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1); + + rewind(fp); + + CHK(sstl_load_stream(sstl, fp, "Tetrahedron") == RES_OK); + CHK(fclose(fp) == 0); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_BINARY); + CHK(!strcmp(desc.filename, "Tetrahedron")); + CHK(desc.solid_name == NULL); + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(v, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(v, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(v,-1.f, 0.f, 0.f)) == 1); + f3_normalize(v, f3(v, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, v, 1.e-6f) == 1); +} + +static void +check_no_seekable_file(struct sstl* sstl) +{ + float v[3] = {0,0,0}; + int fd[2] = {0,0}; + pid_t pid = 0; + + CHK(pipe(fd) == 0); + CHK((pid = fork()) != -1); + + if(pid == 0) { /* Child process */ + const char header[80] = {0}; + const uint32_t ntris = 1; + const uint16_t nattrs = 0; + + CHK(close(fd[0]) == 0); /* Close the unused input stream */ + CHK(sstl_ref_put(sstl) == RES_OK); /* Release the unused sstl */ + + /* Write the binary StL */ + CHK(write(fd[1], header, sizeof(header)) == sizeof(header)); + CHK(write(fd[1], &ntris, sizeof(ntris)) == sizeof(ntris)); + CHK(write(fd[1], f3(v, 0.f,-1.f, 0.f), sizeof(v)) == sizeof(v)); + CHK(write(fd[1], f3(v, 1.f, 0.f, 0.f), sizeof(v)) == sizeof(v)); + CHK(write(fd[1], f3(v, 0.f, 0.f, 1.f), sizeof(v)) == sizeof(v)); + CHK(write(fd[1], f3(v, 0.f, 0.f, 0.f), sizeof(v)) == sizeof(v)); + CHK(write(fd[1], &nattrs, sizeof(nattrs)) == sizeof(nattrs)); + + CHK(close(fd[1]) == 0); + exit(0); + + } else { /* Parent process */ + struct sstl_desc desc = SSTL_DESC_NULL; + FILE* fp = NULL; + CHK(close(fd[1]) == 0); /* Close the unused output stream */ + + CHK(fp = fdopen(fd[0], "r")); + CHK(sstl_load_stream(sstl, fp, "Piped StL") == RES_BAD_ARG); + CHK(sstl_load_stream_binary(sstl, fp, "Piped StL") == RES_OK); + CHK(fclose(fp) == 0); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == SSTL_BINARY); + CHK(!strcmp(desc.filename, "Piped StL")); + 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(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); + } +} + + +/******************************************************************************* + * The test + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct sstl* sstl = NULL; + (void)argc, (void)argv; + + CHK(sstl_create(NULL, NULL, 3, &sstl) == RES_OK); + + check_api(sstl); + check_1_triangle(sstl); + check_1_triangle_no_normal(sstl); + check_invalid_file(sstl); + check_tetrahedron(sstl); + check_no_seekable_file(sstl); + + CHK(sstl_ref_put(sstl) == RES_OK); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_sstl_utils.h b/src/test_sstl_utils.h @@ -1,33 +0,0 @@ -/* Copyright (C) 2015, 2016, 2019, 2021, 2023 |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/>. */ - -#ifndef TEST_SSTL_UTILS_H -#define TEST_SSTL_UTILS_H - -#include <rsys/mem_allocator.h> - -static INLINE void -check_memory_allocator(struct mem_allocator* allocator) -{ - if(MEM_ALLOCATED_SIZE(allocator)) { - char dump[512]; - MEM_DUMP(allocator, dump, sizeof(dump)/sizeof(char)); - fprintf(stderr, "%s\n", dump); - FATAL("Memory leaks\n"); - } -} - -#endif /* TEST_SSTL_UTILS_H */ - diff --git a/src/test_sstl_writer.c b/src/test_sstl_writer.c @@ -0,0 +1,345 @@ +/* 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* fork */ + +#include "sstl.h" + +#include <rsys/float3.h> +#include <rsys/logger.h> +#include <rsys/mem_allocator.h> + +#include <string.h> +#include <unistd.h> /* fork, pipe */ + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +log_stream(const char* msg, void* ctx) +{ + ASSERT(msg); + (void)msg, (void)ctx; + printf("logger %s", msg); +} + +static void +check_api(const enum sstl_type type) +{ + struct logger logger; + struct mem_allocator allocator; + struct sstl_writer_create_args args = SSTL_WRITER_CREATE_ARGS_DEFAULT; + struct sstl_writer* writer = NULL; + const char* filename = "test.stl"; + + CHK(sstl_writer_create(NULL, &writer) == RES_BAD_ARG); + CHK(sstl_writer_create(&args, NULL) == RES_BAD_ARG); + CHK(sstl_writer_create(&args, &writer) == RES_BAD_ARG); + + args.filename = filename; + args.type = type; + args.verbose = 3; + CHK(sstl_writer_create(&args, &writer) == RES_OK); + + CHK(sstl_write_facet(NULL, &SSTL_FACET_NULL) == RES_BAD_ARG); + CHK(sstl_write_facet(writer, NULL) == RES_BAD_ARG); + CHK(sstl_write_facet(writer, &SSTL_FACET_NULL) == RES_OK); + + CHK(sstl_writer_ref_get(NULL) == RES_BAD_ARG); + CHK(sstl_writer_ref_get(writer) == RES_OK); + CHK(sstl_writer_ref_put(NULL) == RES_BAD_ARG); + CHK(sstl_writer_ref_put(writer) == RES_OK); + CHK(sstl_writer_ref_put(writer) == RES_OK); + + args.triangles_count = 0; + CHK(sstl_writer_create(&args, &writer) == RES_OK); + CHK(sstl_write_facet(writer, &SSTL_FACET_NULL) == RES_BAD_OP); + CHK(sstl_writer_ref_put(writer) == RES_OK); + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(logger_init(&allocator, &logger) == RES_OK); + logger_set_stream(&logger, LOG_OUTPUT, log_stream, NULL); + logger_set_stream(&logger, LOG_ERROR, log_stream, NULL); + logger_set_stream(&logger, LOG_WARNING, log_stream, NULL); + + args.allocator = &allocator; + args.logger = &logger; + args.triangles_count = 1; + CHK(sstl_writer_create(&args, &writer) == RES_OK); + CHK(sstl_write_facet(writer, &SSTL_FACET_NULL) == RES_OK); + CHK(sstl_write_facet(writer, &SSTL_FACET_NULL) == RES_BAD_OP); + CHK(sstl_writer_ref_put(writer) == RES_OK); + + logger_release(&logger); + CHK(MEM_ALLOCATED_SIZE(&allocator) == 0); + mem_shutdown_proxy_allocator(&allocator); +} + +static void +check_1_triangle(struct sstl* sstl, const enum sstl_type type) +{ + struct sstl_desc desc = SSTL_DESC_NULL; + struct sstl_facet facet = SSTL_FACET_NULL; + struct sstl_writer_create_args args = SSTL_WRITER_CREATE_ARGS_DEFAULT; + struct sstl_writer* writer = NULL; + + float v[3] = {0,0,0}; + const char* filename = "1_triangle.stl"; + + args.filename = filename; + args.type = type; + args.verbose = 3; + CHK(sstl_writer_create(&args, &writer) == RES_OK); + + f3_splat(facet.normal, 0); /* Let sstl calculate it automatically */ + f3(facet.vertices[0], 0.f, 0.f, 0.f); + f3(facet.vertices[1], 1.f, 0.f, 0.f); + f3(facet.vertices[2], 0.f, 0.f, 1.f); + CHK(sstl_write_facet(writer, &facet) == RES_OK); + + CHK(sstl_writer_ref_put(writer) == RES_OK); + + CHK(sstl_load(sstl, filename) == RES_OK); + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + + CHK(desc.type == type); + CHK(!strcmp(desc.filename, filename)); + 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(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); + + /* The triangle is missing. A warning is signalled but no error is returned: + * author finalization is assumed to always succeed. Partial finalization + * corrupts the output, and it would be too hasardous to try returning to the + * previous state. Finally, detecting a finalization error prevents the writer + * from being released, adding a memory leak to the finalization error, which + * could be impossible to resolve. */ + args.filename = filename; + args.type = SSTL_BINARY; + args.triangles_count = 1; + args.verbose = 3; + CHK(sstl_writer_create(&args, &writer) == RES_OK); + CHK(sstl_writer_ref_put(writer) == RES_OK); +} + +static void +check_tetrahedron(struct sstl* sstl, const enum sstl_type type) +{ + struct sstl_desc desc = SSTL_DESC_NULL; + struct sstl_facet facet = SSTL_FACET_NULL; + struct sstl_writer_create_args args = SSTL_WRITER_CREATE_ARGS_DEFAULT; + struct sstl_writer* writer = NULL; + + float v[3] = {0,0,0}; + FILE* fp = NULL; + const char* filename = "Tetrahedron"; + + CHK((fp = tmpfile()) != NULL); + args.filename = filename; + args.stream = fp; + args.type = type; + args.solid_name = "cube corner"; + args.verbose = 3; + CHK(sstl_writer_create(&args, &writer) == RES_OK); + + f3(facet.normal, 0.f,-1.f, 0.f); + f3(facet.vertices[0], 0.0f, 0.0f, 0.0f); + f3(facet.vertices[1], 0.1f, 0.0f, 0.0f); + f3(facet.vertices[2], 0.0f, 0.0f, 0.1f); + CHK(sstl_write_facet(writer, &facet) == RES_OK); + + f3(facet.normal, 0.f, 0.f, -1.f); + f3(facet.vertices[0], 0.0f, 0.0f, 0.0f); + f3(facet.vertices[1], 0.0f, 0.1f, 0.0f); + f3(facet.vertices[2], 0.1f, 0.0f, 0.0f); + CHK(sstl_write_facet(writer, &facet) == RES_OK); + + f3(facet.normal,-1.f, 0.f, 0.f); + f3(facet.vertices[0], 0.0f, 0.0f, 0.0f); + f3(facet.vertices[1], 0.0f, 0.0f, 0.1f); + f3(facet.vertices[2], 0.0f, 0.1f, 0.0f); + CHK(sstl_write_facet(writer, &facet) == RES_OK); + + f3(facet.normal, 0.577f, 0.577f, 0.577f); + f3(facet.vertices[0], 0.1f, 0.0f, 0.0f); + f3(facet.vertices[1], 0.0f, 0.1f, 0.0f); + f3(facet.vertices[2], 0.0f, 0.0f, 0.1f); + CHK(sstl_write_facet(writer, &facet) == RES_OK); + + CHK(sstl_writer_ref_put(writer) == RES_OK); + + rewind(fp); + CHK(sstl_load_stream(sstl, fp, filename) == RES_OK); + CHK(fclose(fp) == 0); + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(!strcmp(desc.filename, filename)); + CHK(type == SSTL_BINARY || !strcmp(desc.solid_name, "cube corner")); + CHK(desc.type == type); + CHK(desc.vertices_count == 4); + CHK(desc.triangles_count == 4); + + CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(v, 0.0f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(v, 0.1f, 0.0f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(v, 0.0f, 0.1f, 0.0f)) == 1); + CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(v, 0.0f, 0.0f, 0.1f)) == 1); + + CHK(f3_eq(desc.normals + 0*3, f3(v, 0.f,-1.f, 0.f)) == 1); + CHK(f3_eq(desc.normals + 1*3, f3(v, 0.f, 0.f,-1.f)) == 1); + CHK(f3_eq(desc.normals + 2*3, f3(v,-1.f, 0.f, 0.f)) == 1); + f3_normalize(v, f3(v, 1.f, 1.f, 1.f)); + CHK(f3_eq_eps(desc.normals + 3*3, v, 1.e-6f) == 1); +} + +static void +process_write(const enum sstl_type type, FILE* fp, const char* filename) +{ + struct sstl_writer_create_args args = SSTL_WRITER_CREATE_ARGS_DEFAULT; + struct sstl_facet facet = SSTL_FACET_NULL; + struct sstl_writer* writer = NULL; + + args.solid_name = "Triangle"; + args.filename = filename; + args.stream = fp; + args.type = type; + args.verbose = 3; + + switch(type) { + case SSTL_ASCII: + CHK(sstl_writer_create(&args, &writer) == RES_OK); + break; + + case SSTL_BINARY: + /* Binary StL can be written only if the number of triangles is known in + * advance */ + CHK(sstl_writer_create(&args, &writer) == RES_BAD_ARG); + args.triangles_count = 1; + CHK(sstl_writer_create(&args, &writer) == RES_OK); + break; + + default: FATAL("Unreachable code\n"); break; + } + + f3(facet.normal, 0.f,-1.f, 0.f); + f3(facet.vertices[0], 1.f, 0.f, 0.f); + f3(facet.vertices[1], 0.f, 0.f, 1.f); + f3(facet.vertices[2], 0.f, 0.f, 0.f); + CHK(sstl_write_facet(writer, &facet) == RES_OK); + + CHK(sstl_writer_ref_put(writer) == RES_OK); +} + +static void +process_read + (struct sstl* sstl, + const enum sstl_type type, + FILE* fp, + const char* filename) +{ + struct sstl_desc desc = SSTL_DESC_NULL; + float v[3]; + + switch(type) { + case SSTL_ASCII: + CHK(sstl_load_stream_ascii(sstl, fp, filename) == RES_OK); + break; + case SSTL_BINARY: + CHK(sstl_load_stream_binary(sstl, fp, filename) == RES_OK); + break; + default: FATAL("Unreachable code\n"); break; + } + + CHK(sstl_get_desc(sstl, &desc) == RES_OK); + CHK(desc.type == type); + CHK(!strcmp(desc.filename, filename)); + CHK(type == SSTL_ASCII || desc.solid_name == NULL); + CHK(type == SSTL_BINARY || !strcmp(desc.solid_name, "Triangle")); + 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(v, 1.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.vertices + 1*3, f3(v, 0.f, 0.f, 1.f)) == 1); + CHK(f3_eq(desc.vertices + 2*3, f3(v, 0.f, 0.f, 0.f)) == 1); + CHK(f3_eq(desc.normals, f3(v, 0.f, -1.f, 0.f)) == 1); +} + +static void +check_no_seekable_file(struct sstl* sstl, const enum sstl_type type) +{ + int fd[2] = {0,0}; + FILE* fp = NULL; + const char* filename = "Pipe"; + pid_t pid = 0; + + CHK(pipe(fd) == 0); + CHK((pid = fork()) != -1); + + if(pid == 0) { /* Child process */ + CHK(close(fd[0]) == 0); /* Close the unused input stream */ + CHK(sstl_ref_put(sstl) == RES_OK); /* Release the unused sstl */ + CHK((fp = fdopen(fd[1], "w")) != NULL); + process_write(type, fp, filename); + CHK(fclose(fp) == 0); + exit(0); + + } else { /* Parent process */ + CHK(close(fd[1]) == 0); /* Close the unused output stream */ + CHK(fp = fdopen(fd[0], "r")); + process_read(sstl, type, fp, filename); + CHK(fclose(fp) == 0); + } +} + +/******************************************************************************* + * The test + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct sstl* sstl = NULL; + (void)argc, (void)argv; + + CHK(sstl_create(NULL, NULL, 3, &sstl) == RES_OK); + + check_api(SSTL_ASCII); + check_api(SSTL_BINARY); + check_1_triangle(sstl, SSTL_ASCII); + check_1_triangle(sstl, SSTL_BINARY); + check_tetrahedron(sstl, SSTL_ASCII); + check_tetrahedron(sstl, SSTL_BINARY); + check_no_seekable_file(sstl, SSTL_ASCII); + check_no_seekable_file(sstl, SSTL_BINARY); + + CHK(sstl_ref_put(sstl) == RES_OK); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/sstl.1 b/sstl.1 @@ -0,0 +1,101 @@ +.\" 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/>. +.Dd April 11, 2025 +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Dt SSTL 1 +.Os +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh NAME +.Nm sstl +.Nd load and print information on StL files +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SYNOPSIS +.Nm +.Op Fl abhv +.Op Ar file No ... +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh DESCRIPTION +.Nm +loads StL files, whether ASCII or binary encoded, and displays +information about them. +If no file is provided, the StL is read from standard input. +.Pp +Its output is a list of lines, one for each file loaded, where each line +contains a tab-separated list of values formatted as follows: +.Bd -literal -offset Ds +"%s\\t%s\\t%u\\t%u\\t%s\\n", type, name, ntriangles, nvertices, file +.Ed +.Pp +with +.Ar type +the encoding of the file, i.e. either +.Qq ascii +or +.Qq binary , +.Ar name +the name of the geometry +.Po Qq null +if not defined +.Pc , +.Ar ntriangles +and +.Ar nvertices +the number of triangles and vertices in the loaded StL, +and +.Ar file +the file loaded. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a +Assumes ASCII input files. +.It Fl b +Assumes binary input files. +.It Fl h +Output short help and exit. +.It Fl v +Make +.Nm +verbose. +Multiple +.Fl v +options increase the verbosity. +The maximum is 3. +.El +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXIT STATUS +.Ex -std +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXAMPLES +Load all StL files into the current directory. +Let +.Nm +defines their encoding and make the command as verbose as possible: +.Bd -literal -offset Ds +sstl -vvv *.stl +.Ed +.Pp +Read a binary StL from standard input: +.Bd -literal -offset Ds +sstl -b < file.stl +.Ed +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh STANDARDS +.Rs +.%T The StL Format: Standard Data Format for Fabbers +.%A Marshall Burns +.%D 1993 +.%U https://www.fabbers.com/tech/STL_Format +.Re