commit d44bbc28755a47cdbb38c83383c7f768ce0d8c3d
parent 2ece078a8792331f46aab354dc134266fabe6835
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Wed, 23 Apr 2025 16:21:53 +0200
Merge branch 'feature_refactoring' into develop
Diffstat:
| M | .gitignore | | | 19 | ++++++++++--------- |
| M | Makefile | | | 162 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
| M | README.md | | | 12 | ++++++++---- |
| M | config.mk | | | 12 | +++++++++--- |
| D | make.sh | | | 70 | ---------------------------------------------------------------------- |
| M | src/sstl.c | | | 1026 | ++++++++++--------------------------------------------------------------------- |
| M | src/sstl.h | | | 137 | ++++++++++++++++++++++++++++++++++++++++++++----------------------------------- |
| A | src/sstl_ascii.c | | | 298 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/sstl_binary.c | | | 146 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/sstl_c.h | | | 143 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/sstl_main.c | | | 213 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/sstl_writer.c | | | 520 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/test_sstl.c | | | 14 | ++++++++++++-- |
| D | src/test_sstl_load.c | | | 582 | ------------------------------------------------------------------------------ |
| A | src/test_sstl_load_ascii.c | | | 483 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/test_sstl_load_binary.c | | | 413 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| D | src/test_sstl_utils.h | | | 33 | --------------------------------- |
| A | src/test_sstl_writer.c | | | 345 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | sstl.1 | | | 101 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
19 files changed, 3012 insertions(+), 1717 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
@@ -18,10 +19,13 @@ Edit config.mk as needed, then run:
## Release notes
### 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 +60,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
@@ -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 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* 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.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(v, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(v, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(v, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(v, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(v, 0.f, 0.f, 1.f)) == 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.f,-1.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 0.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 1.f, 0.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 0.f, 1.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1);
+
+ CHK(fwrite(f3(v, 0.f, 0.f,-1.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 0.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 1.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 1.f, 0.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(&nattrs, sizeof(nattrs), 1, fp) == 1);
+
+ CHK(fwrite(f3(v,-1.f, 0.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 0.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 0.f, 1.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 1.f, 0.f), 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, 1.f, 0.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 1.f, 0.f), sizeof(v), 1, fp) == 1);
+ CHK(fwrite(f3(v, 0.f, 0.f, 1.f), 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.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(v, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(v, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(v, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(v, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(v, 0.f, 0.f, 1.f)) == 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.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);
+
+ f3(facet.normal, 0.f, 0.f, -1.f);
+ f3(facet.vertices[0], 0.f, 0.f, 0.f);
+ f3(facet.vertices[1], 0.f, 1.f, 0.f);
+ f3(facet.vertices[2], 1.f, 0.f, 0.f);
+ CHK(sstl_write_facet(writer, &facet) == RES_OK);
+
+ f3(facet.normal,-1.f, 0.f, 0.f);
+ f3(facet.vertices[0], 0.f, 0.f, 0.f);
+ f3(facet.vertices[1], 0.f, 0.f, 1.f);
+ f3(facet.vertices[2], 0.f, 1.f, 0.f);
+ CHK(sstl_write_facet(writer, &facet) == RES_OK);
+
+ f3(facet.normal, 0.577f, 0.577f, 0.577f);
+ f3(facet.vertices[0], 1.f, 0.f, 0.f);
+ f3(facet.vertices[1], 0.f, 1.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);
+
+ 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.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(v, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(v, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(v, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(v, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(v, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(v, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(v, 0.f, 0.f, 1.f)) == 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