star-cad

Geometric operators for computer-aided design
git clone git://git.meso-star.fr/star-cad.git
Log | Files | Refs | README | LICENSE

commit 3e66a85c6765bcea4401c206f1310e242f97271e
parent 73052ae91d67aed8e7a9681ec0f2b6798d56b1be
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Fri, 24 Mar 2023 14:48:17 +0100

Merge branch 'release_0.1'

Diffstat:
A.gitignore | 12++++++++++++
DMakefile | 41-----------------------------------------
MREADME.md | 35+++++++++++++++++++++++++++++------
Acmake/CMakeLists.txt | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dconfig.mk | 10----------
Msrc/scad.c | 1059+++++++++++++++++++++++++++++--------------------------------------------------
Msrc/scad.h | 540++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Asrc/scad_c.h | 36++++++++++++++++++++++++++++++++++++
Asrc/scad_device.c | 434+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scad_device.h | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scad_geometry.c | 1960+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scad_geometry.h | 37+++++++++++++++++++++++++++++++++++++
Dsrc/test.c | 48------------------------------------------------
Asrc/test_api.c | 265+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_common.h | 38++++++++++++++++++++++++++++++++++++++
Asrc/test_export.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_partition.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
17 files changed, 4079 insertions(+), 890 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,12 @@ +.gitignore +CMakeCache.txt +CMakeFiles +Makefile +tmp +[Bb]uild* +*.sw[po] +*.[ao] +*.orig +*~ +tags +test diff --git a/Makefile b/Makefile @@ -1,41 +0,0 @@ -.SUFFIXES: # Clean up default inference rules - -include config.mk - -SRC = src/scad.c - -OBJ = $(SRC:.c=.o) - -all: static_lib - -$(OBJ): config.mk - -static_lib: $(OBJ) - $(AR) $(AFLAGS) libscad.a $(OBJ) - -.SUFFIXES: .c .o -.c.o: - $(CC) $(CFLAGS) -c $< -o $@ - -#------------------------------------------------------------------------------ - -test: $(OBJ) src/test.o - $(CC) -o $@ src/test.o $(OBJ) $(LDLIBS) $(LDFLAGS) - -src/test.o: src/test.c - $(CC) $(CFLAGS) -c $< -o $@ - -clean: - @rm -f $(OBJ) libscad.a src/test.o test - -API = src/scad.h - -install: static_lib - mkdir -p $(PREFIX)/lib - mkdir -p $(PREFIX)/include/ - cp libscad.a $(PREFIX)/lib - cp $(API) $(PREFIX)/include/ - -uninstall: - rm -f $(PREFIX)/lib/libscad.a - rm -f $(PREFIX)/include/scad.h diff --git a/README.md b/README.md @@ -1,10 +1,33 @@ # Star-Cad -Star-Cad is a gmsh wrapper library. +Star-Cad is mostly a wrapper for the gmsh library. It only provides access to +some of the OpenCascade kernel features, with the benefit of simplified use. One +of the most valuable features of Star-Cad is that it implements an automated +reference counting mecanism that eases geometry reference, whilst gmsh system +of tags doesn't ensure tag persistence accross geometry deletions. -Star-Cad depends on : -- gmsh https://gmsh.info/ -- Rsys https://gitlab.com/vaplv/rsys +## How to build -To build Star-Cad modify the config.mk to find Rsys and Gmsh on your system. -Star-Cad is build as a static library. +The library uses [CMake](http://www.cmake.org) and the +[RCMake](https://gitlab.com/vaplv/rcmake/#tab-readme) package to build. It also +depends on the +[Gmsh](https://gmsh.info/) and +[RSys](https://gitlab.com/vaplv/rsys/#tab-readme) libraries. + +First ensure that CMake is installed on your system. Then install the RCMake +package as well as all the aforementioned prerequisites. Then generate the +project from the `cmake/CMakeLists.txt` file by appending to the +`CMAKE_PREFIX_PATH` variable the install directories of its dependencies. + +## Release notes + +### Version 0.1.0 + +Initial version. + +## License + +Copyright (C) 2022-2023, 2021 |Meso|Star> (<contact@meso-star.com>). +Star-Cad is free software released under the GPL v3+ license: GNU GPL +version 3 or later. You are welcome to redistribute it under certain +conditions; refer to the COPYING file for details. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -0,0 +1,111 @@ +# Copyright (C) 2022 |Meso|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/>. + +cmake_minimum_required(VERSION 3.1) +project(star-cad C) +enable_testing() + +set(SCAD_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src) +option(NO_TEST "Disable the test" OFF) + +################################################################################ +# Check dependencies +################################################################################ +find_package(gmsh 4.9.5 REQUIRED) +find_package(RCMake 0.4.1 REQUIRED) +find_package(RSys 0.12.1 REQUIRED) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) +include(rcmake) +include(rcmake_runtime) + +include_directories(${GMSH_INCLUDE_DIR} ${RSys_INCLUDE_DIR}) + +################################################################################ +# Configure and define targets +################################################################################ +set(VERSION_MAJOR 0) +set(VERSION_MINOR 1) +set(VERSION_PATCH 0) +set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) + +set(SCAD_FILES_SRC + scad.c + scad_device.c + scad_geometry.c) +set(SCAD_FILES_INC_API scad.h) +set(SCAD_FILES_INC + scad_device.h + scad_geometry.h) +set(SCAD_FILES_DOC COPYING README.md) + +# Prepend each file in the `SCAD_FILES_<SRC|INC>' list by `SCAD_SOURCE_DIR' +rcmake_prepend_path(SCAD_FILES_SRC ${SCAD_SOURCE_DIR}) +rcmake_prepend_path(SCAD_FILES_INC ${SCAD_SOURCE_DIR}) +rcmake_prepend_path(SCAD_FILES_INC_API ${SCAD_SOURCE_DIR}) +rcmake_prepend_path(SCAD_FILES_DOC ${PROJECT_SOURCE_DIR}/../) + +add_library(scad SHARED ${SCAD_FILES_SRC} ${SCAD_FILES_INC} ${SCAD_FILES_INC_API}) +target_link_libraries(scad RSys ${GMSH_LIBRARY} m) + +set_target_properties(scad PROPERTIES + DEFINE_SYMBOL SCAD_SHARED_BUILD + VERSION ${VERSION} + SOVERSION ${VERSION_MAJOR}) + +rcmake_setup_devel(scad StarCAD ${VERSION} star/scad_version.h) + +################################################################################ +# Add tests +################################################################################ +if(NOT NO_TEST) + function(build_test _name) + add_executable(${_name} + ${SCAD_SOURCE_DIR}/${_name}.c + ${SCAD_SOURCE_DIR}/test_common.h) + target_link_libraries(${_name} scad RSys) + set(_libraries ${ARGN}) + foreach(_lib ${_libraries}) + target_link_libraries(${_name} ${_lib}) + endforeach() + endfunction() + + function(register_test _name) + add_test(${_name} ${ARGN}) + endfunction() + + function(new_test _name) + build_test(${_name} ${ARGN}) + register_test(${_name} ${_name}) + endfunction() + + new_test(test_api) + new_test(test_export) + new_test(test_partition) + + rcmake_copy_runtime_libraries(test) + +endif(NOT NO_TEST) + +################################################################################ +# Define output & install directories +################################################################################ +install(TARGETS scad + ARCHIVE DESTINATION bin + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) +install(FILES ${SCAD_FILES_INC_API} DESTINATION include/star) +install(FILES ${SCAD_FILES_DOC} DESTINATION share/doc/star-cad) + diff --git a/config.mk b/config.mk @@ -1,10 +0,0 @@ -CC = cc -PREFIX = /usr/local -INC = /usr/include -LIB = /usr/lib - -AFLAGS = crs -WFLAGS = -Wall -Wextra -Wmissing-declarations -Wmissing-prototypes -Wconversion -Wshadow -CFLAGS = -O2 -std=c89 -pedantic -I$(INC) $(WFLAGS) -LDFLAGS = -L$(LIB) -LDLIBS = -lm -lrsys -lgmsh diff --git a/src/scad.c b/src/scad.c @@ -13,815 +13,528 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - #include "scad.h" -#include <rsys/str.h> - -res_T -scad_init(void) -{ - res_T res = RES_OK; - int ierr = 0; - gmshInitialize(0, NULL, 1, 0, &ierr); - gmshModelAdd("model", &ierr); - - gmshOptionSetNumber("Mesh.StlOneSolidPerSurface", 2, &ierr); - gmshOptionSetNumber("Mesh.MeshSizeFromPoints", 0, &ierr); - gmshOptionSetNumber("Mesh.MeshSizeFromCurvature", 1, &ierr); - gmshOptionSetNumber("Mesh.MinimumElementsPerTwoPi", 36, &ierr); - gmshOptionSetNumber("Mesh.MeshSizeExtendFromBoundary", 0, &ierr); - - if (ierr !=0) goto error; - -exit: - return res; -error: - fprintf(stderr,"Can't initialize gmsh !\n"); - goto exit; -} +#include "scad_c.h" +#include "scad_device.h" +#include "scad_geometry.h" -res_T -scad_release(void) -{ - int ierr = 0; - gmshFinalize(&ierr); - - if (ierr != 0) goto error; - -exit: - return ierr; -error: - fprintf(stderr,"Can't release gmsh !\n"); - goto exit; -} - -res_T -scad_synchronize(void) -{ - int ierr; - gmshModelOccSynchronize(&ierr); - return ierr; -} +#include <gmsh/gmshc.h> -res_T -scad_run_ui(void) -{ - int ierr; - gmshFltkRun(&ierr); - return ierr; -} - - -res_T -scad_concat(scad_geom_T* geom1, const scad_geom_T geom2) -{ - int res = RES_OK; - int i; +#include <rsys/cstr.h> +#include <rsys/rsys.h> +#include <rsys/str.h> +#include <rsys/double3.h> +#include <rsys/float3.h> - if (!geom2) {fprintf(stderr,"Can't concat \n"); res = RES_BAD_ARG; goto error;} +#include <stdio.h> - for (i=0; i<(int)sa_size(geom2) ; ++i) - { - sa_push(*geom1, geom2[i]); - } +#define OKP(Expr) if((Expr) < 0) { res=RES_IO_ERR; goto error; } -exit: - return res; -error: - goto exit; -} - -res_T -scad_addrectangle -(const double xyz[3], const double dxdy[2], scad_geom_T* geom) +static res_T +write_ascii_stl + (const char* filename, + const unsigned trg_count, + double* const* coord, + const size_t* coord_n) { res_T res = RES_OK; - int ierr = 0; - int tag = 0; - - if(!xyz || !dxdy) { - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; - } + size_t i; + FILE* stl_file = NULL; + unsigned cpt; - tag = gmshModelOccAddRectangle(xyz[0], xyz[1], xyz[2], - dxdy[0], dxdy[1], - -1, - 0, - &ierr); + ASSERT(filename && coord && coord_n); - if (ierr !=0) goto error; + stl_file = fopen(filename, "w"); + if(!stl_file) { + res = RES_IO_ERR; + goto error; + } - if (*geom != NULL) *geom = NULL; - sa_push(*geom,2); - sa_push(*geom,tag); + OKP(fprintf(stl_file, "solid %s\n", filename)); + + i = 0; + cpt = 0; + while(cpt < trg_count) { + size_t j; + for(j = 0; j < coord_n[i]; j += 9) { + int k; + double n[3] = { 0,0,0 }, zero[3] = { 0,0,0 }; + double vtx[3][3]; + double tmp[3], edge1[3], edge2[3];; + d3_set(vtx[0], coord[i]+j+0); + d3_set(vtx[1], coord[i]+j+3); + d3_set(vtx[2], coord[i]+j+6); + d3_sub(edge1, vtx[1], vtx[0]); + d3_sub(edge2, vtx[2], vtx[0]); + d3_cross(tmp, edge1, edge2); + if(d3_eq(tmp, zero)) { + log_error(get_device(), "Error: nul triangle in exported geometry.\n"); + res = RES_BAD_ARG; + goto error; + } + d3_normalize(n, tmp); + + OKP(fprintf(stl_file, " facet normal %g %g %g\n", SPLIT3(n))); + OKP(fprintf(stl_file, " outer loop\n")); + for(k = 0; k < 3; k++) { + OKP(fprintf(stl_file, " vertex %.16g %.16g %.16g\n", SPLIT3(vtx[k]))); + } + OKP(fprintf(stl_file, " endloop\n")); + OKP(fprintf(stl_file, " endfacet\n")); + + cpt++; + } + i++; + } + OKP(fprintf(stl_file, "endsolid \n")); exit: + if(stl_file) fclose(stl_file); return res; error: - res = ierr; - fprintf(stderr,"Can't create rectangle !\n"); + log_error(get_device(), "Error: could not export to STL file '%s': %s\n", + filename, res_to_cstr(res)); goto exit; } -res_T -scad_adddisk -(const double xyz[3], const double rad, scad_geom_T* geom) +static res_T +write_binary_stl + (const char* filename, + const unsigned trg_count, + double* const* coord, + const size_t* coord_n) { res_T res = RES_OK; - int ierr = 0; - int tag = 0; - - if(!xyz || rad < 0) { - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; - } - - tag = gmshModelOccAddDisk(xyz[0], xyz[1], xyz[2], rad, rad, -1, &ierr); + size_t i; + char header[80] = "Binary STL"; + FILE* stl_file = NULL; + unsigned cpt; - if (ierr !=0) goto error; - - if (*geom != NULL) *geom = NULL; - sa_push(*geom,2); - sa_push(*geom,tag); - -exit: - return res; -error: - res = ierr; - fprintf(stderr,"Can't create disk !\n"); - goto exit; -} + ASSERT(filename && coord && coord_n); -res_T -scad_addpolygon -(const double* x, const double* y, const double z, const int n, scad_geom_T* geom) -{ - res_T res = RES_OK; - int ierr = 0; - int i; - int* points = NULL; - int* lines = NULL; - int loop; - int surf; - - if(!x || !y || n<3) { - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; + stl_file = fopen(filename, "wb"); + if(!stl_file) { + res = RES_IO_ERR; + goto error; } - for (i=0; i<n; ++i){ - int tag; - tag = gmshModelOccAddPoint(x[i], y[i], z, -1, -1, &ierr); - sa_push(points, tag); - if (ierr !=0) goto error; + if(1 != fwrite(header, sizeof(header), 1, stl_file)) { + res = RES_IO_ERR; + goto error; } - - for (i=0; i<n; ++i){ - int tag; - - if (i != (n-1)) { - tag = gmshModelOccAddLine(points[i], - points[i+1], - -1, - &ierr); - if (ierr !=0) goto error; - } else { - tag = gmshModelOccAddLine(points[i], - points[0], - -1, - &ierr); - if (ierr !=0) goto error; - } - sa_push(lines, tag); - } - - loop = gmshModelOccAddCurveLoop(lines, sa_size(lines), -1, &ierr); - if (ierr !=0) goto error; - surf = gmshModelOccAddPlaneSurface(&loop, 1, -1, &ierr); - if (ierr !=0) goto error; + if(1 != fwrite(&trg_count, 4, 1, stl_file)) { + res = RES_IO_ERR; + goto error; + } - if (*geom != NULL) *geom = NULL; - sa_push(*geom,2); - sa_push(*geom,surf); + i = 0; + cpt = 0; + while(cpt < trg_count) { + size_t j; + for(j = 0; j < coord_n[i]; j += 9) { + struct { + float n[3]; + float vrtx[3][3]; + unsigned short attrib; + } trg; + float tmp[3], edge1[3], edge2[3];; + f3_set_d3(trg.vrtx[0], coord[i]+j+0); + f3_set_d3(trg.vrtx[1], coord[i]+j+3); + f3_set_d3(trg.vrtx[2], coord[i]+j+6); + f3_sub(edge1, trg.vrtx[1], trg.vrtx[0]); + f3_sub(edge2, trg.vrtx[2], trg.vrtx[0]); + f3_cross(tmp, edge1, edge2); + f3_normalize(trg.n, tmp); + trg.attrib = 0; + if(1 != fwrite(&trg, 50, 1, stl_file)) { + res = RES_IO_ERR; + goto error; + } + cpt++; + } + i++; + } exit: - if (points) sa_release(points); - if (lines) sa_release(lines); + if(stl_file) fclose(stl_file); return res; error: - res = ierr; - fprintf(stderr,"Can't create polygon !\n"); + log_error(get_device(), "Error: could not export to STL file '%s': %s\n", + filename, res_to_cstr(res)); goto exit; } -res_T -scad_addbox -(const double xyz[3], const double dxdydz[3], scad_geom_T* geom) +static res_T +write_stl + (const char* filename, + const unsigned trg_count, + double* const* coord, + const size_t* coord_n, + const int binary) { - res_T res = RES_OK; - int ierr = 0; - int tag = 0; - - if(!xyz || !dxdydz) { - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; - } - - tag = gmshModelOccAddBox(xyz[0], xyz[1], xyz[2], - dxdydz[0], dxdydz[1], dxdydz[2], - -1, - &ierr); - - if (ierr !=0) goto error; - - if (*geom != NULL) *geom = NULL; - sa_push(*geom,3); - sa_push(*geom,tag); - -exit: - return res; -error: - res = ierr; - fprintf(stderr,"Can't create box !\n"); - goto exit; - + ASSERT(filename && coord && coord_n); + if(binary) return write_binary_stl(filename, trg_count, coord, coord_n); + else return write_ascii_stl(filename, trg_count, coord, coord_n); } - +/****************************************************************************** + * Exported functions + *****************************************************************************/ res_T -scad_addcylinder -(const double xyz[3], const double axis[3], const double rad, const double angle, scad_geom_T* geom) +scad_synchronize + (void) { res_T res = RES_OK; - int ierr = 0; - int tag = 0; + struct scad_device* dev = get_device(); + int ierr; - if(!xyz || !axis) { - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; + /* Cannot use check_device to avoid an infinite loop! */ + if(!dev) { + /* No logger available for a message */ + fprintf(stderr, + "%s: cannot call API functions if star-cad is not initialized.\n", + FUNC_NAME); + res = RES_BAD_ARG; + goto error; } - tag = gmshModelOccAddCylinder(xyz[0], - xyz[1], - xyz[2], - axis[0], - axis[1], - axis[2], - rad, - -1, - angle, - &ierr); - - if (ierr !=0) goto error; - - if (*geom != NULL) *geom = NULL; - sa_push(*geom,3); - sa_push(*geom,tag); + gmshModelOccSynchronize(&ierr); + get_device()->need_synchro = dev->options.Misc.DebugOpenCascadeSync; + ERR(gmsh_err_to_res_T(ierr)); exit: return res; error: - res = ierr; - fprintf(stderr,"Can't create cylinder !\n"); goto exit; } -int -scad_addsphere -(const double xyz[3], const double rad, scad_geom_T* geom) +res_T +scad_run_ui(void) { res_T res = RES_OK; - int ierr = 0; - int tag = 0; + struct scad_device* dev = get_device(); + int ierr; - if(!xyz) { - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; + /* Cannot use check_device to avoid an infinite loop! */ + if(!dev) { + /* No logger available for a message */ + fprintf(stderr, + "%s: cannot call API functions if star-cad is not initialized.\n", + FUNC_NAME); + res = RES_BAD_ARG; + goto error; } - tag = gmshModelOccAddSphere(xyz[0], - xyz[1], - xyz[2], - rad, - -1, - -PI/2, - PI/2, - 2*PI, - &ierr); - - if (ierr !=0) goto error; + if(dev->options.Misc.SynchronizeOnRunUI && get_device()->need_synchro) { + ERR(scad_synchronize()); + } - if (*geom != NULL) *geom = NULL; - sa_push(*geom,3); - sa_push(*geom,tag); + gmshFltkRun(&ierr); + ERR(gmsh_err_to_res_T(ierr)); exit: return res; error: - res = ierr; - fprintf(stderr,"Can't create sphere !\n"); goto exit; } res_T -scad_remove(scad_geom_T geom) +scad_scene_write + (const char* name) { + int ierr; res_T res = RES_OK; - int ierr = 0; - gmshModelOccRemove(geom, sa_size(geom), 0, &ierr); - - if (ierr !=0) goto error; - -exit: - return res; -error: - res = ierr; - fprintf(stderr,"Can't remove geometry !\n"); - goto exit; -} - - -res_T -scad_fuse -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove) -{ - res_T res = RES_OK; - int ierr, i; - int* tagout; - int** map = NULL; - size_t tagoutn, *mapn, mapnn; - - gmshModelOccFuse(geom1, sa_size(geom1), - geom2, sa_size(geom2), - &tagout, &tagoutn, - &map, &mapn, &mapnn, - -1, - remove, - remove, - &ierr); - - if (ierr != 0 ) {fprintf(stderr,"Fuse not possible !\n"); goto error;} - - if (*out) sa_release(*out); *out = NULL; - for (i=0; i<(int)tagoutn; ++i){ - sa_push(*out, tagout[i]); + if(!name) { + res = RES_BAD_ARG; + goto error; } - -exit: - if (tagout) free(tagout); - if (mapn) free(mapn); - if (map) free(map); - return res; -error: - goto exit; -} - -res_T -scad_cut -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove) -{ - res_T res = RES_OK; - int ierr, i; - int* tagout; - int** map = NULL; - size_t tagoutn, *mapn, mapnn; - - gmshModelOccCut(geom1, sa_size(geom1), - geom2, sa_size(geom2), - &tagout, &tagoutn, - &map, &mapn, &mapnn, - -1, - remove, - remove, - &ierr); - - if (ierr != 0 ) {fprintf(stderr,"Cut not possible !\n"); goto error;} - - if (*out) sa_release(*out); *out = NULL; - for (i=0; i<(int)tagoutn; ++i){ - sa_push(*out, tagout[i]); - } + gmshWrite(name, &ierr); + ERR(gmsh_err_to_res_T(ierr)); exit: - if (tagout) free(tagout); - if (mapn) free(mapn); - if (map) free(map); return res; error: goto exit; } res_T -scad_intersect -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove) +scad_stl_export + (struct scad_geometry* geometry, + const char* prefix, + const int reverse, + const int binary) { + struct str filename; + int* dimTags_to_free = NULL; + int* faces_dimTags = NULL; + size_t faces_dimTags_n; + size_t** nodeTags = NULL; + size_t* nodeTags_n = NULL; + double** coord = NULL; + size_t* coord_n = NULL; + double** pCoord = NULL; + size_t* pCoord_n = NULL; + size_t i, tcount; + int dim_ctrl; + int ierr = 0; + int* data; + size_t sz; + int str_initialized = 0; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; res_T res = RES_OK; - int ierr, i; - int* tagout; - int** map = NULL; - size_t tagoutn, *mapn, mapnn; - - gmshModelOccIntersect(geom1, sa_size(geom1), - geom2, sa_size(geom2), - &tagout, &tagoutn, - &map, &mapn, &mapnn, - -1, - remove, - remove, - &ierr); - - if (ierr != 0 ) {fprintf(stderr,"Intersection not possible !\n"); goto error;} - - - tagoutn = 0; - if (tagoutn == 0) { /* try instersect boundary to extract common face */ - int* bound1; - int* bound2; - size_t n1, n2; - - gmshModelGetBoundary(geom1, sa_size(geom1), - &bound1, &n1, - 1, - 0, - 0, - &ierr); - - /*if (ierr != 0) { */ - /*if (bound1) free(bound1};*/ - /*goto error:*/ - /*}*/ - - gmshModelGetBoundary(geom2, sa_size(geom2), - &bound2, &n2, - 1, - 0, - 0, - &ierr); - - gmshModelOccIntersect(bound1, n1, - bound2, n2, - &tagout, &tagoutn, - &map, &mapn, &mapnn, - -1, - NODELETE, - NODELETE, - &ierr); - } - - if (*out) sa_release(*out); *out = NULL; - for (i=0; i<(int)tagoutn; ++i){ - sa_push(*out, tagout[i]); + if(!geometry) { + res = RES_BAD_ARG; + goto error; } -exit: - if (tagout) free(tagout); - if (mapn) free(mapn); - if (map) free(map); - return res; -error: - goto exit; -} + ERR(check_device(FUNC_NAME)); + sz = geometry->gmsh_dimTags_n; + data = geometry->gmsh_dimTags; + ASSERT(sz > 0 && sz % 2 == 0); -res_T -scad_fragment -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove) -{ - res_T res = RES_OK; - int ierr, i; - int* tagout; - int** map = NULL; - size_t tagoutn, *mapn, mapnn; - - gmshModelOccFragment(geom1, sa_size(geom1), - geom2, sa_size(geom2), - &tagout, &tagoutn, - &map, &mapn, &mapnn, - -1, - remove, - remove, - &ierr); - - if (ierr != 0 ) {fprintf(stderr,"Fragment not possible !\n"); goto error;} - - if (*out) sa_release(*out); *out = NULL; - for (i=0; i<(int)tagoutn; ++i){ - sa_push(*out, tagout[i]); + allocator = dev->allocator; + str_init(allocator, &filename); + str_initialized = 1; + if(prefix) { + ERR(str_set(&filename, prefix)); + } else { + if(str_len(&geometry->name) == 0) { + res = RES_BAD_ARG; + goto error; + } + ERR(str_copy(&filename, &geometry->name)); + } + ERR(str_append(&filename, ".stl")); + + dim_ctrl = data[0]; + if(dim_ctrl == 3) { /* geometry is 3D */ + ERR(scad_synchronize()); + ERR(gmsh_err_to_res_T(ierr)); + + /* TODO: use options to set inward/outward orientation??? + * Is it even needed???? */ + for(i = 0; i < sz/2; i++) { + ASSERT(data[2*i] == dim_ctrl); /* Only 3D tags */ + gmshModelMeshSetOutwardOrientation(data[2*i+1], &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + if (reverse) gmshModelMeshReverse(data, sz, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + /* Get tags to be written (boundary of geometry) */ + gmshModelGetBoundary(data, sz, &faces_dimTags, &faces_dimTags_n, 1, 0, 0, &ierr); + dimTags_to_free = faces_dimTags; + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(faces_dimTags_n % 2 == 0); + } else { /* Geometry is 2D */ + /* Tags to be written are the tags of the 2D object itself */ + faces_dimTags_n = sz; + faces_dimTags = data; + for(i = 0; i < sz/2; i++) { + ASSERT(data[2*i] == dim_ctrl); /* Only 3D tags */ + } + if (reverse) gmshModelMeshReverse(data, sz, &ierr); + ERR(gmsh_err_to_res_T(ierr)); } -exit: - if (tagout) free(tagout); - if (mapn) free(mapn); - if (map) free(map); - return res; -error: - goto exit; -} + /* Allocate room for arrays */ + nodeTags = MEM_CALLOC(allocator, faces_dimTags_n/2, sizeof(*nodeTags)); + nodeTags_n = MEM_CALLOC(allocator, faces_dimTags_n/2, sizeof(*nodeTags_n)); + coord = MEM_CALLOC(allocator, faces_dimTags_n/2, sizeof(*coord)); + coord_n = MEM_CALLOC(allocator, faces_dimTags_n/2, sizeof(*coord_n)); + pCoord = MEM_CALLOC(allocator, faces_dimTags_n/2, sizeof(*pCoord)); + pCoord_n = MEM_CALLOC(allocator, faces_dimTags_n/2, sizeof(*pCoord_n)); + if(!nodeTags || !nodeTags_n || !coord || !coord_n || !pCoord || !pCoord_n) { + res = RES_MEM_ERR; + goto error; + } -res_T -scad_boundary -(const scad_geom_T geom1, scad_geom_T* out) -{ - res_T res = RES_OK; - int ierr,i; - int* b; - size_t n; - - gmshModelGetBoundary(geom1, sa_size(geom1), - &b, &n, - 1, - 0, - 0, - &ierr); - - if (ierr != 0 ) {fprintf(stderr,"Get boundary not possible !\n"); goto error;} - - if (*out) sa_release(*out); *out = NULL; - for (i=0; i<(int)n; ++i){ - sa_push(*out, b[i]); + tcount = 0; + for(i = 0; i < faces_dimTags_n/2; i++) { + /* type = 2: 3-node triangles (see src/common/GmshDefines.h) */ + gmshModelMeshGetNodesByElementType(2, nodeTags+i, nodeTags_n+i, + coord+i, coord_n+i, pCoord+i, pCoord_n+i, faces_dimTags[2*i+1], 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(nodeTags_n[i] % 3 == 0); + ASSERT(coord_n[i] == nodeTags_n[i] * 3); + tcount += coord_n[i]; } + tcount /= 9; + ASSERT(tcount <= UINT_MAX); + + ERR(write_stl(str_cget(&filename), (unsigned)tcount, coord, coord_n, binary)); exit: - if (b) free(b); + if(!allocator) { + /* Early error, nothing was allocated */ + return res; + } + if(str_initialized) str_release(&filename); + gmshFree(dimTags_to_free); + if(nodeTags) { + for(i = 0; i < faces_dimTags_n/2; i++) gmshFree(nodeTags[i]); + MEM_RM(allocator, nodeTags); + } + MEM_RM(allocator, nodeTags_n); + if(coord) { + for(i = 0; i < faces_dimTags_n/2; i++) gmshFree(coord[i]); + MEM_RM(allocator, coord); + } + MEM_RM(allocator, coord_n); + if(pCoord) { + for(i = 0; i < faces_dimTags_n/2; i++) gmshFree(pCoord[i]); + MEM_RM(allocator, pCoord); + } + MEM_RM(allocator, pCoord_n); return res; error: + if(dev) { + log_error(dev, "%s: could not export to STL -- %s\n", + FUNC_NAME, res_to_cstr(res)); + } goto exit; } res_T -scad_translate -(scad_geom_T geom, const double dxdydz[3]) +scad_stl_export_split + (struct scad_geometry* geometry, + const char* prefix, + const int binary) /* FIXME: unused as we use gmshWrite */ { + struct str filename_root; + struct str filename; + int* tagout = NULL; + size_t tagoutn, i; + int dimtag[2]; + int group; + int ierr = 0; + int* data; + size_t sz; + int str_initialized = 0; res_T res = RES_OK; - int ierr; - if (!geom || !dxdydz){ - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; + (void)binary; + + if(!geometry) { + res = RES_BAD_ARG; + goto error; } - gmshModelOccTranslate(geom, sa_size(geom), - dxdydz[0], - dxdydz[1], - dxdydz[2], - &ierr); + ERR(check_device(FUNC_NAME)); - if (ierr != 0 ) {fprintf(stderr,"Translation not possible !\n"); goto error;} + sz = geometry->gmsh_dimTags_n; + data = geometry->gmsh_dimTags; + ASSERT(sz % 2 == 0); -exit: - return res; -error: - res = ierr; - goto exit; -} - -res_T -scad_rotate -(scad_geom_T geom, const double pt[3], const double axis[3], const double angle) -{ - res_T res = RES_OK; - int ierr; - - if (!geom || !pt || !axis){ - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; + str_init(get_device()->allocator, &filename_root); + str_init(get_device()->allocator, &filename); + str_initialized = 1; + if(prefix) { + ERR(str_set(&filename_root, prefix)); + } else { + if(str_len(&geometry->name) == 0) { + res = RES_BAD_ARG; + goto error; + } + ERR(str_copy(&filename_root, &geometry->name)); } + ERR(str_append(&filename_root, "_")); + + if(data[0] == 2) { + FOR_EACH(i, 0, sz/2) { + ASSERT(data[2*i] == 2); + /* When upgrading to gmsh 4.11 the additional arg. name cannot be NULL; + * use "" instead */ + group = gmshModelAddPhysicalGroup(2, &data[2*i+1], 1, -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ERR(str_copy(&filename, &filename_root)); + ERR(str_append_printf(&filename, "%lu.stl", (unsigned long)i)); + gmshWrite(str_cget(&filename), &ierr); + ERR(gmsh_err_to_res_T(ierr)); - gmshModelOccRotate(geom, sa_size(geom), - pt[0], - pt[1], - pt[2], - axis[0], - axis[1], - axis[2], - angle, - &ierr); - - if (ierr != 0 ) {fprintf(stderr,"Rotation not possible !\n"); goto error;} + dimtag[0]=2; + dimtag[1]=group; + gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + } else { + FOR_EACH(i, 0, sz/2) { + ASSERT(data[2*i] == 3); + gmshModelMeshSetOutwardOrientation(data[2*i+1], &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } -exit: - return res; -error: - res = ierr; - goto exit; -} + gmshModelGetBoundary(data, sz, &tagout, &tagoutn, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); -res_T -scad_extrude(const scad_geom_T geom, const double dxdydz[3], scad_geom_T* out) -{ - res_T res = RES_OK; - int ierr, i; - int* tagout; - size_t tagoutn; + FOR_EACH(i, 0, tagoutn/2){ + /* When upgrading to gmsh 4.11 the additional arg. name cannot be NULL; + * use "" instead */ + group = gmshModelAddPhysicalGroup(2, tagout + 2*i+1, 1, -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ERR(str_copy(&filename, &filename_root)); + ERR(str_append_printf(&filename, "%lu.stl", (unsigned long)i)); + gmshWrite(str_cget(&filename), &ierr); + ERR(gmsh_err_to_res_T(ierr)); - if (!geom || !dxdydz){ - fprintf(stderr,"Invalid data !\n"); - return RES_BAD_ARG; - } - - gmshModelOccExtrude(geom, sa_size(geom), - dxdydz[0], - dxdydz[1], - dxdydz[2], - &tagout, &tagoutn, - NULL, 0, - NULL, 0, - 0, - &ierr); - - if (*out) sa_release(*out); *out = NULL; - for (i=0; i<(int)tagoutn; ++i){ - sa_push(*out, tagout[i]); + dimtag[0]=2; + dimtag[1]=group; + gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } } - if (ierr != 0 ) {fprintf(stderr,"Extrusion not possible !\n"); goto error;} - exit: + if(str_initialized) { + str_release(&filename_root); + str_release(&filename); + } + gmshFree(tagout); return res; error: - res = ierr; goto exit; } -res_T -scad_conformal_mesh(void) -{ - int ierr = 0; - int* dimTags; - size_t dimTags_n; - - gmshModelOccSynchronize(&ierr); - gmshModelOccGetEntities(&dimTags, &dimTags_n, 3, &ierr); - if ( ierr == 0 && dimTags_n > 2) gmshModelOccRemoveAllDuplicates(&ierr); - gmshModelOccSynchronize(&ierr); - gmshModelMeshGenerate(2, &ierr); - - return ierr; -} - - res_T -scad_stl_export(const scad_geom_T geom, char *prefix) +scad_scene_mesh + (void) { + int ierr = 0; res_T res = RES_OK; - int ierr; - int i; - int* tagout = NULL; - int* tags = NULL; - size_t tagoutn; - int group, dimtag[2]; - struct str filename; - str_init(NULL, &filename); - str_set(&filename, prefix); - str_append(&filename, ".stl"); + ERR(check_device(FUNC_NAME)); - if (geom[0] == 2) { - tagoutn = sa_size(geom); - for(i=0; i<(int)tagoutn/2; ++i){ - sa_push(tags, geom[2*i + 1]); - } - - group = gmshModelAddPhysicalGroup(2, tags, tagoutn/2, - -1, - &ierr); - if (ierr !=0) goto error; - - } else { - - for (i=0; i<(int)sa_size(geom)/2; ++i) - { - gmshModelMeshSetOutwardOrientation(geom[2*i+1], &ierr); - } - - gmshModelGetBoundary(geom, sa_size(geom), - &tagout, &tagoutn, - 1, - 0, - 0, - &ierr); - - if (ierr !=0) goto error; - - for(i=0; i<(int)tagoutn/2; ++i){ - sa_push(tags, tagout[2*i + 1]); - } - - group = gmshModelAddPhysicalGroup(2, tags, tagoutn/2, - -1, - &ierr); - if (ierr !=0) goto error; - } - - gmshWrite(str_get(&filename), &ierr); - if (ierr !=0) goto error; - - dimtag[0]=2; - dimtag[1]=group; - gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); - if (ierr !=0) goto error; + gmshModelMeshGenerate(2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); exit: - str_release(&filename); - if (tagout) free(tagout); - if (tags) sa_release(tags); return res; error: goto exit; } res_T -scad_stl_export_split(const scad_geom_T geom, char *prefix) +scad_scene_partition + (void) { + int ierr = 0; res_T res = RES_OK; - int ierr; - int i; - int* tagout = NULL; - int* tags = NULL; - size_t tagoutn; - int group, dimtag[2]; - - if (geom[0] == 2) { - tagoutn = sa_size(geom); - for(i=0; i<(int)tagoutn/2; ++i){ - struct str filename; - char num[32]; - - group = gmshModelAddPhysicalGroup(2, &geom[2*i+1], 1, - -1, - &ierr); - if (ierr !=0) goto error; - - str_init(NULL, &filename); - str_set(&filename, prefix); - str_append_char(&filename, '_'); - sprintf(num, "%d", i); - str_append(&filename, num); - str_append(&filename, ".stl"); - gmshWrite(str_get(&filename), &ierr); - if (ierr !=0) {str_release(&filename); goto error;} - dimtag[0]=2; - dimtag[1]=group; - gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); - if (ierr !=0) {str_release(&filename); goto error;} - - str_release(&filename); - } - - } else { - - for (i=0; i<(int)sa_size(geom)/2; ++i) - { - gmshModelMeshSetOutwardOrientation(geom[2*i+1], &ierr); - } - - gmshModelGetBoundary(geom, sa_size(geom), - &tagout, &tagoutn, - 1, - 0, - 0, - &ierr); - - if (ierr !=0) goto error; - - for(i=0; i<(int)tagoutn/2; ++i){ - struct str filename; - char num[32]; - - - group = gmshModelAddPhysicalGroup(2, &tagout[2*i+1], 1, - -1, - &ierr); - if (ierr !=0) goto error; - - str_init(NULL, &filename); - str_set(&filename, prefix); - str_append_char(&filename, '_'); - sprintf(num, "%d", i); - str_append(&filename, num); - str_append(&filename, ".stl"); - gmshWrite(str_get(&filename), &ierr); - if (ierr !=0) {str_release(&filename); goto error;} + ERR(check_device(FUNC_NAME)); - dimtag[0]=2; - dimtag[1]=group; - gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); - if (ierr !=0) {str_release(&filename); goto error;} - - str_release(&filename); - } - } + gmshModelOccRemoveAllDuplicates(&ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshModelOccSynchronize(&ierr); + ERR(gmsh_err_to_res_T(ierr)); exit: - if (tagout) free(tagout); - if (tags) sa_release(tags); return res; error: goto exit; diff --git a/src/scad.h b/src/scad.h @@ -12,121 +12,437 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - #ifndef SCAD_H #define SCAD_H -#include <stdio.h> -#include <stdlib.h> -#include <gmshc.h> #include <rsys/rsys.h> -#include <rsys/stretchy_array.h> -#include <rsys/math.h> -#include <string.h> - -/* wrapping of dimTags gmsh description */ -typedef int* scad_geom_T; -#define SCAD_GEOM_NULL_ NULL -static const scad_geom_T SCAD_GEOM_NULL = SCAD_GEOM_NULL_; - -/* remove the handler but not the geomtry */ -/* to remove geometry, use scad_geom_remove */ -static FINLINE -res_T scad_geom_release(scad_geom_T geom) -{ - sa_release(geom); - return RES_OK; -} - -/*****************************************************************************/ -/*****************************************************************************/ -/*****************************************************************************/ -res_T -scad_init(void); - -res_T -scad_release(void); - -res_T -scad_synchronize(void); - -res_T -scad_run_ui(void); - -res_T -scad_addrectangle -(const double xyz[3], const double dxdy[2], scad_geom_T* geom); - -res_T -scad_adddisk -(const double xyz[3], const double rad, scad_geom_T* geom); - -res_T -scad_addpolygon -(const double* x, const double* y, const double z, const int n, scad_geom_T* geom); - - -res_T -scad_addbox -(const double xyz[3], const double dxdydz[3], scad_geom_T* geom); - -res_T -scad_addcylinder -(const double xyz[3], const double axis[3], const double rad, const double angle, scad_geom_T* geom); - -res_T -scad_addsphere -(const double xyz[3], const double rad, scad_geom_T* geom); -res_T -scad_remove(scad_geom_T geom); - -res_T -scad_concat(scad_geom_T* geom1, const scad_geom_T geom2); - -#define DELETE 1 -#define NODELETE 0 -res_T -scad_fuse -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove); - -res_T -scad_cut -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove); - -res_T -scad_intersect -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove); - -res_T -scad_fragment -(const scad_geom_T geom1, const scad_geom_T geom2, scad_geom_T* out, const int remove); - -res_T -scad_boundary -(const scad_geom_T geom1, scad_geom_T* out); - -res_T -scad_translate -(scad_geom_T geom, const double dxdydz[3]); - -res_T -scad_rotate -(scad_geom_T geom, const double pt[3], const double axis[3], const double angle); - -res_T -scad_extrude(const scad_geom_T geom, const double dxdydz[3], scad_geom_T* out); - -res_T -scad_conformal_mesh(void); - -res_T -scad_stl_export(const scad_geom_T geom, char *prefix); - -res_T -scad_stl_export_split(const scad_geom_T geom, char *prefix); -/*****************************************************************************/ -/*****************************************************************************/ -/*****************************************************************************/ -#endif /* SCAD_H */ +/* Library symbol management */ +#if defined(SCAD_SHARED_BUILD) /* Build shared library */ + #define SCAD_API extern EXPORT_SYM +#elif defined(SCAD_STATIC) /* Use/build static library */ + #define SCAD_API extern LOCAL_SYM +#else /* Use shared library */ + #define SCAD_API extern IMPORT_SYM +#endif + +/* Helper macro that asserts if the invocation of the scad function `Func' + * returns an error. One should use this macro on scad function calls for which + * no explicit error checking is performed */ +#ifndef NDEBUG + #define SCAD(Func) ASSERT(scad_ ## Func == RES_OK) +#else + #define SCAD(Func) scad_ ## Func +#endif + +/* Forward declarations */ +struct mem_allocator; +struct logger; +struct str; + +/* Forward declaration of scad opaque data types */ +struct scad_geometry; /* Wrapping of dimTags gmsh description */ + +/* Verbosity levels */ +enum scad_verbosity_levels { + scad_verbosSCAD_ity_fatal_errors = 0, + Scad_verbosity_errors = 1, + Scad_verbosity_warnings = 2, + Scad_verbosity_direct = 3, + Scad_verbosity_information = 4, + Scad_verbosity_status = 5, + Scad_verbosity_debug = 99 +}; + +/* Mesh algorithms */ +enum scad_mesh_algorithm { + Scad_meshAdapt = 1, + Scad_Delaunay = 5, + Scad_frontal_Delaunay = 6 +}; + +enum scad_sizes_extend_from_boundary { + Scad_never = 0, + Scad_surfaces_and_volumes = 1, + Scad_surfaces_and_volumes_smallest = 2, + Scad_surfaces_only = -2, + Scad_volumes_only = -3 +}; + +enum scad_stl_solids { + Scad_single_solid = 0, + Scad_one_solid_per_surface = 1, + Scad_one_solid_per_physical_surface = 2 +}; + +enum scad_log_refcounting { + Scad_log_none, + Scad_log_only_undeleted, + Scad_log_all +}; + +/* A type to specify options for the gmsh library */ +struct scad_options { + struct { + enum scad_mesh_algorithm Algorithm; + enum scad_sizes_extend_from_boundary MeshSizeExtendFromBoundary; + double MeshSizeFactor; + double MeshSizeFromCurvature; + int MeshSizeFromPoints; + double MeshSizeMax; + double MeshSizeMin; + double Smoothing; + enum scad_stl_solids StlOneSolidPerSurface; + } Mesh; + struct { + enum scad_verbosity_levels Verbosity; + int ExpertMode; /* Avoid user interaction on some option combinations */ + } General; + struct { + int OCCParallel; + } Geometry; + struct { + int Step; /* Run UI when entering any scad API function; requires a FLTK-enabled gmsh build */ + int SynchronizeOnRunUI; + enum scad_log_refcounting LogOpenCascadeTagsRefCounting; + int DebugOpenCascadeSync; /* Systematic call to synchronize; if results change there is a sync bug in star-cad! */ + } Misc; +}; + +#define SCAD_DEFAULT_OPTIONS__ \ + { { Scad_frontal_Delaunay, Scad_surfaces_and_volumes, 1, 36, 1, 1e+22, 0, \ + 1, Scad_one_solid_per_physical_surface }, \ + { Scad_verbosity_errors, 1 }, \ + { 1 }, \ + { 0, Scad_log_none, 0, 0 } \ + } + +static const struct scad_options SCAD_DEFAULT_OPTIONS = SCAD_DEFAULT_OPTIONS__; + +BEGIN_DECLS + +/******************************************************************************* + * Init and Finalize calls. + * All other API calls must be enclosed between Init and Finalize. + ******************************************************************************/ +SCAD_API res_T +scad_initialize + (struct logger* logger, /* May be NULL <=> use default logger */ + struct mem_allocator* allocator, /* May be NULL <=> use default allocator */ + const int verbose); /* Define the level of verbosity: + 0 = no logs, + 1 = errors only, + 2 = errors and warnings, + 3 = errors, warnings, and informative messages */ + +SCAD_API res_T +scad_finalize + (void); + +SCAD_API res_T +scad_set_options + (const struct scad_options* options); /* May be NULL: set default */ + +/* Explicitly synchronize the current state with regard to recent geometry changes. + * Synchronize calls should be automatically triggered when needed. + * Only provided as a way to check for auto-synchronize bugs! */ +SCAD_API res_T +scad_synchronize + (void); + +/******************************************************************************* + * Geometry API - A geometry is a primitive, a group of primitives, or the + * result of an operation on geometries. + * If provided, names must be unique. + ******************************************************************************/ + +SCAD_API res_T +scad_geometry_delete + (struct scad_geometry* geom); + +SCAD_API res_T +scad_scene_clear + (void); + +/* Get the number of components of the geometry `geom' */ +SCAD_API res_T +scad_geometry_get_count + (const struct scad_geometry* geom, + size_t* count); + +/* Get a pointer to `geom's name. + * Note that this reference is only valid during the lifetime of `geom' (don't + * use name after deleting `geom') */ +SCAD_API res_T +scad_geometry_get_name + (const struct scad_geometry* geom, + const char** name); + +/* Swap names of `geom1' and `geom2' */ +SCAD_API res_T +scad_geometry_swap_names + (struct scad_geometry* geom1, + struct scad_geometry* geom2); + +/* Get the `mass' of the geometry `geom'. It means area for a 2D geometry and + * volume for a 3D geometry. */ +SCAD_API res_T +scad_geometry_get_mass + (struct scad_geometry* geom, + double* mass); + +/* Get the center of mass of the various components of the geometry. + * Note that `center' must be allocated be the caller with enough room for (at + * least) 3 times the count of geom (scad_geometry_ge_count) doubles */ +SCAD_API res_T +scad_geometry_get_centerofmass + (struct scad_geometry* geom, + double* center); + +/* Add a rectangle to the scene, defined by a point `xyz' and + * `dxdy' the extents along the x-, y-axes. */ +SCAD_API res_T +scad_add_rectangle + (const char* name, /* Can be NULL */ + const double xyz[3], + const double dxdy[2], + struct scad_geometry** rectangle); + +/* Add a disk in (xy) plane to the scene, defined by a the center `xyz' and + * `radius'. */ +SCAD_API res_T +scad_add_disk + (const char* name, /* Can be NULL */ + const double xyz[3], + const double radius, + struct scad_geometry** disk); + +/* Add a polygonal surface in (xy) plane to the scene at elevation z */ +SCAD_API res_T +scad_add_polygon + (const char* name, /* Can be NULL */ + void (*get_position)(const size_t ivert, double pos[2], void* data), + void* data, /* Custom data; can be NULL if get_position don't use it */ + const double z, + const size_t count, /* size of x and y arrays */ + struct scad_geometry** polygon); + +/* Add a parallelepipedic box to the scene, defined by a point `xyz' and + * `dxdydz' the extents along the x-, y- and z-axes. */ +SCAD_API res_T +scad_add_box + (const char* name, /* Can be NULL */ + const double xyz[3], + const double dxdydz[3], + struct scad_geometry** box); + +/* Add a cylinder to the scene, defined by the center `xyz' of its first + * circular face, the vector `axis' defining its axis and its radius `rad'. The + * `angle' argument defines the angular opening (from 0 to 2*Pi). */ +SCAD_API res_T +scad_add_cylinder + (const char* name, /* Can be NULL */ + const double xyz[3], + const double axis[3], + const double radius, + const double angle, + struct scad_geometry** cylinder); + +/* Add a sphere of center `xyz' and radius `rad' to the scene. */ +SCAD_API res_T +scad_add_sphere + (const char* name, /* Can be NULL */ + const double xyz[3], + const double radius, + struct scad_geometry** sphere); + +/* Compute the boolean union (the fusion) of the geometries in `geometries' and + * `tools'. */ +SCAD_API res_T +scad_fuse_geometries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry); + +/* Compute the boolean difference between the geometries in `geometries' and + * `tools'. */ +SCAD_API res_T +scad_cut_geometries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry); + +/* Compute the boolean intersection (the common parts) of the geometries + * in `geometries' and `tools'. */ +SCAD_API res_T +scad_intersect_geometries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry); + +/* compute boundary intersection (the common part) of the geometries in + * `geometries' and `tools'. */ +SCAD_API res_T +scad_geometries_common_boundaries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry); + +/* Compute the boolean fragments (general fuse) resulting from the + * intersection of the geometries in `geometries', making all interfaces + * conformal. + * If overlapping is allowed `out_geometries' is constructed and the new + * geometries are created unnamed; if overlapping is not allowed + * `out_geometries' remains unchanged and can be NULL. + * When applied to geometries of different dimensions, the lower dimensional + * geometries will be automatically embedded in the higher dimensional + * geometries if they are not on their boundary. */ +SCAD_API res_T +scad_geometries_partition + (struct scad_geometry** geometries, + const size_t geometries_count, + const int allow_overlapping, + struct scad_geometry** out_geometries); /* Can be NULL if overlapping disallowed */ + +/* Partition the whole scene. + * Overlapping is not detected and newly created objects are not returned. */ +SCAD_API res_T +scad_scene_partition + (void); + +SCAD_API res_T +scad_fragment_geometries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry); + +/* Get the boundary of the geometry `geom'. */ +SCAD_API res_T +scad_geometry_boundary + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** out_boundary); + +/* copy the geometry `geom'. */ +SCAD_API res_T +scad_geometry_copy + (const struct scad_geometry* geom, + const char* name, /* Can be NULL */ + struct scad_geometry** out_copy); + +/* Change the name of geometry `geom'. */ +SCAD_API res_T +scad_geometry_rename + (struct scad_geometry* geom, + const char* name); /* Can be NULL */ + +/* Translate the geometry `geom' along (`dx', `dy', `dz'). */ +SCAD_API res_T +scad_geometry_translate + (struct scad_geometry* geom, + const double dxdydz[3]); + +/* Rotate the geometry `geom' by `angle' radians around the axis of revolution + * defined by the point `pt' and the direction `dir'. */ +SCAD_API res_T +scad_geometry_rotate + (struct scad_geometry* geom, + const double pt[3], + const double dir[3], + const double angle); + +/* Extrude the geometry `geom' using a translation along (`dx', `dy', `dz').*/ +SCAD_API res_T +scad_geometry_extrude + (const struct scad_geometry* geom, + const char* name, /* Can be NULL */ + const double dxdydz[3], + struct scad_geometry** out_geometry); + +/* Return a list of geometries which form the geometry geom */ +SCAD_API res_T +scad_geometry_explode + (const struct scad_geometry* geom, + const char* prefix_name, /* Can be NULL */ + struct scad_geometry*** out_geometry, + size_t* out_geometry_n); + +/* Import a step model (`filename'). The imported geometries are recorded in + * `out_geometry'. + * Note that `out_geometry' is allocated using the allocator provided when + * initializing star-cad and should be freed accordingly. */ +SCAD_API res_T +scad_step_import + (const char* filename, /* name of step file */ + const char* name, /* Can be NULL */ + struct scad_geometry*** out_geometry, + size_t* out_geometry_n); + +/* Export the geometry `geom' to an STL file. + * If `prefix' is provided it is used to name the file (just adding .stl), + * otherwise the geometry name is used instead (and it is an error if neither + * prefix nor the geometry name are defined). The file format is either binary + * or ascii, depending on the value of the `binary' argument. */ +SCAD_API res_T +scad_stl_export + (struct scad_geometry* geom, + const char* prefix, /* Can be NULL if geometry was named: use geometry name */ + const int reverse, /* set `1' to reverse mesh */ + const int binary); /* File format */ + +SCAD_API res_T +scad_stl_export_split + (struct scad_geometry* geom, + const char* prefix, /* Can be NULL if geometry was named: use geometry name */ + const int binary); /* File format */ + + +SCAD_API res_T +scad_scene_write + (const char* name); + +SCAD_API res_T /* FIXME remove this */ +scad_run_ui + (void); + +SCAD_API res_T +scad_scene_mesh + (void); + +SCAD_API res_T +scad_geometry_normal + (struct scad_geometry* geom, + double p[3], + double N[3], + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry); + +/* Scale the geometry by * factors `scale' along the three coordinate axes; + * use `center', as the center of the homothetic transformation. */ +SCAD_API res_T +scad_geometry_dilate + (struct scad_geometry* geom, + double center[3], + double scale[3]); + + +END_DECLS + +#endif /* SCAD_H */ diff --git a/src/scad_c.h b/src/scad_c.h @@ -0,0 +1,36 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#ifndef SCAD_C_H +#define SCAD_C_H + +#include <rsys/rsys.h> + +#define ERR(Expr) if((res = (Expr)) != RES_OK) goto error; else (void)0 + +static INLINE res_T +gmsh_err_to_res_T(const int ierr) +{ + res_T res = RES_OK; + + switch(ierr) { + /* TODO identify more precisely the gmsh errors */ + case 0: res = RES_OK; break; + default: res = RES_UNKNOWN_ERR; break; + } + return res; +} + +#endif diff --git a/src/scad_device.c b/src/scad_device.c @@ -0,0 +1,434 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#include "rsys/str.h" +#include "scad.h" +#include "scad_c.h" +#include "scad_device.h" +#include "scad_geometry.h" + +#include <rsys/logger.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> +#include <rsys/cstr.h> + +#include <gmsh/gmshc.h> +#include <rsys/rsys.h> + +/******************************************************************************* + * Local functions + ******************************************************************************/ +static void +device_release(struct scad_device* dev) +{ + struct htable_geometries tmp; + struct htable_geometries_iterator it, end; + int log, empty; + enum scad_log_refcounting option; + enum log_type log_type; + + ASSERT(dev); + + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + empty = htable_geometries_is_empty(&dev->allgeom); + log_type = empty ? LOG_OUTPUT : LOG_WARNING; + log = (option == Scad_log_all) || (!empty && option == Scad_log_only_undeleted); + + /* Duplicate the htable we iterate on as dev->allgeom will be altered during + * the process (through calls to geometry_release) */ + htable_geometries_init(dev->allocator, &tmp); + CHK(RES_OK == htable_geometries_copy(&tmp, &dev->allgeom)); + htable_geometries_begin(&tmp, &it); + htable_geometries_end(&tmp, &end); + if(log && empty) { + logger_print(dev->logger, log_type, "No scad geometry.\n"); + } + while(!htable_geometries_iterator_eq(&it, &end)) { + struct scad_geometry* geom = *htable_geometries_iterator_key_get(&it); + CHK(RES_OK == geometry_release(log, log_type, geom)); + htable_geometries_iterator_next(&it); + } + htable_names_release(&dev->geometry_names); + htable_tags2geom_release(&dev->tags2geom[0]); + htable_tags2geom_release(&dev->tags2geom[1]); + htable_geometries_release(&dev->allgeom); + if(log) { + logger_print(dev->logger, log_type, "End finalizing scad.\n"); + } + MEM_RM(dev->allocator, dev); + htable_geometries_release(&tmp); +} + +void +log_error(struct scad_device* dev, const char* msg, ...) +{ + va_list vargs_list; + ASSERT(dev && msg); + if(dev->verbose < 1) return; + va_start(vargs_list, msg); + log_msg(dev, LOG_ERROR, msg, vargs_list); + va_end(vargs_list); +} + +void +log_warning(struct scad_device* dev, const char* msg, ...) +{ + va_list vargs_list; + ASSERT(dev && msg); + if(dev->verbose < 2) return; + va_start(vargs_list, msg); + log_msg(dev, LOG_WARNING, msg, vargs_list); + va_end(vargs_list); +} + + +void +log_message(struct scad_device* dev, const char* msg, ...) +{ + va_list vargs_list; + ASSERT(dev && msg); + if(dev->verbose < 3) return; + va_start(vargs_list, msg); + log_msg(dev, LOG_OUTPUT, msg, vargs_list); + va_end(vargs_list); +} + +/******************************************************************************* + * The unique device in scad-cad + ******************************************************************************/ +static struct scad_device* g_device = NULL; + +/******************************************************************************* + * Exported scad_device functions + ******************************************************************************/ +res_T +check_device + (const char* function_name) +{ + res_T res = RES_OK; + ASSERT(function_name); + + if(!g_device) { + /* No logger available for a message */ + fprintf(stderr, + "%s: cannot call API functions if star-cad is not initialized.\n", + function_name); + res = RES_BAD_ARG; + goto error; + } + + if(get_device()->need_synchro) { + ERR(scad_synchronize()); + } + + if(g_device->options.Misc.Step) { + ERR(scad_run_ui()); + } + +exit: + return res; +error: + goto exit; +} + +struct scad_device* +get_device + (void) +{ + return g_device; +} + +res_T +device_register_tags + (struct scad_geometry* geom) +{ + res_T res = RES_OK; + int* dimTags; + size_t count, i; + struct scad_device* dev = get_device(); + int log = (dev->options.Misc.LogOpenCascadeTagsRefCounting == Scad_log_all); + + ASSERT(geom); + + dimTags = geom->gmsh_dimTags; + count = geom->gmsh_dimTags_n; + if(log) { + if(str_is_empty(&geom->name)) { + log_message(dev, "Registering tags for unnamed geometry %p.\n", + (void*)geom); + } else { + log_message(dev, "Registering tags for geometry '%s'.\n", + str_cget(&geom->name)); + } + } + + for(i = 0; i < count; i += 2) { + int dim = dimTags[i]; + int tag = dimTags[i+1]; + struct htable_tags2geom* t2g; + struct htable_geometries* geoms; + char one = 1; + CHK(dim == 2 || dim == 3); /* other dims not managed yet */ + /* Add geom to the geometries that use tag@dim */ + t2g = g_device->tags2geom + (dim-2); + geoms = htable_tags2geom_find(t2g, &tag); + if(!geoms) { + /* First geom using this tag: create the table */ + struct htable_geometries g; + htable_geometries_init(g_device->allocator, &g); + ERR(htable_tags2geom_set(t2g, &tag, &g)); + geoms = htable_tags2geom_find(t2g, &tag); + ASSERT(geoms); + if(log) { + log_message(dev, "New dim %d tag %d (count set to 1).\n", dim, tag); + } + } else { + if(log) { + size_t n = htable_geometries_size_get(geoms); + if(n > 0) { + log_message(dev, "Dim %d tag %d (count increased to %lu).\n", + dim, tag, n+1); + } else { + log_message(dev, "Reuse dim %d tag %d (count set to 1).\n", dim, tag); + } + } + } + ASSERT(!htable_geometries_find(geoms, &geom)); + ERR(htable_geometries_set(geoms, &geom, &one)); + ASSERT(htable_geometries_size_get(geoms) >= 1); + } + +end: + return res; +error: + goto end; +} + +res_T +device_unregister_tags + (const int log, + const enum log_type log_type, + struct scad_geometry* geom) +{ + res_T res = RES_OK; + int* dimTags; + size_t count, i; + struct scad_device* dev = get_device(); + + ASSERT(geom); + + dimTags = geom->gmsh_dimTags; + count = geom->gmsh_dimTags_n; + + if(log) { + if(str_is_empty(&geom->name)) { + logger_print(dev->logger, log_type, + "Unregistering tags for unnamed geometry %p.\n", (void*)geom); + } else { + logger_print(dev->logger, log_type, + "Unregistering tags for geometry '%s'.\n", str_cget(&geom->name)); + } + } + + for(i = 0; i < count; i += 2) { + int dim = dimTags[i]; + int tag = dimTags[i+1]; + int ierr; + struct htable_tags2geom* t2g; + struct htable_geometries* geoms; + size_t n; + CHK(dim == 2 || dim == 3); /* other dims not managed yet */ + t2g = g_device->tags2geom + (dim-2); + geoms = htable_tags2geom_find(t2g, &tag); + n = htable_geometries_erase(geoms, &geom); + ASSERT(geoms && n == 1); (void)n; + n = htable_geometries_size_get(geoms); + if(n > 0) { + if(log) { + logger_print(dev->logger, log_type, + "Dim %d tag %d (count decreased to %lu).\n", dim, tag, (unsigned long)n); + } + continue; + } + /* The gmsh geometry with tag 'tag' is not in use anymore: release it */ + if(log) { + logger_print(dev->logger, log_type, "Dim %d tag %d removed.\n", dim, tag); + } + + gmshModelOccRemove(dimTags+i, 2, 1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + +end: + return res; +error: + goto end; +} + +/******************************************************************************* + * API scad_device functions + ******************************************************************************/ +res_T +scad_initialize + (struct logger* logger, + struct mem_allocator* mem_allocator, + const int verbose) +{ + struct mem_allocator* allocator; + res_T res = RES_OK; + int ierr; + + if(g_device != NULL) { + log_error(g_device, "scad-star is already initialized.\n"); + res = RES_BAD_ARG; + goto error; + } + + if(0 > verbose || verbose > 3) { + res = RES_BAD_ARG; + goto error; + } + + gmshInitialize(0, NULL, 1, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + allocator = mem_allocator ? mem_allocator : &mem_default_allocator; + g_device + = (struct scad_device*)MEM_CALLOC(allocator, 1, sizeof(struct scad_device)); + if(!g_device) { + res = RES_MEM_ERR; + goto error; + } + g_device->logger = logger ? logger : LOGGER_DEFAULT; + g_device->allocator = allocator; + g_device->need_synchro = g_device->options.Misc.DebugOpenCascadeSync; + g_device->verbose = verbose; + htable_names_init(allocator, &g_device->geometry_names); + htable_geometries_init(allocator, &g_device->allgeom); + htable_tags2geom_init(allocator, &g_device->tags2geom[0]); + htable_tags2geom_init(allocator, &g_device->tags2geom[1]); + /* Init to default */ + scad_set_options(NULL); + +exit: + return res; +error: + if(g_device) { + device_release(g_device); + g_device = NULL; + } + goto exit; +} + +res_T +scad_finalize + (void) +{ + res_T res = RES_OK; + int ierr; + struct scad_device* dev = get_device(); + int log, empty; + enum scad_log_refcounting option; + enum log_type log_type; + + ERR(check_device(FUNC_NAME)); + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + + empty = htable_geometries_is_empty(&dev->allgeom); + log_type = empty ? LOG_OUTPUT : LOG_WARNING; + log = (option == Scad_log_all) || (!empty && option == Scad_log_only_undeleted); + if(log) { + logger_print(dev->logger, log_type, + "Finalizing scad; undeleted tags will be automatically unregistered.\n"); + } + + device_release(g_device); + g_device = NULL; + gmshFinalize(&ierr); + ERR(gmsh_err_to_res_T(ierr)); + +exit: + return res; +error: + goto exit; +} + +#define SET_GMSH_OPTION_INT(Option) \ + gmshOptionSetNumber((#Option), (actual_options->Option), &ierr);\ + if(ierr) {\ + log_error(dev, "Could not set option %s to %d.\n",\ + (#Option), (actual_options->Option));\ + res = RES_BAD_ARG;\ + goto error;\ + } + +#define SET_GMSH_OPTION_DOUBLE(Option) \ + gmshOptionSetNumber((#Option), (actual_options->Option), &ierr);\ + if(ierr) {\ + log_error(dev, "Could not set option %s to %g.\n",\ + (#Option), (actual_options->Option));\ + res = RES_BAD_ARG;\ + goto error;\ + } + +res_T +scad_set_options + (const struct scad_options* options) +{ + res_T res = RES_OK; + const struct scad_options* actual_options + = options ? options : &SCAD_DEFAULT_OPTIONS; + int ierr = 0; + struct scad_options keep; + struct scad_device* dev = NULL; + + ERR(check_device(FUNC_NAME)); + + dev = get_device(); + keep = dev->options; + + SET_GMSH_OPTION_INT(Mesh.Algorithm); + SET_GMSH_OPTION_INT(Mesh.MeshSizeExtendFromBoundary); + SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeFactor); + SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeFromCurvature); + SET_GMSH_OPTION_INT(Mesh.MeshSizeFromPoints); + SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeMax); + SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeMin); + SET_GMSH_OPTION_DOUBLE(Mesh.Smoothing); + SET_GMSH_OPTION_INT(Mesh.StlOneSolidPerSurface); + + SET_GMSH_OPTION_INT(General.Verbosity); + SET_GMSH_OPTION_INT(General.ExpertMode); + + SET_GMSH_OPTION_INT(Geometry.OCCParallel); + + if(options) { + /* Check non-gmsh option validity if user-provided */ + (void)actual_options->Misc.Step; /* int boolean: always OK */ + (void)actual_options->Misc.SynchronizeOnRunUI; /* int boolean: always OK */ + (void)actual_options->Misc.LogOpenCascadeTagsRefCounting; /* int boolean: always OK */ + (void)actual_options->Misc.DebugOpenCascadeSync; /* int boolean: always OK */ + } + + dev->options = *actual_options; + +exit: + return res; +error: + if(dev) dev->options = keep; + goto exit; +} + +#undef SET_GMSH_OPTION_DOUBLE diff --git a/src/scad_device.h b/src/scad_device.h @@ -0,0 +1,158 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#ifndef SCAD_DEVICE_H +#define SCAD_DEVICE_H + +#include "scad.h" +#include "scad_geometry.h" + +#include <rsys/rsys.h> +#include <rsys/ref_count.h> +#include <rsys/logger.h> +#include <rsys/hash_table.h> +#include <rsys/str.h> + +static INLINE char +eq_str(const struct str* a, const struct str* b) +{ + return !strcmp(str_cget(a), str_cget(b)); +} + +static INLINE size_t +hash_str(const struct str* a) +{ + return hash_fnv32(str_cget(a), str_len(a)); +} + +#define HTABLE_NAME names +#define HTABLE_DATA struct scad_geometry* +#define HTABLE_KEY struct str +#define HTABLE_KEY_FUNCTOR_INIT str_init +#define HTABLE_KEY_FUNCTOR_RELEASE str_release +#define HTABLE_KEY_FUNCTOR_COPY str_copy +#define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE str_copy_and_release +#define HTABLE_KEY_FUNCTOR_EQ eq_str +#define HTABLE_KEY_FUNCTOR_HASH hash_str +#include <rsys/hash_table.h> + +#define HTABLE_NAME geometries +#define HTABLE_DATA char +#define HTABLE_KEY struct scad_geometry* +#include <rsys/hash_table.h> + +#define HTABLE_NAME tags2geom +#define HTABLE_DATA struct htable_geometries +#define HTABLE_KEY int +#define HTABLE_DATA_FUNCTOR_INIT htable_geometries_init +#define HTABLE_DATA_FUNCTOR_RELEASE htable_geometries_release +#define HTABLE_DATA_FUNCTOR_COPY htable_geometries_copy +#define HTABLE_DATA_FUNCTOR_COPY_AND_RELEASE htable_geometries_copy_and_release +#include <rsys/hash_table.h> + +struct scad_device { + struct logger* logger; + struct mem_allocator* allocator; + struct scad_options options; + struct htable_names geometry_names; + struct htable_geometries allgeom; + struct htable_tags2geom tags2geom[2]; + int verbose; + int need_synchro; + + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +/* Conditionally log a message on the LOG_ERROR stream of the device logger, + * with respect to the device verbose flag */ +extern LOCAL_SYM void +log_error + (struct scad_device* dev, + const char* msg, + ...) +#ifdef COMPILER_GCC + __attribute((format(printf, 2, 3))) +#endif +; + +/* Conditionally log a message on the LOG_WARNING stream of the device logger, + * with respect to the device verbose flag */ +extern LOCAL_SYM void +log_warning + (struct scad_device* dev, + const char* msg, + ...) +#ifdef COMPILER_GCC + __attribute((format(printf, 2, 3))) +#endif +; + +/* Conditionally log a message on the LOG_OUTPUT stream of the device logger, + * with respect to the device verbose flag */ +extern LOCAL_SYM void +log_message + (struct scad_device* dev, + const char* msg, + ...) +#ifdef COMPILER_GCC + __attribute((format(printf, 2, 3))) +#endif +; + +static INLINE void +log_msg + (struct scad_device* dev, + const enum log_type stream, + const char* msg, + va_list vargs) +{ + res_T res; (void)res; + ASSERT(dev && msg); + res = logger_vprint(dev->logger, stream, msg, vargs); + ASSERT(res == RES_OK); +} + +/******************************************************************************* + * Exported scad_device functions + ******************************************************************************/ +LOCAL_SYM res_T +check_device + (const char* function_name); + +LOCAL_SYM struct scad_device* +get_device + (void); + +LOCAL_SYM res_T +device_register_tags + (struct scad_geometry* geom); + +LOCAL_SYM res_T +device_unregister_tags + (const int log, + const enum log_type log_type, + struct scad_geometry* geom); + +LOCAL_SYM res_T +device_apply_mappings + (const int* original_dimTags, + int** mappings, + size_t* mappings_counts, /* Number of items in each mapping */ + const size_t mappings_count); /* Number of mappings; count(original_dimTags)/2 */ + +#endif diff --git a/src/scad_geometry.c b/src/scad_geometry.c @@ -0,0 +1,1960 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#include "rsys/logger.h" +#include "scad.h" +#include "scad_c.h" +#include "scad_device.h" +#include "scad_geometry.h" + +#include <rsys/rsys.h> +#include <rsys/mem_allocator.h> +#include <rsys/str.h> +#include <rsys/math.h> +#include <rsys/double3.h> + +#include <rsys/hash_table.h> + +#include <stdlib.h> +#include <string.h> +#include <gmsh/gmshc.h> + +/* A type used to deduplicate tags */ +#define HTABLE_NAME tags +#define HTABLE_KEY int +#define HTABLE_DATA char +#include <rsys/hash_table.h> + +/* A type used for mappings */ +#define HTABLE_NAME mappings +#define HTABLE_KEY int +#define HTABLE_DATA size_t +#include <rsys/hash_table.h> + +/******************************************************************************* + * Utility functions + ******************************************************************************/ +struct coord_pair { + const double* x; + const double* y; +}; + +static res_T +geom_set_name + (struct scad_geometry* geom, + const char* name) +{ + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = dev->allocator; + struct str str_name; + int name_initialized = 0; + int same_name; + res_T res = RES_OK; + + ASSERT(geom); + + if(name) { + if(strlen(name) == 0) { + res = RES_BAD_ARG; + log_error(get_device(), "Geometry name \"\" is invalid.\n"); + goto error; + } + str_init(allocator, &str_name); + name_initialized = 1; + ERR(str_set(&str_name, name)); + if(htable_names_find(&dev->geometry_names, &str_name)) { + /* if defined, names must be unique */ + res = RES_BAD_ARG; + log_error(get_device(), "Geometry name '%s' is allready in use.\n", + name); + goto error; + } + } + + same_name = (!name && str_is_empty(&geom->name)) + || (name && 0 == strcmp(name, str_cget(&geom->name))); + + if(!same_name) { + size_t n = htable_names_erase(&dev->geometry_names, &geom->name); + ASSERT((n == 1) == !str_is_empty(&geom->name)); (void)n; + } + + if(name) { + str_set(&geom->name, name); + ERR(htable_names_set(&dev->geometry_names, &geom->name, &geom)); + } else { + str_clear(&geom->name); + } + +exit: + if(name_initialized) str_release(&str_name); + return res; +error: + goto exit; +} + +static res_T +scad_geometry_create + (const char* name, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = dev->allocator; + char one = 1; + int log; + enum scad_log_refcounting option; + + ASSERT(out_geometry); + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + geom = (struct scad_geometry*)MEM_CALLOC(allocator, 1, sizeof(*geom)); + if(!geom) { + res = RES_MEM_ERR; + goto error; + } + + str_init(allocator, &geom->name); + ERR(htable_geometries_set(&dev->allgeom, &geom, &one)); + ERR(geom_set_name(geom, name)); + dev->need_synchro = 1; + +end: + *out_geometry = geom; + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + goto end; +} + +static res_T +gather_tags + (struct scad_geometry** geometries, + const size_t geometries_count, + int** out_dimTags, + size_t* out_dimTags_n) +{ + res_T res = RES_OK; + int* dimTags = NULL; + size_t i, j, c, sz; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = dev->allocator; + struct htable_tags t2, t3; + struct htable_tags_iterator it, end; + + ASSERT((geometries || !geometries_count) && (out_dimTags || !out_dimTags_n)); + + htable_tags_init(allocator, &t2); + htable_tags_init(allocator, &t3); + + /* list tags and remove duplicates */ + for(i = 0; i < geometries_count; i++) { + for(j = 0; j < geometries[i]->gmsh_dimTags_n; j += 2) { + char one = 1; + int dim = geometries[i]->gmsh_dimTags[j]; + int tag = geometries[i]->gmsh_dimTags[j+1]; + struct htable_tags* tn = (dim == 2) ? &t2 : &t3; + ASSERT(dim == 2 || dim == 3); + ERR(htable_tags_set(tn, &tag, &one)); + } + } + + /* Build result */ + sz = htable_tags_size_get(&t2) + htable_tags_size_get(&t3); + dimTags = MEM_ALLOC(allocator, sz * 2 * sizeof(*dimTags)); + if(!dimTags) { + res = RES_MEM_ERR; + goto error; + } + + c = 0; + htable_tags_begin(&t2, &it); + htable_tags_end(&t2, &end); + while(!htable_tags_iterator_eq(&it, &end)) { + dimTags[c++] = 2; + dimTags[c++] = *htable_tags_iterator_key_get(&it); + htable_tags_iterator_next(&it); + } + htable_tags_begin(&t3, &it); + htable_tags_end(&t3, &end); + while(!htable_tags_iterator_eq(&it, &end)) { + dimTags[c++] = 3; + dimTags[c++] = *htable_tags_iterator_key_get(&it); + htable_tags_iterator_next(&it); + } + ASSERT(sz*2 == c); + + *out_dimTags_n = c; + *out_dimTags = dimTags; + +exit: + htable_tags_release(&t2); + htable_tags_release(&t3); + return res; +error: + MEM_RM(allocator, dimTags); + goto exit; +} + +/* gmsh documentation states that memory allocated by gmsh should be freed using + * gmshFree. + * According to valgrind map results as allocated by gmsh are not fully freed if + * simply freed using gmshFree. + * A code review shows that maps content is recursivelly allocated. + * We end writting this helper function that free maps content and makes + * valgrind happy. */ +static void +free_gmsh_map + (int** map, + size_t mapnn) +{ + size_t i; + if(!mapnn) return; + for(i = 0; i < mapnn; i++) { + gmshFree(map[i]); + } + gmshFree(map); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +geometry_release + (const int log, + const enum log_type log_type, + struct scad_geometry* geom) +{ + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = dev->allocator; + res_T res = RES_OK; + size_t n; + + ASSERT(geom); + + dev->need_synchro = 1; + + ERR(device_unregister_tags(log, log_type, geom)); + MEM_RM(allocator, geom->gmsh_dimTags); + if(str_len(&geom->name) != 0) { + n = htable_names_erase(&dev->geometry_names, &geom->name); + ASSERT(n == 1); + } + str_release(&geom->name); + n = htable_geometries_erase(&dev->allgeom, &geom); + ASSERT(n == 1); (void)n; + MEM_RM(allocator, geom); + +end: + return res; +error: + goto end; +} + +/****************************************************************************** + * Exported functions + *****************************************************************************/ +res_T +scad_geometry_delete + (struct scad_geometry* geom) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + int log; + enum scad_log_refcounting option; + + if(!geom) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + +end: + return res; +error: + goto end; +} + +res_T +scad_scene_clear + (void) +{ + res_T res = RES_OK; + int ierr; + struct htable_geometries tmp; + struct htable_geometries_iterator it, end; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log, empty; + enum scad_log_refcounting option; + enum log_type log_type; + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + + htable_geometries_init(allocator, &tmp); + ERR(htable_geometries_copy(&tmp, &dev->allgeom)); + htable_geometries_begin(&tmp, &it); + htable_geometries_end(&tmp, &end); + empty = htable_geometries_is_empty(&dev->allgeom); + log_type = empty ? LOG_OUTPUT : LOG_WARNING; + log = (option == Scad_log_all) || (!empty && option == Scad_log_only_undeleted); + if(log) { + logger_print(dev->logger, log_type, "Clearing scene.\n"); + if(empty) { + logger_print(dev->logger, log_type, "scene is empty.\n"); + } + } + while(!htable_geometries_iterator_eq(&it, &end)) { + struct scad_geometry* geom = *htable_geometries_iterator_key_get(&it); + CHK(RES_OK == geometry_release(log, log_type, geom)); + htable_geometries_iterator_next(&it); + } + if(log) { + logger_print(dev->logger, log_type, "End clearing scene.\n"); + } + + /* Ensure clear is complete (not scad-registered tags) */ + gmshClear(&ierr); + ERR(gmsh_err_to_res_T(ierr)); + +end: + if(allocator) htable_geometries_release(&tmp); + return res; +error: + goto end; +} + +res_T +scad_geometry_get_count + (const struct scad_geometry* geom, + size_t* count) +{ + res_T res = RES_OK; + + if(!geom || !count) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + ASSERT(geom->gmsh_dimTags_n % 2 == 0); + *count = geom->gmsh_dimTags_n / 2; + +exit: + return res; +error: + goto exit; +} + +res_T +scad_geometry_get_name + (const struct scad_geometry* geom, + const char** name) +{ + res_T res = RES_OK; + + if(!geom || !name) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + *name = str_cget(&geom->name); + +exit: + return res; +error: + goto exit; +} + +res_T +scad_geometry_swap_names + (struct scad_geometry* geom1, + struct scad_geometry* geom2) +{ + res_T res = RES_OK; + int init = 0; + struct str tmp; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + + if(!geom1 || !geom2) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + if(!str_is_empty(&geom1->name)) { + ERR(htable_names_set(&dev->geometry_names, &geom1->name, &geom2)); + } + if(!str_is_empty(&geom2->name)) { + ERR(htable_names_set(&dev->geometry_names, &geom2->name, &geom1)); + } + + str_init(allocator, & tmp); + init = 1; + ERR(str_copy(&tmp, &geom1->name)); + ERR(str_copy(&geom1->name, &geom2->name)); + ERR(str_copy(&geom2->name, &tmp)); + +exit: + if(init) str_release(&tmp); + return res; +error: + goto exit; +} + +res_T +scad_geometry_get_mass + (struct scad_geometry* geom, + double* mass) +{ + res_T res = RES_OK; + int dim = 0; + size_t i, count = 0; + int* data = NULL; + size_t sz = 0; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + + if(!geom || !mass) goto error; + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + ASSERT(geom->gmsh_dimTags_n % 2 == 0); + count = geom->gmsh_dimTags_n / 2; + + ERR(gather_tags(&geom, 1, &data, &sz)); + + dim = data[0]; + *mass = 0; + for(i=0; i<count; ++i) { + double geom_mass = 0; + int ierr = 0; + if(data[2*i] != dim) goto error; + gmshModelOccGetMass(data[2*i], data[2*i + 1], &geom_mass, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + *mass += geom_mass; + } + +exit: + if(allocator) MEM_RM(allocator, data); + return res; +error: + goto exit; +} + +res_T +scad_geometry_get_centerofmass + (struct scad_geometry* geom, + double* center) +{ + res_T res = RES_OK; + size_t i = 0; + + if(!geom || !center) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + for(i = 0; i < geom->gmsh_dimTags_n; i += 2) { + double x, y, z; + int ierr = 0; + int dim = geom->gmsh_dimTags[i]; + int tag = geom->gmsh_dimTags[i + 1]; + gmshModelOccGetCenterOfMass(dim, tag, &x, &y, &z, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + center[3*i] = x; + center[3*i + 1] = y; + center[3*i + 2] = z; + } + +exit: + return res; +error: + goto exit; +} + +res_T +scad_add_rectangle + (const char* name, + const double xyz[3], + const double dxdy[2], + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int ierr, gmsh_ID; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!xyz || !dxdy || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + gmsh_ID = gmshModelOccAddRectangle(SPLIT3(xyz), SPLIT2(dxdy), -1, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags + = MEM_ALLOC(allocator, geom->gmsh_dimTags_n * sizeof(*geom->gmsh_dimTags)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 2; + geom->gmsh_dimTags[1] = gmsh_ID; + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_add_disk + (const char* name, + const double xyz[3], + const double radius, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int ierr, gmsh_ID; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!xyz || radius <= 0 || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + gmsh_ID = gmshModelOccAddDisk(SPLIT3(xyz), radius, radius, -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags + = MEM_ALLOC(allocator, geom->gmsh_dimTags_n * sizeof(*geom->gmsh_dimTags)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 2; + geom->gmsh_dimTags[1] = gmsh_ID; + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_add_polygon + (const char* name, + void (*get_position)(const size_t ivert, double pos[2], void* data), + void* data, + const double z, + const size_t count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int ierr, gmsh_ID; + struct scad_geometry* geom = NULL; + size_t i; + int* points = NULL; + int* lines = NULL; + int loop; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!get_position || count < 3 || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + points = MEM_ALLOC(allocator, count * sizeof(*points)); + lines = MEM_ALLOC(allocator, count * sizeof(*lines)); + if(!points || !lines) { + res = RES_MEM_ERR; + goto error; + } + + for(i=0; i<count; ++i) { + double pos[2]; + get_position(i, pos, data); + points[i] = gmshModelOccAddPoint(pos[0], pos[1], z, -1, -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + + for(i=0; i<count; i++) { + size_t end = (i == count-1) ? 0 : i+1; + lines[i] = gmshModelOccAddLine(points[i], points[end], -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + + loop = gmshModelOccAddCurveLoop(lines, count, -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + gmsh_ID = gmshModelOccAddPlaneSurface(&loop, 1, -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 2; + geom->gmsh_dimTags[1] = gmsh_ID; + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + if(allocator) { + MEM_RM(allocator, points); + MEM_RM(allocator, lines); + } + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_add_box + (const char* name, + const double xyz[3], + const double dxdydz[3], + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int ierr, gmsh_ID; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!xyz || !dxdydz || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + gmsh_ID = gmshModelOccAddBox(SPLIT3(xyz), SPLIT3(dxdydz), -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 3; + geom->gmsh_dimTags[1] = gmsh_ID; + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_add_cylinder + (const char* name, + const double xyz[3], + const double axis[3], + const double radius, + const double angle, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int ierr, gmsh_ID; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!xyz || !axis || radius <= 0 || angle < 0 || angle > 2*PI || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + gmsh_ID = gmshModelOccAddCylinder(SPLIT3(xyz), SPLIT3(axis), radius, -1, + angle, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 3; + geom->gmsh_dimTags[1] = gmsh_ID; + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + goto exit; +} + +int +scad_add_sphere + (const char* name, + const double xyz[3], + const double radius, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int ierr, gmsh_ID; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!xyz || radius <= 0 || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + gmsh_ID = + gmshModelOccAddSphere(SPLIT3(xyz), radius, -1, -PI/2, PI/2, 2*PI, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 3; + geom->gmsh_dimTags[1] = gmsh_ID; + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_fuse_geometries + (const char* name, + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn = 0, sz1, sz2; + int* data1 = NULL; + int* data2 = NULL; + int ierr = 0; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geometries || !geometries_count || !tools || !tools_count || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + ERR(gather_tags(geometries, geometries_count, &data1, &sz1)); + ERR(gather_tags(tools, tools_count, &data2, &sz2)); + + /* We don't remove gmsh objects here; they are only removed when their tags are + * no longuer used by any star-cad geometry */ + gmshModelOccFuse(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + if(allocator) { + MEM_RM(allocator, data1); + MEM_RM(allocator, data2); + } + gmshFree(mapn); + gmshFree(tagout); + free_gmsh_map(map, mapnn); + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + +res_T +scad_cut_geometries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn = 0, sz1, sz2; + int* data1 = NULL; + int* data2 = NULL; + int ierr = 0; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geometries || !geometries_count || !tools || !tools_count || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + ERR(gather_tags(geometries, geometries_count, &data1, &sz1)); + ERR(gather_tags(tools, tools_count, &data2, &sz2)); + + /* We don't remove gmsh objects here; they are only removed when their tags are + * no longuer used by any star-cad geometry */ + gmshModelOccCut(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + if(allocator) { + MEM_RM(allocator, data1); + MEM_RM(allocator, data2); + } + gmshFree(mapn); + gmshFree(tagout); + free_gmsh_map(map, mapnn); + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + +res_T +scad_intersect_geometries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn = 0, sz1, sz2; + int* data1 = NULL; + int* data2 = NULL; + int ierr = 0; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geometries || !geometries_count || !tools || !tools_count || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + ERR(gather_tags(geometries, geometries_count, &data1, &sz1)); + ERR(gather_tags(tools, tools_count, &data2, &sz2)); + + /* We don't remove gmsh objects here; they are only removed when their tags are + * no longuer used by any star-cad geometry */ + gmshModelOccIntersect(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = tagoutn; + if (tagoutn == 0){ + geom->gmsh_dimTags = NULL; + } else { + geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + } + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + if(allocator) { + MEM_RM(allocator, data1); + MEM_RM(allocator, data2); + } + gmshFree(mapn); + gmshFree(tagout); + free_gmsh_map(map, mapnn); + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + +res_T +scad_geometries_common_boundaries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn = 0, sz1, sz2; + int* data1 = NULL; + int* data2 = NULL; + int ierr = 0; + int* bound1 = NULL; + int* bound2 = NULL; + size_t n1, n2; + struct scad_geometry* geom = NULL; + struct mem_allocator* allocator = NULL; + struct scad_device* dev = get_device(); + int log; + enum scad_log_refcounting option; + + if(!geometries || !geometries_count || !tools || !tools_count || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + ERR(gather_tags(geometries, geometries_count, &data1, &sz1)); + ERR(gather_tags(tools, tools_count, &data2, &sz2)); + + /* We don't remove gmsh objects here; they are only removed when their tags are + * no longuer used by any star-cad geometry */ + gmshModelGetBoundary(data1, sz1, &bound1, &n1, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshModelGetBoundary(data2, sz2, &bound2, &n2, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshModelOccIntersect(bound1, n1, bound2, n2, &tagout, &tagoutn, &map, + &mapn, &mapnn, -1, 0/*no delete*/, 0/*no delete*/, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = tagoutn; + if (tagoutn == 0) { + geom->gmsh_dimTags = NULL; + } else { + geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + } + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + if(allocator) { + MEM_RM(allocator, data1); + MEM_RM(allocator, data2); + } + gmshFree(bound1); + gmshFree(bound2); + gmshFree(mapn); + gmshFree(tagout); + free_gmsh_map(map, mapnn); + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + +res_T +scad_geometry_rotate + (struct scad_geometry* geom, + const double pt[3], + const double axis[3], + const double angle) +{ + int* data; + size_t sz; + int ierr = 0; + res_T res = RES_OK; + + if(!geom || !pt || !axis) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + sz = geom->gmsh_dimTags_n; + data = geom->gmsh_dimTags; + gmshModelOccRotate(data, sz, SPLIT3(pt), SPLIT3(axis), angle, &ierr); + get_device()->need_synchro = 1; + ERR(gmsh_err_to_res_T(ierr)); + +exit: + return res; +error: + goto exit; +} + +res_T +scad_geometry_extrude + (const struct scad_geometry* geom, + const char* name, + const double dxdydz[3], + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* tagout = NULL; + size_t tagoutn; + size_t i, j; + int* extrude_data = NULL; + size_t extrude_sz = 0; + int ierr = 0; + struct scad_geometry* extrude_geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geom || !dxdydz || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + gmshModelOccExtrude(geom->gmsh_dimTags, geom->gmsh_dimTags_n, SPLIT3(dxdydz), + &tagout, &tagoutn, NULL, 0, NULL, 0, 0, &ierr); + get_device()->need_synchro = 1; + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &extrude_geom)); + /* keep only 3D entities */ + /* TODO : NOT SURE OF THE CONCEPT */ + for(i=0; i<tagoutn; i+=2) { + int dim = tagout[i]; + if(dim == 3) extrude_sz += 2; + } + extrude_data = MEM_ALLOC(allocator, extrude_sz * sizeof(*extrude_data)); + if(!extrude_data) { + res = RES_MEM_ERR; + goto error; + } + j = 0; + for(i=0; i<tagoutn; i+=2) { + int dim = tagout[i]; + int tag = tagout[i+1]; + if(dim == 3) { + extrude_data[j] = dim; + extrude_data[j+1] = tag; + j += 2; + } + } + ASSERT(j == extrude_sz); + extrude_geom->gmsh_dimTags_n = extrude_sz; + extrude_geom->gmsh_dimTags = extrude_data; + ERR(device_register_tags(extrude_geom)); + +exit: + if(out_geometry) *out_geometry = extrude_geom; + gmshFree(tagout); + return res; +error: + if(extrude_geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, extrude_geom)); + extrude_geom = NULL; + } + goto exit; +} + +res_T +scad_geometry_explode + (const struct scad_geometry* geom, + const char* prefix_name, /* Can be NULL */ + struct scad_geometry*** out_geometry, + size_t* out_geometry_n) +{ + res_T res = RES_OK; + int* data = NULL; + size_t i, sz = 0; + struct scad_geometry** geom_array = NULL; + struct str name; + int name_initialized = 0; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + + if(!geom || !out_geometry || !out_geometry_n) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + data = geom->gmsh_dimTags; + sz = geom->gmsh_dimTags_n; + + ASSERT(sz % 2 == 0); + geom_array = MEM_CALLOC(allocator, sz/2, sizeof(*geom_array)); + if(!geom_array) { + res = RES_MEM_ERR; + goto error; + } + + if(prefix_name) { + str_init(allocator, &name); + name_initialized = 1; + } + for(i=0; i<sz/2; ++i) { + if(prefix_name) { + ERR(str_set(&name, prefix_name)); + ERR(str_append_printf(&name,"_%lu", (unsigned long)i)); + ERR(scad_geometry_create(str_cget(&name), geom_array+i)); + } else { + ERR(scad_geometry_create(NULL, geom_array+i)); + } + geom_array[i]->gmsh_dimTags_n = 2; + geom_array[i]->gmsh_dimTags + = MEM_ALLOC(allocator, 2 * sizeof(*geom_array[i]->gmsh_dimTags)); + if(!geom_array[i]->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom_array[i]->gmsh_dimTags[0] = data[2*i]; + geom_array[i]->gmsh_dimTags[1] = data[2*i+1]; + + ERR(device_register_tags(geom_array[i])); + } + +exit: + if(out_geometry_n) *out_geometry_n = sz/2 ; + if(out_geometry) *out_geometry = geom_array; + if(name_initialized) str_release(&name); + return res; +error: + if(geom_array) { + for(i = 0; i < sz/2; i++) { + if(geom_array[i]) SCAD(geometry_delete(geom_array[i])); + } + MEM_RM(allocator, geom_array); + geom_array = NULL; + } + goto exit; +} + + +res_T +scad_geometry_copy + (const struct scad_geometry* geom, + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* data1; + int* tagout = NULL; + size_t sz1, tagoutn; + int ierr = 0; + struct scad_geometry* copy = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geom || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + sz1 = geom->gmsh_dimTags_n; + data1 = geom->gmsh_dimTags; + gmshModelOccCopy(data1, sz1, &tagout, &tagoutn, &ierr); + get_device()->need_synchro = 1; + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &copy)); + copy->gmsh_dimTags_n = tagoutn; + copy->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!copy->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(copy->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + + ERR(device_register_tags(copy)); + +exit: + if(out_geometry) *out_geometry = copy; + gmshFree(tagout); + return res; +error: + if(copy) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, copy)); + copy = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + +res_T +scad_geometry_rename + (struct scad_geometry* geom, + const char* name) /* Can be NULL */ +{ + res_T res = RES_OK; + + if(!geom) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + ERR(geom_set_name(geom, name)); + +exit: + return res; +error: + goto exit; +} + +res_T +scad_geometry_translate + (struct scad_geometry* geom, + const double dxdydz[3]) +{ + int* data; + size_t sz; + int ierr = 0; + res_T res = RES_OK; + + if(!geom || !dxdydz) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + sz = geom->gmsh_dimTags_n; + data = geom->gmsh_dimTags; + gmshModelOccTranslate(data, sz, SPLIT3(dxdydz), &ierr); + get_device()->need_synchro = 1; + ERR(gmsh_err_to_res_T(ierr)); + +exit: + return res; +error: + goto exit; +} + +res_T +scad_geometries_partition + (struct scad_geometry** geometries, + const size_t geometries_count, + const int allow_overlapping, + struct scad_geometry** out_geometries) +{ + res_T res = RES_OK; + size_t i; + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn = 0, mapnn = 0, sz; + int* data = NULL; + int ierr = 0; + struct scad_geometry** geoms = NULL; + struct htable_mappings m2, m3; + int hm_initialized = 0; + struct scad_device* dev = get_device(); + struct htable_tags t2, t3; + struct htable_tags_iterator it, end; + int ht_initialized = 0; + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geometries || !geometries_count || (allow_overlapping && !out_geometries)) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + ERR(gather_tags(geometries, geometries_count, &data, &sz)); + + /* As a general principle, we don't remove gmsh objects directly; they are + * only removed from scad_geometry_delete when their tags are no longuer used + * by any star-cad geometry. + * Here we can safely use the remove flag in the non-overlapping case, as + * this ends in the same tags being reused for output. */ + gmshModelOccFragment(data, sz, NULL, 0, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, (allow_overlapping == 0), 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(sz == 2*mapnn); /* Because input tags where deduplicated */ + + get_device()->need_synchro = 1; + + /* Check first if there was an overlapping problem */ + if(!allow_overlapping) { + /* No overlapping means that each tag in geometries is translated into a + * single tag in map */ + unsigned long ucount = 0; + for(i = 0; i < mapnn; i++) { + if(mapn[i] != 2) { + res = RES_BAD_ARG; + if(str_is_empty(&geometries[i]->name)) { + ucount++; + } else { + log_error(get_device(), "Geometry '%s' overlapping.\n", + str_cget(&geometries[i]->name)); + } + } + /* Additionally, with the delete flag ON we expect the tags to remain + * unchanged in the non-overlapping case. */ + ASSERT(res != RES_OK || allow_overlapping || map[i][1] == data[2*i+1]); + } + if(ucount) { + log_error(get_device(), "%lu unamed overlapping geometries.\n", ucount); + } + if(res != RES_OK) goto error; + } else { + /* Create htables of mappings to ease access */ + htable_mappings_init(allocator, &m2); + htable_mappings_init(allocator, &m3); + hm_initialized = 1; + for(i = 0; i < sz; i += 2) { + int dim = data[i]; + int tag = data[i+1]; + size_t mapping = i/2; + struct htable_mappings* mn = (dim == 2) ? &m2 : &m3; + ASSERT(dim == 2 || dim == 3); + ERR(htable_mappings_set(mn, &tag, &mapping)); + } + + /* Create output geometries from mapping */ + geoms = MEM_CALLOC(allocator, geometries_count, sizeof(*geoms)); + if(!geoms) { + res = RES_MEM_ERR; + goto error; + } + htable_tags_init(allocator, &t2); + htable_tags_init(allocator, &t3); + ht_initialized = 1; + for(i = 0; i < geometries_count; i++) { + struct scad_geometry* geom = geometries[i]; + size_t c, n, j; + int* dt = NULL; + /* For each tag in geometries[i] out_geometries[i] includes the mapped tags. + * The resulting tags need to be deduplicated though. */ + htable_tags_clear(&t2); + htable_tags_clear(&t3); + for(j = 0; j < geom->gmsh_dimTags_n; j += 2) { + int dim = geom->gmsh_dimTags[j]; + int tag = geom->gmsh_dimTags[j+1]; + struct htable_mappings* mn = (dim == 2) ? &m2 : &m3; + size_t k; + size_t* mapping = htable_mappings_find(mn, &tag); + ASSERT(dim == 2 || dim == 3); + ASSERT(mapping && *mapping < mapnn); + for(k = 0; k < mapn[*mapping]; k += 2) { + char one = 1; + int d = map[*mapping][k]; + int t = map[*mapping][k+1]; + struct htable_tags* tn = (d == 2) ? &t2 : &t3; + ERR(htable_tags_set(tn, &t, &one)); + } + } + /* Allocate result */ + n = htable_tags_size_get(&t2) + htable_tags_size_get(&t3); + dt = MEM_ALLOC(allocator, sizeof(*dt) * 2 * n); + if(!dt) { + res = RES_MEM_ERR; + goto error; + } + /* Copy tags */ + c = 0; + htable_tags_begin(&t2, &it); + htable_tags_end(&t2, &end); + while(!htable_tags_iterator_eq(&it, &end)) { + dt[c++] = 2; + dt[c++] = *htable_tags_iterator_key_get(&it); + htable_tags_iterator_next(&it); + } + htable_tags_begin(&t3, &it); + htable_tags_end(&t3, &end); + while(!htable_tags_iterator_eq(&it, &end)) { + dt[c++] = 3; + dt[c++] = *htable_tags_iterator_key_get(&it); + htable_tags_iterator_next(&it); + } + ASSERT(c == 2*n); + + /* Create geometry */ + ERR(scad_geometry_create(NULL, geoms+i)); + geoms[i]->gmsh_dimTags_n = c; + geoms[i]->gmsh_dimTags = dt; + ERR(device_register_tags(geoms[i])); + } + memcpy(out_geometries, geoms, geometries_count * sizeof(*geoms)); + } + +exit: + gmshFree(mapn); + free_gmsh_map(map, mapnn); + if(hm_initialized) { + htable_mappings_release(&m2); + htable_mappings_release(&m3); + } + if(ht_initialized) { + htable_tags_release(&t2); + htable_tags_release(&t3); + } + if(allocator) { + MEM_RM(allocator, data); + MEM_RM(allocator, geoms); + } + gmshFree(tagout); + return res; +error: + if(geoms) { + for(i = 0; i < geometries_count; i++) { + if(geoms[i]) CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geoms[i])); + } + } + if(tagout) { + gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + } + goto exit; +} + +res_T +scad_fragment_geometries + (const char* name, + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn = 0, sz1, sz2; + int* data1 = NULL; + int* data2 = NULL; + int ierr = 0; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geometries || !geometries_count || !tools || !tools_count || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + ERR(gather_tags(geometries, geometries_count, &data1, &sz1)); + ERR(gather_tags(tools, tools_count, &data2, &sz2)); + + /* We don't remove gmsh objects here; they are only removed when their tags are + * no longuer used by any star-cad geometry */ + gmshModelOccFragment(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + if(allocator) { + MEM_RM(allocator, data1); + MEM_RM(allocator, data2); + } + gmshFree(mapn); + gmshFree(tagout); + free_gmsh_map(map, mapnn); + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + + + +res_T +scad_geometry_boundary + (const char* name, + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int* tagout = NULL; + size_t tagoutn, sz; + int* data = NULL; + int ierr = 0; + struct scad_geometry* geom = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + int log; + enum scad_log_refcounting option; + + if(!geometries || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + log = option == Scad_log_all; + + ERR(gather_tags(geometries, geometries_count, &data, &sz)); + gmshModelGetBoundary(data, sz, &tagout, &tagoutn, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * 2 * sizeof(*tagout)); + if(!geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + + ERR(device_register_tags(geom)); + +exit: + if(allocator) MEM_RM(allocator, data); + if(out_geometry) *out_geometry = geom; + gmshFree(tagout); + return res; +error: + if(geom) { + CHK(RES_OK == geometry_release(log, LOG_OUTPUT, geom)); + geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + +res_T +scad_step_import + (const char* filename, + const char* name, + struct scad_geometry*** out_geometry, + size_t* out_geometry_n) +{ + int ierr; + int* tagout = NULL; + size_t tagoutn, i, ga_sz; + struct str strname; + int name_initialized = 0; + struct scad_geometry** geom_array = NULL; + struct mem_allocator* allocator = NULL; + struct scad_device* dev = get_device(); + res_T res = RES_OK; + + if(!filename || !out_geometry || !out_geometry_n) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + gmshModelOccImportShapes(filename, &tagout, &tagoutn, 1, "step", &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ASSERT(tagoutn % 2 == 0); + ga_sz = tagoutn / 2; + allocator = get_device()->allocator; + geom_array = MEM_CALLOC(allocator, ga_sz, sizeof(*geom_array)); + if(!geom_array) { + res = RES_MEM_ERR; + goto error; + } + + str_init(allocator, &strname); + name_initialized = 1; + for(i=0; i<ga_sz; ++i) { + if (name) { + ERR(str_set(&strname, name)); + ERR(str_append_printf(&strname,"_%lu", (unsigned long)i)); + ERR(scad_geometry_create(str_cget(&strname), geom_array+i)); + } else { + ERR(scad_geometry_create(NULL, geom_array+i)); + } + + geom_array[i]->gmsh_dimTags_n = 2; + geom_array[i]->gmsh_dimTags + = MEM_ALLOC(allocator, 2 * sizeof(*geom_array[i]->gmsh_dimTags)); + if(!geom_array[i]->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + geom_array[i]->gmsh_dimTags[0] = tagout[2*i]; + geom_array[i]->gmsh_dimTags[1] = tagout[2*i+1]; + + ERR(device_register_tags(geom_array[i])); + } + +exit: + gmshFree(tagout); + if(out_geometry_n) *out_geometry_n = ga_sz ; + if(out_geometry) *out_geometry = geom_array; + if(name_initialized) str_release(&strname); + return res; +error: + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + ga_sz = 0; + if(geom_array) { + for(i=0; i<ga_sz; ++i) { + if(geom_array[i]) SCAD(geometry_delete(geom_array[i])); + } + MEM_RM(allocator, geom_array); + geom_array = NULL; + } + goto exit; +} + + +res_T +scad_geometry_normal + (struct scad_geometry* geom, + double p[3], + double N[3], + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + int ierr = 0; + size_t i; + int* data = NULL; + size_t sz = 0; + struct scad_geometry* surface = NULL; + struct scad_geometry* out = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + + if(!geom || !p || !N || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + if(geom->gmsh_dimTags[0] == 2) { + ERR(scad_geometry_copy(geom, NULL, &surface)); + ERR(scad_synchronize()); + } else if(geom->gmsh_dimTags[0] == 3) { + ERR(scad_geometry_boundary(NULL, &geom, 1, &surface)); + } else { + res = RES_BAD_ARG; + goto error; + } + + ERR(gather_tags(&surface, 1, &data, &sz)); + + for(i=0; sz/2; ++i) { + double* coord = NULL; + double* pcoord = NULL; + size_t pcoord_n; + size_t coord_n; + double* normals = NULL; + size_t normals_n; + int dim = data[2*i]; + int tag = data[2*i + 1]; + + gmshModelGetParametrization(2, tag, p, 3, &pcoord, &pcoord_n, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + gmshModelGetValue(2, tag, pcoord, pcoord_n, &coord, &coord_n, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + if(d3_eq_eps(p, coord, 1e-6)) { + gmshModelGetNormal(tag, pcoord, pcoord_n, &normals, &normals_n, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(scad_geometry_create(name, &out)); + out->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*out->gmsh_dimTags)); + if(!out->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + out->gmsh_dimTags_n = 2; + out->gmsh_dimTags[0] = dim; + out->gmsh_dimTags[1] = tag; + ERR(device_register_tags(out)); + + d3_set(N, normals); + gmshFree(coord); + gmshFree(pcoord); + gmshFree(normals); + break; + } + } + +exit: + if(out_geometry) *out_geometry = out; + if(allocator) MEM_RM(allocator, data); + if(surface) scad_geometry_delete(surface); + return res; +error: + goto exit; +} + +res_T +scad_geometry_dilate + (struct scad_geometry* geom, + double center[3], + double scale[3]) +{ + res_T res = RES_OK; + int ierr = 0; + int* data = NULL; + size_t sz = 0; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + + if(!geom || !scale|| !center) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + ERR(gather_tags(&geom, 1, &data, &sz)); + + gmshModelOccDilate(data, sz, SPLIT3(center), SPLIT3(scale), &ierr); + ERR(gmsh_err_to_res_T(ierr)); + +exit: + if(allocator) MEM_RM(allocator, data); + return res; +error: + goto exit; +} diff --git a/src/scad_geometry.h b/src/scad_geometry.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#ifndef SCAD_PRIMITIVE_H +#define SCAD_PRIMITIVE_H + +#include <stdlib.h> + +#include <rsys/rsys.h> +#include <rsys/str.h> +#include <rsys/logger.h> + +struct scad_geometry { + int* gmsh_dimTags; + size_t gmsh_dimTags_n; + struct str name; +}; + +LOCAL_SYM res_T +geometry_release + (const int log, + const enum log_type log_type, + struct scad_geometry* geom); + +#endif diff --git a/src/test.c b/src/test.c @@ -1,48 +0,0 @@ -/* Copyright (C) 2022 |Meso|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/>. */ - -#include "scad.h" - -#define ERR(Expr) if((res = (Expr)) != RES_OK) goto error; else (void)0 - -int -main(int argc, char* argv[]) -{ - res_T res = RES_OK; - double p1[3] = {0, 0, 0}; - double p2[3] = {0.25, 0.25, 0.8}; - double d1[3] = {1, 1, 1}; - double d2[3] = {0.5, 0.5, 0.5}; - scad_geom_T box1 = SCAD_GEOM_NULL; - scad_geom_T box2 = SCAD_GEOM_NULL; - scad_geom_T cut = SCAD_GEOM_NULL; - - (void)argc; (void)argv; - - ERR(scad_init()); - ERR(scad_addbox(p1, d1, &box1)); - ERR(scad_addbox(p2, d2, &box2)); - - ERR(scad_conformal_mesh()); - -exit: - scad_geom_release(box1); - scad_geom_release(box2); - scad_geom_release(cut); - scad_release(); - return res; -error: - goto exit; -} diff --git a/src/test_api.c b/src/test_api.c @@ -0,0 +1,265 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#include "scad.h" +#include "scad_geometry.h" +#include "test_common.h" + +#include <rsys/rsys.h> +#include <rsys/str.h> +#include <rsys/math.h> +#include <rsys/mem_allocator.h> +#include <rsys/logger.h> + +#include <stdlib.h> +#include <stdio.h> + +static void +get_position + (const size_t ivert, double pos[2], void* data) +{ + double* coord = data; + ASSERT(pos && coord); + pos[0] = coord[2*ivert]; + pos[1] = coord[1+2*ivert]; + +} + +int +main(int argc, char* argv[]) +{ + res_T res = RES_OK; + double p1[3] = {0, 0, 0}; + double p2[3] = {0.25, 0.25, 0.8}; + double d1[3] = {1, 1, 1}; + double coord[] = {0, 1.6, 0.5, 0.9, 0.8, 0.6}; + struct scad_geometry* geom1 = NULL; + struct scad_geometry* geom2 = NULL; + struct scad_geometry* geom = NULL; + struct scad_geometry* poly = NULL; + struct scad_geometry** geom_array = NULL; + struct scad_geometry* geoms[2]; + struct scad_geometry* out_geoms[2]; + struct mem_allocator allocator; + struct logger logger; + const char* name; + size_t i, c; + + (void)argc; (void)argv; + + OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator)); + OK(logger_init(&allocator, &logger)); + + /* cannot call any API function before a successful call to scad_initialize */ + BAD(scad_finalize()); + BAD(scad_set_options(NULL)); + BAD(scad_synchronize()); + BAD(scad_scene_clear()); + BAD(scad_add_rectangle(NULL, p1, d1, &geom)); + BAD(scad_add_disk(NULL, p1, 1, &geom)); + BAD(scad_add_polygon(NULL, get_position, coord, 0, 3, &geom)); + BAD(scad_add_box(NULL, p1, d1, &geom)); + BAD(scad_add_cylinder(NULL, p1, d1, 2, 1, &geom)); + BAD(scad_add_sphere(NULL, p1, 1, &geom)); + BAD(scad_step_import("test.step", NULL, &geom_array, &c)); + BAD(scad_scene_mesh()); + BAD(scad_run_ui()); + BAD(scad_geometry_get_name(geom, &name)); + + /* cannot call any API function after a successful call to scad_finalize */ + OK(scad_initialize(&logger, &allocator, 3)); + OK(scad_finalize()); + + BAD(scad_finalize()); + BAD(scad_set_options(NULL)); + BAD(scad_synchronize()); + BAD(scad_scene_clear()); + BAD(scad_add_rectangle(NULL, p1, d1, &geom)); + BAD(scad_add_disk(NULL, p1, 1, &geom)); + BAD(scad_add_polygon(NULL, get_position, coord, 0, 3, &geom)); + BAD(scad_add_box(NULL, p1, d1, &geom)); + BAD(scad_add_cylinder(NULL, p1, d1, 2, 1, &geom)); + BAD(scad_add_sphere(NULL, p1, 1, &geom)); + BAD(scad_step_import("test.step", NULL, &geom_array, &c)); + BAD(scad_scene_mesh()); + BAD(scad_run_ui()); + BAD(scad_geometry_get_name(geom, &name)); + + BAD(scad_initialize(&logger, &allocator, 4)); + BAD(scad_initialize(&logger, &allocator, -1)); + + OK(scad_initialize(&logger, &allocator, 3)); + OK(scad_add_sphere("sphere 1", p1, .1, &geom1)); + OK(scad_add_sphere(NULL, p2, .2, &geom2)); + geoms[0] = geom1; + geoms[1] = geom2; + + OK(scad_add_sphere(NULL, p1, .1, &geom)); + BAD(scad_geometry_delete(NULL)); + OK(scad_geometry_delete(geom)); + + BAD(scad_geometry_get_count(NULL, &c)); + BAD(scad_geometry_get_count(geom1, NULL)); + OK(scad_geometry_get_count(geom1, &c)); + + BAD(scad_add_rectangle(NULL, NULL, d1, &geom)); + BAD(scad_add_rectangle(NULL, p1, NULL, &geom)); + BAD(scad_add_rectangle(NULL, p1, d1, NULL)); + BAD(scad_add_rectangle("sphere 1", p1, d1, &geom)); /* Name already used */ + OK(scad_add_rectangle(NULL, p1, d1, &geom)); + OK(scad_geometry_delete(geom)); + + BAD(scad_add_disk(NULL, NULL, 1, &geom)); + BAD(scad_add_disk(NULL, p1, 0, &geom)); + BAD(scad_add_disk(NULL, p1, 1, NULL)); + OK(scad_add_disk(NULL, p1, 1, &geom)); + OK(scad_geometry_delete(geom)); + + BAD(scad_add_polygon(NULL, NULL, coord, 0, 3, &poly)); + BAD(scad_add_polygon(NULL, get_position, coord, 0, 2, &poly)); + BAD(scad_add_polygon(NULL, get_position, coord, 0, 2, &poly)); + BAD(scad_add_polygon(NULL, get_position, coord, 0, 3, NULL)); + OK(scad_add_polygon(NULL, get_position, coord, 0, 3, &poly)); + + BAD(scad_add_box(NULL, NULL, d1, &geom)); + BAD(scad_add_box(NULL, p1, NULL, &geom)); + BAD(scad_add_box(NULL, p1, d1, NULL)); + OK(scad_add_box(NULL, p1, d1, &geom)); + OK(scad_geometry_delete(geom)); + + BAD(scad_add_cylinder(NULL, NULL, d1, 2, 1, &geom)); + BAD(scad_add_cylinder(NULL, p1, NULL, 2, 1, &geom)); + BAD(scad_add_cylinder(NULL, p1, d1, 0, 1, &geom)); + BAD(scad_add_cylinder(NULL, p1, d1, 2, -1, &geom)); + BAD(scad_add_cylinder(NULL, p1, d1, 2, 3*PI, &geom)); + BAD(scad_add_cylinder(NULL, p1, d1, 2, 1, NULL)); + OK(scad_add_cylinder(NULL, p1, d1, 2, 1, &geom)); + OK(scad_geometry_delete(geom)); + + BAD(scad_add_sphere(NULL, NULL, 1, &geom)); + BAD(scad_add_sphere(NULL, p1, 0, &geom)); + BAD(scad_add_sphere(NULL, p1, 1, NULL)); + OK(scad_add_sphere(NULL, p1, 1, &geom)); + OK(scad_geometry_delete(geom)); + + /* BAD(scad_fuse_geometries(NULL, NULL, 0, geoms, 2, &geom)); */ + BAD(scad_fuse_geometries(NULL, geoms, 2, NULL, 0, &geom)); + BAD(scad_fuse_geometries(NULL, NULL, 1, &geom2, 1, &geom)); + BAD(scad_fuse_geometries(NULL, &geom1, 1, NULL, 1, &geom)); + BAD(scad_fuse_geometries(NULL, &geom1, 1, &geom2, 1, NULL)); + OK(scad_fuse_geometries(NULL, &geom1, 1, &geom2, 1, &geom)); + OK(scad_geometry_delete(geom)); + + /* BAD(scad_cut_geometries(NULL, NULL, 0, geoms, 2, &geom)); */ + BAD(scad_cut_geometries(NULL, geoms, 2, NULL, 0, &geom)); + BAD(scad_cut_geometries(NULL, NULL, 1, &geom2, 1, &geom)); + BAD(scad_cut_geometries(NULL, &geom1, 1, NULL, 1, &geom)); + BAD(scad_cut_geometries(NULL, &geom1, 1, &geom2, 1, NULL)); + OK(scad_cut_geometries(NULL, &geom1, 1, &geom2, 1, &geom)); + OK(scad_geometry_delete(geom)); + + /* BAD(scad_intersect_geometries(NULL, NULL, 0, geoms, 2, &geom)); */ + BAD(scad_intersect_geometries(NULL, geoms, 2, NULL, 0, &geom)); + BAD(scad_intersect_geometries(NULL, NULL, 1, &geom2, 1, &geom)); + BAD(scad_intersect_geometries(NULL, &geom1, 1, NULL, 1, &geom)); + BAD(scad_intersect_geometries(NULL, &geom1, 1, &geom2, 1, NULL)); + OK(scad_intersect_geometries(NULL, &geom1, 1, &geom2, 1, &geom)); + OK(scad_geometry_delete(geom)); + + /* BAD(scad_geometries_common_boundaries(NULL, NULL, 0, geoms, 2, &geom)); */ + BAD(scad_geometries_common_boundaries(NULL, geoms, 2, NULL, 0, &geom)); + BAD(scad_geometries_common_boundaries(NULL, NULL, 1, &geom2, 1, &geom)); + BAD(scad_geometries_common_boundaries(NULL, &geom1, 1, NULL, 1, &geom)); + BAD(scad_geometries_common_boundaries(NULL, &geom1, 1, &geom2, 1, NULL)); + OK(scad_geometries_common_boundaries(NULL, &geom1, 1, &geom2, 1, &geom)); + OK(scad_geometry_delete(geom)); + + BAD(scad_geometries_partition(NULL, 2, 1, out_geoms)); + BAD(scad_geometries_partition(geoms, 0, 1, out_geoms)); + BAD(scad_geometries_partition(geoms, 2, 1, NULL)); + OK(scad_geometries_partition(geoms, 2, 1, out_geoms)); + OK(scad_geometry_delete(out_geoms[0])); + OK(scad_geometry_delete(out_geoms[1])); + + BAD(scad_geometry_boundary(NULL, NULL, 0, &geom)); + BAD(scad_geometry_boundary(NULL, &geom1, 1, NULL)); + OK(scad_geometry_boundary(NULL, &geom1, 1, &geom)); + OK(scad_geometry_delete(geom)); + + BAD(scad_geometry_copy(NULL, NULL, &geom)); + BAD(scad_geometry_copy(geom1, NULL, NULL)); + BAD(scad_geometry_copy(geom1, "sphere 1", NULL)); /* Name already in use */ + OK(scad_geometry_copy(geom1, "Sphere 1", &geom)); + OK(scad_geometry_delete(geom)); + + BAD(scad_geometry_rename(NULL, NULL)); + BAD(scad_geometry_rename(geom2, "sphere 1")); /* Name already in use */ + OK(scad_geometry_rename(geom2, NULL)); + + BAD(scad_geometry_translate(NULL, d1)); + BAD(scad_geometry_translate(geom1, NULL)); + OK(scad_geometry_translate(geom1, d1)); + + BAD(scad_geometry_rotate(NULL, p1, d1, 1)); + BAD(scad_geometry_rotate(geom1, NULL, d1, 1)); + BAD(scad_geometry_rotate(geom1, p1, NULL, 1)); + OK(scad_geometry_rotate(geom1, p1, d1, 1)); + + BAD(scad_geometry_extrude(NULL, NULL, d1, &geom)); + BAD(scad_geometry_extrude(poly, NULL, NULL, &geom)); + BAD(scad_geometry_extrude(poly, NULL, d1, NULL)); + OK(scad_geometry_extrude(poly, NULL, d1, &geom)); + OK(scad_geometry_delete(poly)); + OK(scad_geometry_delete(geom)); + + BAD(scad_scene_write(NULL)); + ERR(scad_scene_write("")); + OK(scad_scene_write("/tmp/test.step")); + + BAD(scad_step_import(NULL, "step", &geom_array, &c)); + BAD(scad_step_import("/tmp/test.step", NULL, &geom_array, &c)); + BAD(scad_step_import("/tmp/test.step", "step", NULL, &c)); + BAD(scad_step_import("/tmp/test.step", "step", &geom_array, NULL)); + ERR(scad_step_import("dont_exist.step", "step", &geom_array, &c)); + OK(scad_step_import("/tmp/test.step", "step", &geom_array, &c)); + for(i = 0; i < c; i++) { + OK(scad_geometry_delete(geom_array[i])); + } + MEM_RM(&allocator, geom_array); + + BAD(scad_stl_export(NULL, NULL, 0, 0)); + BAD(scad_stl_export(geom2, NULL, 0, 0)); /* geom2 has no name */ + OK(scad_stl_export(geom2, "/tmp/test", 0, 0)); + OK(scad_stl_export(geom1, NULL, 0, 0)); + + BAD(scad_stl_export_split(NULL, NULL, 0)); + BAD(scad_stl_export_split(geom2, NULL, 0)); /* geom2 has no name */ + OK(scad_stl_export_split(geom2, "/tmp/test", 0)); + OK(scad_stl_export_split(geom1, NULL, 0)); + + BAD(scad_geometry_get_name(NULL, &name)); + BAD(scad_geometry_get_name(geom1, NULL)); + OK(scad_geometry_get_name(geom1, &name)); + OK(scad_geometry_get_name(geom2, &name)); + + logger_release(&logger); + OK(scad_finalize()); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + + return (res == RES_OK) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/test_common.h b/src/test_common.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#include "scad.h" +#include "scad_geometry.h" + +#include <rsys/rsys.h> +#include <rsys/math.h> +#include <rsys/mem_allocator.h> + +#include <stdlib.h> + +#define OK(Expr) CHK(RES_OK == (Expr)) +#define BAD(Expr) CHK(RES_BAD_ARG == (Expr)) +#define ERR(Expr) CHK(RES_OK != (Expr)) + +static void +check_memory_allocator(struct mem_allocator* allocator) { + if(MEM_ALLOCATED_SIZE(allocator)) { + char dump[4096]; + MEM_DUMP(allocator, dump, sizeof(dump)/sizeof(char)); + fprintf(stderr, "%s\n", dump); + FATAL("Memory leaks.\n"); + } +} + diff --git a/src/test_export.c b/src/test_export.c @@ -0,0 +1,73 @@ +/* Copyright (C) 2022 |Meso|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/>. */ + +#include "scad.h" +#include "scad_geometry.h" +#include "test_common.h" + +#include <rsys/rsys.h> +#include <rsys/math.h> +#include <rsys/mem_allocator.h> +#include <rsys/logger.h> + +#include <stdlib.h> + +int +main(int argc, char* argv[]) +{ + res_T res = RES_OK; + double p1[3] = {0, 0, 0}; + double p2[3] = {4.25, 4.25, 4.8}; + double d1[3] = {1, 1, 1}; + struct scad_geometry* rectangle = NULL; + struct scad_geometry* cube = NULL; + struct scad_geometry* cube2 = NULL; + struct scad_geometry* geoms[2]; + struct mem_allocator allocator; + struct logger logger; + + (void)argc; (void)argv; + + OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator)); + OK(logger_init(&allocator, &logger)); + logger_release(&logger); + OK(scad_initialize(&logger, &allocator, 3)); + + OK(scad_add_rectangle("rectangle", p1, d1, &rectangle)); + OK(scad_add_box("cube", p1, d1, &cube)); + OK(scad_add_box(NULL, p2, d1, geoms+1)); + + geoms[0] = cube; + OK(scad_fuse_geometries("cube2", geoms, 1, geoms+1, 1, &cube2)); + + OK(scad_scene_mesh()); + + OK(scad_stl_export(rectangle, NULL, 0, 0)); + OK(scad_stl_export(rectangle, "bin_rectangle", 0, 1)); + + OK(scad_stl_export(cube, NULL, 0, 0)); + OK(scad_stl_export(cube, "bin_cube", 0, 1)); + + OK(scad_stl_export(cube2, NULL, 0, 0)); + OK(scad_stl_export(cube2, "bin_cube2", 0, 1)); + + OK(scad_finalize()); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + + return (res == RES_OK) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/test_partition.c b/src/test_partition.c @@ -0,0 +1,112 @@ +/* Copyright (C) 2022 |Meso|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 + +#include "scad.h" +#include "scad_geometry.h" +#include "test_common.h" + +#include <rsys/rsys.h> +#include <rsys/math.h> +#include <rsys/mem_allocator.h> +#include <rsys/logger.h> +#include <rsys/double3.h> + +#include <stdlib.h> +#include <stdio.h> + +static res_T +two_geoms + (double x, + const int allow_overlapping, + struct mem_allocator* allocator, + struct logger* logger) +{ + double p0[3] = {0, 0, 2}; + double d0[3] = {1, 1, 1}; + double p1[3] = {0, 0, 0}; + double d1[3] = {1, 1, 1}; + double p2[3]; + double d2[3] = {0.5, 0.5, 0.5}; + struct scad_geometry* geoms[2]; + struct scad_geometry* tmp[2]; + struct scad_geometry* out_geoms[2]; + char name[64]; + res_T res; + + OK(scad_initialize(logger, allocator, 3)); + + /* set position for cube #2 */ + d3(p2, x, 0.25, 0.25); + + OK(scad_add_box("cube0", p0, d0, tmp)); + OK(scad_add_box("cube1", p1, d1, tmp+1)); + + OK(scad_fuse_geometries("cubes", tmp, 1, tmp+1, 1, geoms)); + OK(scad_add_box("cube2", p2, d2, geoms+1)); + + res = scad_geometries_partition(geoms, 2, allow_overlapping, out_geoms); + if(res != RES_OK) goto end; + + OK(scad_scene_mesh()); + + if(allow_overlapping) { + snprintf(name, sizeof(name), "part_%g_1o", x); + OK(scad_stl_export(out_geoms[0], name, 0, 0)); + snprintf(name, sizeof(name), "part_%g_2o", x); + OK(scad_stl_export(out_geoms[1], name, 0, 1)); + } else { + snprintf(name, sizeof(name), "part_%g_1", x); + OK(scad_stl_export(geoms[0], name, 0, 0)); + snprintf(name, sizeof(name), "part_%g_2", x); + OK(scad_stl_export(geoms[1], name, 0, 1)); + } + +end: + OK(scad_finalize()); + return res; +} + +int +main(int argc, char* argv[]) +{ + struct mem_allocator allocator; + struct logger logger; + res_T res = RES_OK; + + (void)argc; (void)argv; + + OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator)); + OK(logger_init(&allocator, &logger)); + + /* First with distant geometries */ + OK(two_geoms(1.1, 0, &allocator, &logger)); + + /* First with contacting geometries */ + OK(two_geoms(1, 0, &allocator, &logger)); + + /* First with overlapping geometries */ + BAD(two_geoms(0.9, 0, &allocator, &logger)); + OK(two_geoms(0.9, 1, &allocator, &logger)); + + logger_release(&logger); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + + return (res == RES_OK) ? EXIT_SUCCESS : EXIT_FAILURE; +}