star-mesh

Define and load a binary data format for meshes
git clone git://git.meso-star.fr/star-mesh.git
Log | Files | Refs | README | LICENSE

commit dcd5347ba86979bb53978c38b60875177e83f141
parent cbf9aaae703b390303e4e46304df49060799a833
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Fri,  9 May 2025 15:36:57 +0200

Merge branch 'release_0.2'

Diffstat:
M.gitignore | 17+++++++++--------
MMakefile | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
MREADME.md | 24+++++++++++++++++++-----
Mconfig.mk | 14+++++++++-----
Adoc/smsh-desc.1 | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/smsh.5 | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/smsh2vtk.1 | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/vtk-data.1 | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dmake.sh | 70----------------------------------------------------------------------
Dsmsh.5 | 102-------------------------------------------------------------------------------
Asrc/smsh-desc.c | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/smsh.c | 2+-
Msrc/smsh.h | 2+-
Asrc/smsh2vtk.c | 355+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/smsh_c.h | 2+-
Msrc/smsh_log.c | 2+-
Msrc/smsh_log.h | 2+-
Msrc/test_smsh.c | 2+-
Msrc/test_smsh_load.c | 2+-
Asrc/vtk-data.c | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
20 files changed, 1360 insertions(+), 253 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,12 +1,13 @@ +*~ +*.[abod] +.config .gitignore -[Bb]uild* -*.sw[po] -*.[aod] +*.pc +smsh2vtk +smsh-desc *.so -*~ +*.sw[po] +tags test* !test*.[ch] -.config -.test -tags -*.pc +vtk-data diff --git a/Makefile b/Makefile @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +# Copyright (C) 2020-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,7 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. .POSIX: -.SUFFIXES: # Clean up default inference rules +.SUFFIXES: .b .c .d .o include config.mk @@ -22,14 +22,20 @@ LIBNAME_STATIC = libsmsh.a LIBNAME_SHARED = libsmsh.so LIBNAME = $(LIBNAME_$(LIB_TYPE)) +default: library utils +all: default tests + ################################################################################ -# Library building +# Library ################################################################################ SRC = src/smsh.c src/smsh_log.c OBJ = $(SRC:.c=.o) DEP = $(SRC:.c=.d) -build_library: .config $(DEP) +CFLAGS_LIB = -std=c89 $(CFLAGS_SO) $(INCS) -DSMSH_SHARED_BUILD +LDFLAGS_LIB = $(LDFLAGS_SO) $(LIBS) + +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) $(CFLAGS_SO) $(RSYS_CFLAGS) -o $@ $(OBJ) $(LDFLAGS_SO) $(RSYS_LIBS) + $(CC) $(CFLAGS_LIB) -o $@ $(OBJ) $(LDFLAGS_LIB) $(LIBNAME_STATIC): libsmsh.o $(AR) -rc $@ $? @@ -51,19 +57,55 @@ libsmsh.o: $(OBJ) $(OBJCOPY) $(OCPFLAGS) $@ .config: config.mk - @if ! $(PKG_CONFIG) --atleast-version $(RSYS_VERSION) rsys; then\ - echo "rsys $(RSYS_VERSION) not found" >&2; exit 1; fi + $(PKG_CONFIG) --atleast-version $(RSYS_VERSION) rsys @echo "config done" > $@ .SUFFIXES: .c .d .o .c.d: - @$(CC) $(CFLAGS_SO) $(RSYS_CFLAGS) -MM -MT "$(@:.d=.o) $@" $< -MF $@ + @$(CC) $(CFLAGS_LIB) -MM -MT "$(@:.d=.o) $@" $< -MF $@ .c.o: - $(CC) $(CFLAGS_SO) $(RSYS_CFLAGS) -DSMSH_SHARED_BUILD -c $< -o $@ + $(CC) $(CFLAGS_LIB) -DSMSH_SHARED_BUILD -c $< -o $@ ################################################################################ -# Installation +# Util +################################################################################ +UTIL_SRC = src/smsh2vtk.c src/smsh-desc.c src/vtk-data.c +UTIL_BIN = $(UTIL_SRC:.c=.b) +UTIL_OBJ = $(UTIL_SRC:.c=.o) +UTIL_DEP = $(UTIL_SRC:.c=.d) +UTIL_EXE = smsh2vtk smsh-desc vtk-data + +PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) +INCS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rsys smsh-local.pc) +LIBS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys smsh-local.pc) + +CFLAGS_UTIL = -std=c99 $(CFLAGS_EXE) $(INCS_UTIL) +LDFLAGS_UTIL = $(LDFLAGS_EXE) $(LIBS_UTIL) + +utils: library $(UTIL_DEP) $(UTIL_BIN) + @for src in $(UTIL_SRC); do \ + dep="$${src%*.c}.d"; \ + bin="$${src%*.c}.b"; \ + exe="$$(basename "$${src}" ".c")"; \ + $(MAKE) -fMakefile -f"$${dep}" -f "$${bin}" "$${exe}"; \ + done + +$(UTIL_BIN): + @exe=$$(basename "$@" ".b"); \ + printf '%s: src/%s.o\n' "$${exe}" "$${exe}" > $@ + +$(UTIL_DEP): config.mk smsh-local.pc + $(CC) $(CFLAGS_UTIL) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ + +$(UTIL_OBJ): config.mk smsh-local.pc + $(CC) $(CFLAGS_UTIL) -c $(@:.o=.c) -o $@ + +$(UTIL_EXE): config.mk smsh-local.pc $(LIBNAME) + $(CC) $(CFLAGS_UTIL) -o $@ src/$@.o $(LDFLAGS_UTIL) + +################################################################################ +# Miscellaneous ################################################################################ pkg: sed -e 's#@PREFIX@#$(PREFIX)#g' \ @@ -79,37 +121,44 @@ smsh-local.pc: smsh.pc.in -e 's#@RSYS_VERSION@#$(RSYS_VERSION)#g'\ smsh.pc.in > $@ -install: build_library pkg - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/lib" $(LIBNAME) - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/lib/pkgconfig" smsh.pc - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/include/star" src/smsh.h - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/share/doc/star-mesh" COPYING README.md - @$(SHELL) make.sh install "$(DESTDIR)$(PREFIX)/share/man/man5" smsh.5 +install: library utils pkg + install() { mode="$$1"; prefix="$$2"; shift 2; \ + mkdir -p "$${prefix}"; \ + cp "$$@" "$${prefix}"; \ + chmod "$${mode}" "$$@"; \ + }; \ + install 755 "$(DESTDIR)$(LIBPREFIX)" $(LIBNAME); \ + install 755 "$(DESTDIR)$(BINPREFIX)" $(UTIL_EXE); \ + install 644 "$(DESTDIR)$(LIBPREFIX)/pkgconfig" smsh.pc; \ + install 644 "$(DESTDIR)$(INCPREFIX)/star" src/smsh.h; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/smsh2vtk.1; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/smsh-desc.1; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man1" doc/vtk-data.1; \ + install 644 "$(DESTDIR)$(MANPREFIX)/man5" doc/smsh.5; \ + install 644 "$(DESTDIR)$(PREFIX)/share/doc/star-mesh" COPYING README.md uninstall: - rm -f "$(DESTDIR)$(PREFIX)/lib/$(LIBNAME)" - rm -f "$(DESTDIR)$(PREFIX)/lib/pkgconfig/smsh.pc" + rm -f "$(DESTDIR)$(LIBPREFIX)/$(LIBNAME)" + rm -f "$(DESTDIR)$(LIBPREFIX)/pkgconfig/smsh.pc" + rm -f "$(DESTDIR)$(INCPREFIX)/star/smsh.h" + rm -f "$(DESTDIR)$(MANPREFIX)/man1/smsh2vtk.1" + rm -f "$(DESTDIR)$(MANPREFIX)/man1/smsh-desc.1" + rm -f "$(DESTDIR)$(MANPREFIX)/man1/vtk-data.1" + rm -f "$(DESTDIR)$(MANPREFIX)/man5/smsh.5" rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-mesh/COPYING" rm -f "$(DESTDIR)$(PREFIX)/share/doc/star-mesh/README.md" - rm -f "$(DESTDIR)$(PREFIX)/include/star/smsh.h" - rm -f "$(DESTDIR)$(PREFIX)/share/man/man5/smsh.5" + for i in $(UTIL_EXE); do rm -f "$(DESTDIR)$(BINPREFIX)/$${i}"; done -################################################################################ -# Miscellaneous targets -################################################################################ -all: build_library build_tests +lint: + mandoc -T lint -Wbase doc/smsh.5 + mandoc -T lint -Wbase doc/smsh2vtk.1 || [ $$? -eq 1 ]; + mandoc -T lint -Wbase doc/smsh-desc.1 || [ $$? -eq 1 ]; + mandoc -T lint -Wbase doc/vtk-data.1 clean: clean_test - rm -f $(OBJ) $(TEST_OBJ) $(LIBNAME) - rm -f .config .test libsmsh.o smsh.pc smsh-local.pc - rm -f test_file.smsh - -distclean: clean - rm -f $(DEP) $(TEST_DEP) - -lint: - shellcheck -o all make.sh - mandoc -T lint -Wbase smsh.5 + rm -f $(DEP) $(OBJ) $(LIBNAME) + rm -f $(UTIL_BIN) $(UTIL_DEP) $(UTIL_OBJ) $(UTIL_EXE) + rm -f .config libsmsh.o smsh.pc smsh-local.pc ################################################################################ # Tests @@ -117,30 +166,52 @@ lint: TEST_SRC = src/test_smsh.c src/test_smsh_load.c TEST_OBJ = $(TEST_SRC:.c=.o) TEST_DEP = $(TEST_SRC:.c=.d) - -PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) -SMSH_CFLAGS = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags smsh-local.pc) -SMSH_LIBS = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs smsh-local.pc) - -test: build_tests - @$(SHELL) make.sh run_test $(TEST_SRC) - -build_tests: build_library $(TEST_DEP) .test - @$(MAKE) -fMakefile -f.test $$(for i in $(TEST_DEP); do echo -f"$${i}"; done) test_bin - -.test: Makefile make.sh - @echo "Setup tests" - @$(SHELL) make.sh config_test $(TEST_SRC) > $@ - -clean_test: - @$(SHELL) make.sh clean_test $(TEST_SRC) +TEST_BIN = $(TEST_SRC:.c=.b) + +INCS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rsys smsh-local.pc) +LIBS_TEST = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys smsh-local.pc) + +CFLAGS_TEST = -std=c89 $(CFLAGS_EXE) $(INCS_TEST) +LDFLAGS_TEST = $(LDFLAGS_EXE) $(LIBS_TEST) + +test: tests + @err=0; \ + for i in $(TEST_SRC); do \ + test="$$(basename "$${i}" ".c")"; \ + if "./$${test}" > /dev/null 2>&1; then \ + printf '%s\n' "$${test}"; \ + else \ + >&2 printf '%s: error %s\n' "$${test}" "$$?"; \ + err=$$((err+1)); \ + fi \ + done; \ + [ "$${err}" -eq 0 ] + +tests: library $(TEST_DEP) $(TEST_BIN) + @$(MAKE) -fMakefile \ + $$(for i in $(TEST_DEP); do echo -f"$${i}"; done) \ + $$(for i in $(TEST_BIN); do echo -f"$${i}"; done) \ + test_bin + +.c.b: + @{ \ + bin="$$(basename "$<" ".c")"; \ + printf '%s: %s\n' "$${bin}" $(<:.c=.o); \ + printf 'test_bin: %s\n' "$${bin}"; \ + } > $@ $(TEST_DEP): config.mk smsh-local.pc - @$(CC) $(CFLAGS_EXE) $(RSYS_CFLAGS) $(SMSH_CFLAGS) \ - -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ + @$(CC) $(CFLAGS_TEST) -MM -MT "$(@:.d=.o) $@" $(@:.d=.c) -MF $@ $(TEST_OBJ): config.mk smsh-local.pc - $(CC) $(CFLAGS_EXE) $(RSYS_CFLAGS) $(SMSH_CFLAGS) -c $(@:.o=.c) -o $@ + $(CC) $(CFLAGS_TEST) -c $(@:.o=.c) -o $@ -test_smsh test_smsh_load: config.mk smsh-local.pc $(LIBNAME) - $(CC) $(CFLAGS_EXE) -o $@ src/$@.o $(LDFLAGS_EXE) $(SMSH_LIBS) $(RSYS_LIBS) +test_smsh \ +test_smsh_load \ +: config.mk smsh-local.pc $(LIBNAME) + $(CC) $(CFLAGS_TEST) -o $@ src/$@.o $(LDFLAGS_TEST) + +clean_test: + rm -f $(TEST_BIN) $(TEST_DEP) $(TEST_OBJ) + rm -f test_file.smsh + for i in $(TEST_SRC); do rm -f "$$(basename "$${i}" ".c")"; done diff --git a/README.md b/README.md @@ -19,12 +19,26 @@ Edit config.mk as needed, then run: ## Release notes +### Version 0.2 + +- Add the smsh-desc utility. + It prints the descriptor of a smsh file +- Add the smsh2vtk utility. + It converts triangular or tetrahedral meshes saved in smshs format to + VTK (legacy) format. +- Add the vtk-data utility. + It formats a list of doubles as cell data in the legacy VTK format. + It is then possible to attach data to a VTK mesh such as that produced + by the smsh2vtk tool. +- Improves the building system. + Simplify it by doing everything in one place (the Makefile). + Add macros to control installation subdirectories. + ### Version 0.1 -- Make memory mapping optional. By default, data is now loaded into - memory. Memory mapping becomes an option of the load functions, - (forbidden on stdin). As a consequence, this commit introduces API - breaks. +- Make memory mapping optional. + By default, data is now loaded into memory. Memory mapping becomes an + option of the load functions, (forbidden on stdin). - Write the man page directly in mdoc's roff macros, instead of using the intermediate scdoc source. - Replace CMake by Makefile as build system. @@ -35,7 +49,7 @@ Edit config.mk as needed, then run: ## License -Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +Copyright (C) 2020-2023, 2025 |Méso|Star> (contact@meso-star.com) Star-Mesh is free software released under the GPL v3+ license: GNU GPL version 3 or later. You are welcome to redistribute it under certain diff --git a/config.mk b/config.mk @@ -1,4 +1,4 @@ -VERSION = 0.1.0 +VERSION = 0.2.0 PREFIX = /usr/local LIB_TYPE = SHARED @@ -7,6 +7,11 @@ LIB_TYPE = SHARED BUILD_TYPE = RELEASE #BUILD_TYPE = DEBUG +BINPREFIX = $(PREFIX)/bin +LIBPREFIX = $(PREFIX)/lib +INCPREFIX = $(PREFIX)/include +MANPREFIX = $(PREFIX)/share/man + ################################################################################ # Tools ################################################################################ @@ -24,9 +29,9 @@ PCFLAGS_SHARED = 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) +RSYS_VERSION = 0.14 +INCS = $$($(PKG_CONFIG) $(PCFLAGS) --cflags rsys) +LIBS = $$($(PKG_CONFIG) $(PCFLAGS) --libs rsys) ################################################################################ # Compilation options @@ -47,7 +52,6 @@ CFLAGS_HARDENED =\ -fstack-protector-strong CFLAGS_COMMON =\ - -std=c89\ -pedantic\ -fvisibility=hidden\ -fstrict-aliasing\ diff --git a/doc/smsh-desc.1 b/doc/smsh-desc.1 @@ -0,0 +1,64 @@ +.\" Copyright (C) 2020-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 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/>. +.Dd May 9, 2025 +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Dt SMSH-DESC 1 +.Os +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh NAME +.Nm smsh-desc +.Nd +.Xr smsh 5 +file descriptor +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SYNOPSIS +.Nm +.Op Fl cdnpt +.Ar mesh +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh DESCRIPTION +Print the pagesize on which the +.Xr smsh 5 +mesh data is aligned, as well as the number and dimension of nodes and +cells. +.Pp +The descritpion is printed on stdout with a message of the form: +.Bd -literal -offset Ds +"%zu %zu %zu %u %u %s", pagesz, #nodes, #cells, Dnode, Dcell, mesh +.Ed +.Pp +When any option is specified, +.Nm +reports only the information requested by the specified options. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c +print cells count. +.It Fl d +print node dimension. +.It Fl n +print node count. +.It Fl p +print data alignment. +.It Fl t +print cell dimension. +.El +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXIT STATUS +.Ex -std +.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SEE ALSO +.Xr smsh 5 diff --git a/doc/smsh.5 b/doc/smsh.5 @@ -0,0 +1,102 @@ +.\" Copyright (C) 2020-2023, 2025, 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 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/>. +.Dd July 26, 2023 +.Dt SMSH 5 +.Os +.Sh NAME +.Nm smsh +.Nd Star-Mesh file format +.Sh DESCRIPTION +.Nm +is a binary file format that describes an indexed mesh +.Pq surface or volume . +Only the geometric data of the mesh is stored; no additional properties are +attached to its nodes or cells. +.Pp +A +.Nm +file begins with a header that describes the layout of the data, followed by +the geometric data itself, i.e. the list of nodes and the list of cells. +.Pp +The header consists of 5 integers. +The first integer is a power of two +.Pq usually 4096 +that defines the size of the memory page in bytes +.Pq Va pagesize +on which the list of nodes and the list of cells are aligned. +By aligning data to +.Va pagesize , +and depending on system requirements, memory mapping can be used to +automatically load/unload pages on demand +.Pq see Xr mmap 2 . +The remaining integers store the number of nodes +.Pq Va #nodes +and the number of cells +.Pq Va #cells , +followed by the size of a node +.Pq Va dimnode +and the size of a cell +.Pq Va dimcell , +respectively. +.Pp +Fill bytes follow the file header to align nodes to +.Va pagesize . +The nodes are then listed with a list of +.Va #nodes dimnode +double-precision floating-point numbers per node, where +.Va #nodes +is the number +of mesh nodes and +.Va dimnode +is the number of floating-point numbers per node. +Additional fill bytes are added after the node list to align the list of +upcoming cells to +.Va pagesize . +The cells are then listed using +.Va dimcell +64-bit unsigned integers per node where each +integer indexes a node in the previously defined list of nodes +.Pq indexing starts at 0 . +Finally, fill bytes are added to align the overall file size to +.Va pagesize . +.Pp +Data are encoded with respect to the little endian bytes ordering, i.e. least +significant bytes are stored first. +.Pp +The file format is as follows: +.Bl -column (pagesize) (::=) () +.It Ao Va smsh Ac Ta ::= Ta Ao Va pagesize Ac Ao Va #nodes Ac Ao Va #cells Ac Ao Va dimnode Ac Ao Va dimcel Ac +.It Ta Ta Aq Va padding +.It Ta Ta Aq Va nodes +.It Ta Ta Aq Va padding +.It Ta Ta Aq Va cells +.It Ta Ta Aq Va padding +.It Ao Va pagesize Ac Ta ::= Ta Vt uint64_t +.It Ao Va #nodes Ac Ta ::= Ta Vt uint64_t +.It Ao Va #cells Ac Ta ::= Ta Vt uint64_t +.It Ao Va dimnode Ac Ta ::= Ta Vt uint32_t +.It Ao Va dimcell Ac Ta ::= Ta Vt uint32_t +.It \ Ta Ta +.It Ao Va padding Ac Ta ::= Ta Op Vt int8_t ... +# Ensure alignment on +.Va pagesize +.It \ Ta Ta +.It Ao Va nodes Ac Ta ::= Ta Ao Va node-pos Ac Va ... +.It Ao Va cells Ac Ta ::= Ta Ao Va cell-ids Ac Va ... +.It Ao Va node-pos Ac Ta ::= Ta Vt double ... +.It Ao Va cell-ids Ac Ta ::= Ta Vt uint64_t ... +.El +.Sh SEE ALSO +.Xr mmap 2 diff --git a/doc/smsh2vtk.1 b/doc/smsh2vtk.1 @@ -0,0 +1,69 @@ +.\" Copyright (C) 2020-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 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/>. +.Dd March 1, 2025 +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Dt SMSH2VTK 1 +.Os +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh NAME +.Nm smsh2vtk +.Nd convert +.Xr smsh 5 +mesh to VTK format +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SYNOPSIS +.Nm +.Op Fl o Ar output +.Ar mesh +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh DESCRIPTION +.Nm +converts a triangular or tetrahedral mesh saved in +.Xr smsh 5 +format to legacy VTK format. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl o Ar output +Output file. +If not defined, output data is written to standard output. +.El +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXIT STATUS +.Ex -std +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXAMPLES +Convert the +.Xr smsh 5 +.Pa input.smsh +mesh to VTK format and saves the results in the +.Pa output.vtk +file: +.Bd -literal -offset Ds +smsh2vtk -o output.vtk input.smsh +.Ed +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SEE ALSO +.Xr smsh 5 +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh STANDARDS +.Rs +.%B The VTK User's Guide +.%O Simple Legacy Formats +.%I Kitware, Inc +.%N 11 +.%D 2010 +.%P 470--482 +.Re diff --git a/doc/vtk-data.1 b/doc/vtk-data.1 @@ -0,0 +1,106 @@ +.\" Copyright (C) 2020-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 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/>. +.Dd May 9, 2025 +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Dt VTK-CELL-DATA 1 +.Os +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh NAME +.Nm vtk-data +.Nd transforms a list of values into cell data in VTK format +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh SYNOPSIS +.Nm +.Op Fl n Ar name No ... +.Op Fl o Ar output +.Fl c Ar cell_count +.Op Ar data +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh DESCRIPTION +.Nm +formats a list of double precision values in cell data sets as defined in the +legacy VTK format. +To describe a valid VTK file, the result must then be concatenated with a VTK +file storing the geometry of the cells to which this data corresponds. +.Pp +As input, +.Nm +reads a set of +.Ar cell_count +floating-point ASCII values from +.Ar data +or from standard input if +.Ar data +is not specified. +This continues until no more data is supplied or 8 sets have been read. +Each set thus represents a data set associated with each cell. +If a set doesn't contain enough data +.Pq i.e. less than Ar cell_count , +an error is returned. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl n Ar name +Name of a data set. +It cannot contain spaces or tabs. +This option can be repeated as many times as there are data sets to be +processed. +By default, the name of each dataset is +.Qq data Ns Ar i , +with +.Ar i +in +.Bq 0,7 +the dataset index. +.It Fl o Ar output +Output file. +If not defined, VTK output data is written to the standard output. +.It Fl c Ar cell_count +Number of cells, i.e. number of floating-point values provided per data set. +.El +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXIT STATUS +.Ex -std +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh EXAMPLES +Formats a list of 200 floating-point ASCII values read from the +.Pa data.txt +file into two sets of +.Ar 100 +values, one value per cell. +Name the first set of data per cell +.Qq temperature , +and the second +.Qq flux . +Output VTK data are written to +.Pa data.vtk . +Next, concatenate the cell data with their corresponding geometry stored in the +.Pa mesh.vtk +file, and thus define the complete VTK file +.Pa results.vtk . +.Bd -literal -offset Ds +vtk-cell-data -c 100 -n temperature -n flux -o data.vtk data.txt +cat mesh.vtk data.vtk > result.vtk +.Ed +.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.Sh STANDARDS +.Rs +.%B The VTK User's Guide +.%O Simple Legacy Formats +.%I Kitware, Inc +.%N 11 +.%D 2010 +.%P 470--482 +.Re diff --git a/make.sh b/make.sh @@ -1,70 +0,0 @@ -#!/bin/sh - -# Copyright (C) 2020-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: %s\n" "${test}" "src/${test}.o" - 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/smsh.5 b/smsh.5 @@ -1,102 +0,0 @@ -.\" Copyright (C) 2020-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/>. -.Dd July 26, 2023 -.Dt SMSH 5 -.Os -.Sh NAME -.Nm smsh -.Nd Star-Mesh file format -.Sh DESCRIPTION -.Nm -is a binary file format that describes an indexed mesh -.Pq surface or volume . -Only the geometric data of the mesh is stored; no additional properties are -attached to its nodes or cells. -.Pp -A -.Nm -file begins with a header that describes the layout of the data, followed by -the geometric data itself, i.e. the list of nodes and the list of cells. -.Pp -The header consists of 5 integers. -The first integer is a power of two -.Pq usually 4096 -that defines the size of the memory page in bytes -.Pq Va pagesize -on which the list of nodes and the list of cells are aligned. -By aligning data to -.Va pagesize , -and depending on system requirements, memory mapping can be used to -automatically load/unload pages on demand -.Pq see Xr mmap 2 . -The remaining integers store the number of nodes -.Pq Va #nodes -and the number of cells -.Pq Va #cells , -followed by the size of a node -.Pq Va dimnode -and the size of a cell -.Pq Va dimcell , -respectively. -.Pp -Fill bytes follow the file header to align nodes to -.Va pagesize . -The nodes are then listed with a list of -.Va #nodes dimnode -double-precision floating-point numbers per node, where -.Va #nodes -is the number -of mesh nodes and -.Va dimnode -is the number of floating-point numbers per node. -Additional fill bytes are added after the node list to align the list of -upcoming cells to -.Va pagesize . -The cells are then listed using -.Va dimcell -64-bit unsigned integers per node where each -integer indexes a node in the previously defined list of nodes -.Pq indexing starts at 0 . -Finally, fill bytes are added to align the overall file size to -.Va pagesize . -.Pp -Data are encoded with respect to the little endian bytes ordering, i.e. least -significant bytes are stored first. -.Pp -The file format is as follows: -.Bl -column (pagesize) (::=) () -.It Ao Va smsh Ac Ta ::= Ta Ao Va pagesize Ac Ao Va #nodes Ac Ao Va #cells Ac Ao Va dimnode Ac Ao Va dimcel Ac -.It Ta Ta Aq Va padding -.It Ta Ta Aq Va nodes -.It Ta Ta Aq Va padding -.It Ta Ta Aq Va cells -.It Ta Ta Aq Va padding -.It Ao Va pagesize Ac Ta ::= Ta Vt uint64_t -.It Ao Va #nodes Ac Ta ::= Ta Vt uint64_t -.It Ao Va #cells Ac Ta ::= Ta Vt uint64_t -.It Ao Va dimnode Ac Ta ::= Ta Vt uint32_t -.It Ao Va dimcell Ac Ta ::= Ta Vt uint32_t -.It \ Ta Ta -.It Ao Va padding Ac Ta ::= Ta Op Vt int8_t ... -# Ensure alignment on -.Va pagesize -.It \ Ta Ta -.It Ao Va nodes Ac Ta ::= Ta Ao Va node-pos Ac Va ... -.It Ao Va cells Ac Ta ::= Ta Ao Va cell-ids Ac Va ... -.It Ao Va node-pos Ac Ta ::= Ta Vt double ... -.It Ao Va cell-ids Ac Ta ::= Ta Vt uint64_t ... -.El -.Sh SEE ALSO -.Xr mmap 2 diff --git a/src/smsh-desc.c b/src/smsh-desc.c @@ -0,0 +1,209 @@ +/* Copyright (C) 2020-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 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* getopt support */ + +#include "smsh.h" + +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> +#include <rsys/str.h> + +#include <errno.h> +#include <unistd.h> /* getopt */ + +enum query_flag { + QUERY_DCELL = BIT(0), + QUERY_DNODE = BIT(1), + QUERY_NCELLS = BIT(2), + QUERY_NNODES = BIT(3), + QUERY_PAGESIZE = BIT(4), + QUERY_ALL = ~0 +}; + +/* Input arguments */ +struct args { + const char* mesh; + int query_mask; /* Cmbination of query flag */ +}; +static const struct args ARGS_DEFAULT = {0}; + +/* Command data */ +struct cmd { + struct args args; + struct smsh* mesh; /* Tetrahedral mesh */ + uint64_t pagesize; +}; +static const struct cmd CMD_NULL = {0}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE void +usage(FILE* stream) +{ + fprintf(stream, "usage: smsh-desc [-cdnpt] mesh\n"); +} + +static res_T +args_init(struct args* args, const int argc, char** argv) +{ + int opt = 0; + res_T res = RES_OK; + + *args = ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "cdnpqt")) != -1) { + switch(opt) { + case 'c': args->query_mask |= QUERY_NCELLS; break; + case 'd': args->query_mask |= QUERY_DNODE; break; + case 'n': args->query_mask |= QUERY_NNODES; break; + case 'p': args->query_mask |= QUERY_PAGESIZE; break; + case 't': args->query_mask |= QUERY_DCELL; break; + default: res = RES_BAD_ARG; + } + if(res != RES_OK) goto error; + } + + if(optind < argc) args->mesh = argv[optind]; + + /* By default, query all */ + if(!args->query_mask) args->query_mask = QUERY_ALL; + + if(!args->mesh) { + fprintf(stderr, "mesh is missing\n"); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + usage(stderr); + goto exit; +} + +static res_T +setup_mesh(struct cmd* cmd, const struct args* args) +{ + struct smsh_create_args create_args = SMSH_CREATE_ARGS_DEFAULT; + struct smsh_load_args load_args = SMSH_LOAD_ARGS_NULL; + FILE* fp = NULL; + res_T res = RES_OK; + ASSERT(cmd && args); + + if(!(fp = fopen(args->mesh, "r"))) { + fprintf(stderr, "%s: %s\n", args->mesh, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + if(fread(&cmd->pagesize, sizeof(uint64_t), 1, fp) != 1) { + fprintf(stderr, "%s: %s\n", args->mesh, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + + create_args.verbose = 1; + if((res = smsh_create(&create_args, &cmd->mesh)) != RES_OK) goto error; + + load_args.path = args->mesh; + if((res = smsh_load(cmd->mesh, &load_args)) != RES_OK) goto error; + +exit: + if(fp) CHK(fclose(fp) == 0); + return res; +error: + if(cmd->mesh) { SMSH(ref_put(cmd->mesh)); cmd->mesh = NULL; } + goto exit; +} + +static void +cmd_release(struct cmd* cmd) +{ + ASSERT(cmd); + if(cmd->mesh) SMSH(ref_put(cmd->mesh)); +} + +static res_T +cmd_init(struct cmd* cmd, const struct args* args) +{ + res_T res = RES_OK; + ASSERT(cmd && args && args->query_mask); + + if((res = setup_mesh(cmd, args)) != RES_OK) goto error; + cmd->args = *args; + +exit: + return res; +error: + cmd_release(cmd); + goto exit; +} + +static res_T +cmd_run(struct cmd* cmd) +{ + struct smsh_desc desc = SMSH_DESC_NULL; + struct str str; + res_T res = RES_OK; + ASSERT(cmd); + + str_init(NULL, &str); + SMSH(get_desc(cmd->mesh, &desc)); + + #define APPEND(Fmt, Val) { \ + if((res = str_append_printf(&str, Fmt, (Val))) != RES_OK) goto error; \ + } (void)0 + if(cmd->args.query_mask & QUERY_PAGESIZE) APPEND("%zu ", cmd->pagesize); + if(cmd->args.query_mask & QUERY_NNODES) APPEND("%zu ", desc.nnodes); + if(cmd->args.query_mask & QUERY_NCELLS) APPEND("%zu ", desc.ncells); + if(cmd->args.query_mask & QUERY_DNODE) APPEND("%u ", desc.dnode); + if(cmd->args.query_mask & QUERY_DCELL) APPEND("%u ", desc.dcell); + APPEND("%s", cmd->args.mesh); + #undef APPEND + + printf("%s\n", str_cget(&str)); + +exit: + str_release(&str); + return res; +error: + fprintf(stderr, "error: %s\n", res_to_cstr(res)); + 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((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/smsh.c b/src/smsh.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +/* Copyright (C) 2020-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 diff --git a/src/smsh.h b/src/smsh.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +/* Copyright (C) 2020-2023, 2025 |Méso|Star> (contact@meso-star.com) * * This program is free software: you can redismshbute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/smsh2vtk.c b/src/smsh2vtk.c @@ -0,0 +1,355 @@ +/* Copyright (C) 2020-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 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* getopt support */ + +#include "smsh.h" + +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> +#include <rsys/text_reader.h> + +#include <errno.h> +#include <string.h> /* strerror */ +#include <unistd.h> /* getopt */ + +/* Input arguments */ +struct args { + const char* mesh; /* Tetrahedral mesh */ + const char* output; /* Output file */ +}; +static const struct args ARGS_DEFAULT = {0}; + +/* Command data */ +struct cmd { + struct smsh* mesh; /* Tetrahedral mesh */ + FILE* output; /* Ouput file */ +}; +static const struct cmd CMD_NULL = {0}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE void +usage(FILE* stream) +{ + fprintf(stream, + "usage: smsh2vtk [-o output] mesh\n"); +} + +static void +args_release(struct args* args) +{ + ASSERT(args); + *args = ARGS_DEFAULT; +} + +static res_T +args_init(struct args* args, const int argc, char** argv) +{ + int opt = 0; + res_T res = RES_OK; + + *args = ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "o:")) != -1) { + switch(opt) { + case 'o': args->output = optarg; break; + default: res = RES_BAD_ARG; + } + if(res != RES_OK) goto error; + } + + if(optind < argc) args->mesh = argv[optind]; + + if(!args->mesh) { + fprintf(stderr, "mesh is missing\n"); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + usage(stderr); + args_release(args); + goto exit; +} + +static res_T +check_mem_leaks(void) +{ + char buffer[128] = {0}; + size_t sz = 0; + + if((sz = mem_allocated_size()) == 0) + return RES_OK; /* No memory leak */ + + size_to_cstr(sz, SIZE_ALL, NULL, buffer, sizeof(buffer)); + fprintf(stderr, "memory leaks: %s\n", buffer); + return RES_MEM_ERR; +} + +static res_T +write_vtk_header(struct cmd* cmd) +{ + res_T res = RES_OK; + ASSERT(cmd); + + #define FPRINTF(Msg) \ + { if(fprintf(cmd->output, Msg) < 0) goto error; } (void)0 + FPRINTF("# vtk DataFile Version 2.0\n"); + FPRINTF("Volumic mesh\n"); + FPRINTF("ASCII\n"); + #undef FPRINTF + +exit: + return res; +error: + fprintf(stderr, "header write error -- %s\n", strerror(errno)); + res = RES_IO_ERR; + goto exit; +} + +static INLINE int +write_nodes(FILE* stream, const struct smsh_desc* desc) +{ + int err = 0; + size_t i = 0; + ASSERT(stream && desc && desc->dnode == 3); + + #define FPRINTF(...) \ + { if(fprintf(stream, __VA_ARGS__) < 0) goto error; } (void)0 + /* Vertices */ + FPRINTF("POINTS %zu double\n", desc->nnodes); + FOR_EACH(i, 0, desc->nnodes) FPRINTF("%f %f %f\n", SPLIT3(desc->nodes+i*3)); + #undef FPRINTF + +exit: + return err; +error: + err = errno; + goto exit; +} + +static res_T +write_tetrahedra(FILE* stream, const struct smsh_desc* desc) +{ + size_t i = 0; + int err = 0; + + ASSERT(stream && desc && desc->dnode == 3 && desc->dcell == 4); + + #define FPRINTF(...) { \ + if(fprintf(stream, __VA_ARGS__) < 0) { err = errno; goto error; } \ + } (void)0 + + FPRINTF("DATASET UNSTRUCTURED_GRID\n"); + + /* Vertices */ + if((err = write_nodes(stream, desc))) goto error; + + /* Cells */ + FPRINTF("CELLS %zu %zu\n", desc->ncells, desc->ncells*(4+1)); + FOR_EACH(i, 0, desc->ncells) { + FPRINTF("4 %zu %zu %zu %zu\n", SPLIT4(desc->cells+i*4)); + } + + /* Cell types (VTK_TETRA == 10) */ + FPRINTF("CELL_TYPES %zu\n", desc->ncells); + FOR_EACH(i, 0, desc->ncells) FPRINTF("10\n"); + + #undef FPRINTF + +exit: + return err; +error: + goto exit; +} + +static res_T +write_triangles(FILE* stream, const struct smsh_desc* desc) +{ + size_t i = 0; + int err = 0; + + ASSERT(stream && desc && desc->dnode == 3 && desc->dcell == 3); + + #define FPRINTF(...) { \ + if(fprintf(stream, __VA_ARGS__) < 0) { err = errno; goto error; } \ + } (void)0 + + FPRINTF("DATASET POLYDATA\n"); + + /* Vertices */ + if((err == write_nodes(stream, desc))) goto error; + + /* Triangles */ + FPRINTF("POLYGONS %zu %zu\n", desc->ncells, desc->ncells*(3+1)); + FOR_EACH(i, 0, desc->ncells) { + FPRINTF("3 %zu %zu %zu\n", SPLIT3(desc->cells+i*3)); + } + + #undef FPRINTF + +exit: + return err; +error: + goto exit; +} + +static int +write_mesh(struct cmd* cmd) +{ + struct smsh_desc desc = SMSH_DESC_NULL; + int err = 0; + res_T res = RES_OK; + ASSERT(cmd); + + SMSH(get_desc(cmd->mesh, &desc)); + + switch(desc.dcell) { + case 3: err = write_triangles(cmd->output, &desc); break; + case 4: err = write_tetrahedra(cmd->output, &desc); break; + default: FATAL("Unreachable code\n"); + } + if(err != 0) goto error; + +exit: + return res; +error: + fprintf(stderr, "mesh write error -- %s\n", strerror(err)); + res = RES_IO_ERR; + goto exit; +} + +static res_T +setup_mesh(struct cmd* cmd, const struct args* args) +{ + struct smsh_create_args create_args = SMSH_CREATE_ARGS_DEFAULT; + struct smsh_load_args load_args = SMSH_LOAD_ARGS_NULL; + struct smsh_desc desc = SMSH_DESC_NULL; + res_T res = RES_OK; + ASSERT(cmd && args); + + create_args.verbose = 1; + if((res = smsh_create(&create_args, &cmd->mesh)) != RES_OK) goto error; + + load_args.path = args->mesh; + if((res = smsh_load(cmd->mesh, &load_args)) != RES_OK) goto error; + + if((res = smsh_get_desc(cmd->mesh, &desc)) != RES_OK) goto error; + + if(desc.dnode != 3 || (desc.dcell != 3 && desc.dcell != 4)) { + fprintf(stderr, + "expecting a tetrahedral mesh or a triangle mesh " + "-- dcell = %u, dnode = %u\n", + desc.dcell, desc.dnode); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + if(cmd->mesh) { SMSH(ref_put(cmd->mesh)); cmd->mesh = NULL; } + goto exit; +} + +static res_T +setup_output(struct cmd* cmd, const struct args* args) +{ + res_T res = RES_OK; + ASSERT(cmd && args); + + if(!args->output) { + cmd->output = stdout; + } else if((cmd->output = fopen(args->output, "w")) == NULL) { + fprintf(stderr, "error opening output file '%s' -- %s\n", + args->output, strerror(errno)); + res = RES_IO_ERR; + goto error; + } + +exit: + return res; +error: + if(cmd->output) { CHK(fclose(cmd->output) == 0); cmd->output = NULL; } + goto exit; +} + +static void +cmd_release(struct cmd* cmd) +{ + ASSERT(cmd); + if(cmd->mesh) SMSH(ref_put(cmd->mesh)); + if(cmd->output && cmd->output != stdout) CHK(fclose(cmd->output) == 0); +} + +static res_T +cmd_init(struct cmd* cmd, const struct args* args) +{ + res_T res = RES_OK; + ASSERT(cmd && args); + + if((res = setup_mesh(cmd, args)) != RES_OK) goto error; + if((res = setup_output(cmd, args)) != RES_OK) goto error; + +exit: + return res; +error: + cmd_release(cmd); + goto exit; +} + +static res_T +cmd_run(struct cmd* cmd) +{ + res_T res = RES_OK; + ASSERT(cmd); + + if((res = write_vtk_header(cmd)) != RES_OK) goto error; + if((res = write_mesh(cmd)) != RES_OK) goto error; + +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((res = cmd_init(&cmd, &args)) != RES_OK) goto error; + if((res = cmd_run(&cmd)) != RES_OK) goto error; + +exit: + args_release(&args); + cmd_release(&cmd); + if((res = check_mem_leaks()) != RES_OK && !err) err = 1; + return err; +error: + err = 1; + goto exit; +} diff --git a/src/smsh_c.h b/src/smsh_c.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +/* Copyright (C) 2020-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 diff --git a/src/smsh_log.c b/src/smsh_log.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +/* Copyright (C) 2020-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 diff --git a/src/smsh_log.h b/src/smsh_log.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +/* Copyright (C) 2020-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 diff --git a/src/test_smsh.c b/src/test_smsh.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +/* Copyright (C) 2020-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 diff --git a/src/test_smsh_load.c b/src/test_smsh_load.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2023 |Méso|Star> (contact@meso-star.com) +/* Copyright (C) 2020-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 diff --git a/src/vtk-data.c b/src/vtk-data.c @@ -0,0 +1,284 @@ +/* Copyright (C) 2020-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 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* getopt support */ + +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> +#include <rsys/str.h> +#include <rsys/text_reader.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> /* getopt */ + +#define MAX_DATA 8 + +/* Input arguments */ +struct args { + const char* output; + const char* data; + unsigned long nitems; + const char* names[MAX_DATA]; + unsigned ndata; +}; +static const struct args ARGS_DEFAULT = {0}; + +/* Command data */ +struct cmd { + const char* names[MAX_DATA]; + const char* data_name; + FILE* output; + FILE* data; + size_t nitems; +}; +static const struct cmd CMD_NULL = {0}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE void +usage(FILE* stream) +{ + fprintf(stream, + "usage: vtk-data [-n name ...] [-o output] -c cell_count [data]\n"); +} + +static res_T +parse_name(struct args* args, const char* name) +{ + unsigned i; + ASSERT(name); + + if(args->ndata >= MAX_DATA) { + fprintf(stderr, "too many names\n"); + return RES_BAD_ARG; + } + + /* Names cannot contain white spaces */ + if(strcspn(name, " \t") != strlen(name)) { + fprintf(stderr, "name \"%s\" cannot contain white spaces \n", name); + return RES_BAD_ARG; + } + + /* Names must be unique */ + FOR_EACH(i, 0, args->ndata) { + if(!strcmp(args->names[i], name)) { + fprintf(stderr, "name \"%s\" already defined\n", name); + return RES_BAD_ARG; + } + } + + args->names[args->ndata++] = name; + return RES_OK; +} + +static res_T +args_init(struct args* args, const int argc, char** argv) +{ + int opt = 0; + res_T res = RES_OK; + + *args = ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "o:c:n:")) != -1) { + switch(opt) { + case 'n': res = parse_name(args, optarg); break; + case 'o': args->output = optarg; break; + case 'c': + res = cstr_to_ulong(optarg, &args->nitems); + if(res == RES_OK && args->nitems == 0) res = RES_BAD_ARG; + break; + default: res = RES_BAD_ARG; goto error; + } + if(res != RES_OK) goto error; + } + + if(optind < argc) args->data = argv[optind]; + + if(!args->nitems) { + fprintf(stderr, "missing item count -- option '-c'\n"); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + usage(stderr); + goto exit; +} + +static void +cmd_release(struct cmd* cmd) +{ + if(cmd->data && cmd->data != stdin) CHK(!fclose(cmd->data)); + if(cmd->output && cmd->output != stdout) CHK(!fclose(cmd->output)); +} + +static res_T +cmd_init(struct cmd* cmd, const struct args* args) +{ + res_T res = RES_OK; + ASSERT(cmd && args); + + if(!(cmd->output = args->output ? fopen(args->output, "w") : stdout)) { + perror(args->output); + res = RES_IO_ERR; + goto error; + } + + if(!(cmd->data = args->data ? fopen(args->data, "r") : stdin)) { + perror(args->data); + res = RES_IO_ERR; + goto error; + } + + cmd->data_name = args->data ? args->data : "stdin"; + memcpy(cmd->names, args->names, sizeof(char*[MAX_DATA])); + cmd->nitems = args->nitems; + +exit: + return res; +error: + cmd_release(cmd); + goto exit; +} + +static res_T +write_data(struct cmd* cmd, const int idata, struct txtrdr* txtrdr) +{ + size_t i = 0; + int res = RES_OK; + + /* Pre-conditions */ + ASSERT(cmd && idata < MAX_DATA && txtrdr); + ASSERT(txtrdr_get_line(txtrdr)); + + if(cmd->names[idata]) { + res = fprintf(cmd->output, "SCALARS %s double 1\n", cmd->names[idata]); + } else { + /* Make default name unique */ + res = fprintf(cmd->output, "SCALARS data%d double 1\n", idata); + } + if(res < 0) { + perror(NULL); + res = RES_IO_ERR; + goto error; + } + + if(fprintf(cmd->output, "LOOKUP_TABLE default\n") < 0) { + perror(NULL); + res = RES_IO_ERR; + goto error; + } + + FOR_EACH(i, 0, cmd->nitems) { + const char* line = NULL; + double f = 0; + + if(!(line = txtrdr_get_line(txtrdr))) { + fprintf(stderr, "missing data\n"); + res = RES_BAD_ARG; + goto error; + } + + if((sscanf(line, "%lf\n", &f)) != 1) { + fprintf(stderr, "%s:%lu: unable to read double\n", + txtrdr_get_name(txtrdr), txtrdr_get_line_num(txtrdr)); + res = RES_IO_ERR; + goto error; + } + + if(fprintf(cmd->output, "%lf\n", f) < 0) { + perror(NULL); + res = RES_IO_ERR; + goto error; + } + + if((res = txtrdr_read_line(txtrdr)) != RES_OK) { + fprintf(stderr, "%s\n", res_to_cstr(res)); + goto error; + } + } + +exit: + return res; +error: + goto exit; +} + +static res_T +cmd_run(struct cmd* cmd) +{ + struct txtrdr* txtrdr = NULL; + int i = 0; + res_T res = RES_OK; + ASSERT(cmd); + + res = txtrdr_stream(NULL, cmd->data, cmd->data_name, '#', &txtrdr); + if(res != RES_OK) { + fprintf(stderr, "%s\n", res_to_cstr(res)); + goto error; + } + + if((res = txtrdr_read_line(txtrdr)) != RES_OK) { + fprintf(stderr, "%s\n", res_to_cstr(res)); + goto error; + } + + if(!txtrdr_get_line(txtrdr)) goto exit; /* No data */ + + if(fprintf(cmd->output, "CELL_DATA %zu\n", cmd->nitems) < 0) { + fprintf(stderr, "%s\n", strerror(errno)); + res = RES_IO_ERR; + goto error; + } + + FOR_EACH(i, 0, MAX_DATA) { + if((res = write_data(cmd, i, txtrdr)) != RES_OK) goto error; + if(!txtrdr_get_line(txtrdr)) break; /* No more data */ + } + +exit: + if(txtrdr) txtrdr_ref_put(txtrdr); + return res; +error: + goto exit; +} + +/******************************************************************************* + * Main function + ******************************************************************************/ +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((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; +}