star-cad

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

commit 9a934b6abffb7b5f7190174ce7d0f42ffc2b8cc8
parent 55aff2e562fb349960a9408928d8208f7613c51e
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Fri, 15 Jul 2022 17:24:38 +0200

Split in more files; make scad types opaque

Diffstat:
Mcmake/CMakeLists.txt | 11++++++++---
Msrc/scad.c | 810++++++++++++++++++++++++++++++-------------------------------------------------
Msrc/scad.h | 252++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Asrc/scad_c.h | 36++++++++++++++++++++++++++++++++++++
Asrc/scad_device.c | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scad_device.h | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scad_geometry.c | 764+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scad_geometry.h | 39+++++++++++++++++++++++++++++++++++++++
Asrc/scad_scene.c | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scad_scene.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test1.c | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
11 files changed, 1912 insertions(+), 585 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -42,10 +42,15 @@ set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(SCAD_FILES_SRC - scad.c) + scad.c + scad_scene.c + scad_device.c + scad_geometry.c) set(SCAD_FILES_INC_API scad.h) set(SCAD_FILES_INC - ) + scad_scene.h + 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' @@ -55,7 +60,7 @@ 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_LIBRARIES}) +target_link_libraries(scad RSys ${GMSH_LIBRARIES} m) set_target_properties(scad PROPERTIES DEFINE_SYMBOL SCAD_SHARED_BUILD diff --git a/src/scad.c b/src/scad.c @@ -14,543 +14,336 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "scad.h" +#include "scad_c.h" +#include "scad_device.h" +#include "scad_scene.h" +#include "scad_geometry.h" #include <gmshc.h> #include <rsys/cstr.h> +#include <rsys/rsys.h> #include <rsys/str.h> +#include <rsys/float3.h> #include <rsys/stretchy_array.h> #include <stdio.h> -/****************************************************************************** - * Helper functions - *****************************************************************************/ -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; -} - -/****************************************************************************** - * Exported functions - *****************************************************************************/ -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); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - gmshOptionSetNumber("Mesh.MeshSizeFromPoints", 0, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - gmshOptionSetNumber("Mesh.MeshSizeFromCurvature", 1, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - gmshOptionSetNumber("Mesh.MinimumElementsPerTwoPi", 36, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - gmshOptionSetNumber("Mesh.MeshSizeExtendFromBoundary", 0, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - -exit: - return res; -error: - fprintf(stderr, "%s: can't initialize gmsh -- %s\n", - FUNC_NAME, res_to_cstr(res)); - goto exit; -} - -res_T -scad_release(void) -{ - res_T res = RES_OK; - int ierr = 0; - - gmshFinalize(&ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - -exit: - return res; -error: - fprintf(stderr, "%s: can't release gmsh -- %s\n", FUNC_NAME, res_to_cstr(res)); - goto exit; -} - -res_T -scad_synchronize(void) -{ - int ierr; - gmshModelOccSynchronize(&ierr); - return gmsh_err_to_res_T(ierr); -} +#define OKP(Expr) if((Expr) < 0) { res=RES_IO_ERR; goto error; } -res_T -scad_run_ui(void) -{ - int ierr; - gmshFltkRun(&ierr); - return gmsh_err_to_res_T(ierr); -} - -res_T -scad_geom_release(scad_geom_T geom) -{ - sa_release(geom); - return RES_OK; -} - -res_T -scad_concat(scad_geom_T* geom1, const scad_geom_T geom2) +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; size_t i; + FILE* stl_file = NULL; + unsigned cpt; - if(!geom1 || !geom2) { - fprintf(stderr, "%s: can't concat\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - - FOR_EACH(i, 0, sa_size(geom2)) { - sa_push(*geom1, geom2[i]); - } - -exit: - return res; -error: - goto exit; -} - -res_T -scad_addbox - (const double xyz[3], - const double dxdydz[3], - scad_geom_T* geom) -{ - int ierr = 0; - int tag = 0; - res_T res = RES_OK; - - if(!xyz || !dxdydz) { - fprintf(stderr, "%s: invalid data!\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - - tag = gmshModelOccAddBox(SPLIT3(xyz), SPLIT3(dxdydz), -1, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: can't create box -- %s\n", FUNC_NAME, res_to_cstr(res)); - goto error; - } - - if(*geom != NULL) *geom = NULL; - sa_push(*geom,3); - sa_push(*geom,tag); - -exit: - return res; -error: - goto exit; -} - -res_T -scad_addcylinder - (const double xyz[3], - const double axis[3], - const double rad, - const double angle, - scad_geom_T* geom) -{ - int ierr = 0; - int tag = 0; - res_T res = RES_OK; - - if(!xyz || !axis) { - fprintf(stderr, "%s: invalid data!\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - - tag = gmshModelOccAddCylinder(SPLIT3(xyz), SPLIT3(axis), rad, -1, angle, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: can't create cylinder -- %s\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - - if(*geom != NULL) *geom = NULL; - sa_push(*geom,3); - sa_push(*geom,tag); - -exit: - return res; -error: - goto exit; -} - -int -scad_addsphere - (const double xyz[3], - const double rad, - scad_geom_T* geom) -{ - int ierr = 0; - int tag = 0; - res_T res = RES_OK; - - if(!xyz) { - fprintf(stderr,"%s: invalid data!\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - - tag = gmshModelOccAddSphere(SPLIT3(xyz), rad, -1, -PI/2, PI/2, 2*PI, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: can't create sphere -- %s\n", - FUNC_NAME, res_to_cstr(res)); - goto exit; - } - - if(*geom != NULL) *geom = NULL; - sa_push(*geom,3); - sa_push(*geom,tag); - -exit: - return res; -error: - goto exit; -} - -res_T -scad_remove(scad_geom_T geom) -{ - int ierr = 0; - res_T res = RES_OK; - - gmshModelOccRemove(geom, sa_size(geom), 0, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: can't remove geometry -- %s\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - -exit: - return res; -error: - goto exit; -} - -res_T -scad_fuse - (const scad_geom_T geom1, - const scad_geom_T geom2, - scad_geom_T* out, - const int remove) -{ - int* tagout = NULL; - int** map = NULL; - size_t* mapn = NULL; - size_t tagoutn, mapnn, i; - int ierr = 0; - res_T res = RES_OK; + ASSERT(filename && coord && coord_n); - gmshModelOccFuse(geom1, sa_size(geom1), geom2, sa_size(geom2), &tagout, - &tagoutn, &map, &mapn, &mapnn, -1, remove, remove, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: fuse not possible -- %s\n", FUNC_NAME, res_to_cstr(res)); + stl_file = fopen(filename, "w"); + if(!stl_file) { + res = RES_IO_ERR; goto error; } - FOR_EACH(i, 0, tagoutn) { - sa_push(*out, tagout[i]); + 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; + float n[3]; + float vtx[3][3]; + float tmp[3], edge1[3], edge2[3];; + f3_set_d3(vtx[0], coord[i]+j+0); + f3_set_d3(vtx[1], coord[i]+j+3); + f3_set_d3(vtx[2], coord[i]+j+6); + f3_sub(edge1, vtx[1], vtx[0]); + f3_sub(edge2, vtx[2], vtx[0]); + f3_cross(tmp, edge1, edge2); + f3_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 %g %g %g\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(tagout) free(tagout); - if(mapn) free(mapn); - if(map) free(map); + if(stl_file) fclose(stl_file); return res; error: + fprintf(stderr, "Error: could not export to STL file '%s': %s\n", + filename, res_to_cstr(res)); goto exit; } - - -res_T -scad_cut - (const scad_geom_T geom1, - const scad_geom_T geom2, - scad_geom_T* out, - const int remove) + +static res_T +write_binary_stl + (const char* filename, + const unsigned trg_count, + double* const* coord, + const size_t* coord_n) { - int* tagout = NULL; - int** map = NULL; - size_t* mapn = NULL; - size_t tagoutn, mapnn, i; - int ierr = 0; res_T res = RES_OK; + size_t i; + char header[80] = "Binary STL"; + FILE* stl_file = NULL; + unsigned cpt; - gmshModelOccCut(geom1, sa_size(geom1), geom2, sa_size(geom2), &tagout, - &tagoutn, &map, &mapn, &mapnn, -1, remove, remove, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: cut not possible -- %s\n", FUNC_NAME, res_to_cstr(res)); - goto error; - } - - if(*out) sa_clear(*out); - FOR_EACH(i, 0, tagoutn) { - sa_push(*out, tagout[i]); - } - -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) -{ - int* tagout = NULL; - int** map = NULL; - size_t* mapn = NULL; - size_t tagoutn, mapnn, i; - int ierr = 0; - res_T res = RES_OK; + ASSERT(filename && coord && coord_n); - gmshModelOccIntersect(geom1, sa_size(geom1), geom2, sa_size(geom2), &tagout, - &tagoutn, &map, &mapn, &mapnn, -1, remove, remove, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: intersection not possible -- %s\n", - FUNC_NAME, res_to_cstr(res)); + stl_file = fopen(filename, "wb"); + if(!stl_file) { + res = RES_IO_ERR; 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); - gmshModelGetBoundary(geom2, sa_size(geom2), &bound2, &n2, 1, 0, 0, &ierr); - gmshModelOccIntersect(bound1, n1, bound2, n2, &tagout, &tagoutn, &map, - &mapn, &mapnn, -1, 0/*no delete*/, 0/*no delete*/, &ierr); - } - - FOR_EACH(i, 0, tagoutn) { - sa_push(*out, tagout[i]); - } - -exit: - if(tagout) free(tagout); - if(mapn) free(mapn); - if(map) free(map); - return res; -error: - goto exit; -} - - -res_T -scad_fragment - (const scad_geom_T geom1, - const scad_geom_T geom2, - scad_geom_T* out, - const int remove) -{ - int* tagout = NULL; - int** map = NULL; - size_t* mapn = NULL; - size_t tagoutn, mapnn, i; - int ierr = 0; - res_T res = RES_OK; - - gmshModelOccFragment(geom1, sa_size(geom1), geom2, sa_size(geom2), &tagout, - &tagoutn, &map, &mapn, &mapnn, -1, remove, remove, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: fragment not possible -- %s\n", - FUNC_NAME, res_to_cstr(res)); + if(1 != fwrite(header, sizeof(header), 1, stl_file)) { + res = RES_IO_ERR; goto error; } - FOR_EACH(i, 0, tagoutn) { - sa_push(*out, tagout[i]); - } - -exit: - if(tagout) free(tagout); - if(mapn) free(mapn); - if(map) free(map); - return res; -error: - goto exit; -} - -res_T -scad_boundary(const scad_geom_T geom1, scad_geom_T* out) -{ - int* b = NULL; - size_t i, n; - int ierr = 0; - res_T res = RES_OK; - - gmshModelGetBoundary(geom1, sa_size(geom1), &b, &n, 1, 0, 0, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: get boundary not possible -- %s\n", - FUNC_NAME, res_to_cstr(res)); + if(1 != fwrite(&trg_count, 4, 1, stl_file)) { + res = RES_IO_ERR; goto error; } - FOR_EACH(i, 0, n) { - sa_push(*out, b[i]); + 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(b) free(b); + if(stl_file) fclose(stl_file); return res; error: + fprintf(stderr, "Error: could not export to STL file '%s': %s\n", + filename, res_to_cstr(res)); goto exit; } - -res_T -scad_translate -(scad_geom_T geom, const double dxdydz[3]) + +static res_T +write_stl + (const char* filename, + const unsigned trg_count, + double* const* coord, + const size_t* coord_n, + const int binary) { - int ierr = 0; - res_T res = RES_OK; - - if(!geom || !dxdydz){ - fprintf(stderr,"%s: invalid data!\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - - gmshModelOccTranslate(geom, sa_size(geom), SPLIT3(dxdydz), &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: translation not possible -- %s\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - -exit: - return res; -error: - 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_rotate - (scad_geom_T geom, - const double pt[3], - const double axis[3], - const double angle) +scad_device_synchronize + (struct scad_device* device) { - int ierr = 0; - res_T res = RES_OK; - - if(!geom || !pt || !axis){ - fprintf(stderr, "%s: invalid data!\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - - gmshModelOccRotate(geom, sa_size(geom), SPLIT3(pt), SPLIT3(axis), angle, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) { - fprintf(stderr, "%s: rotation not possible -- %s\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - -exit: - return res; -error: - goto exit; + int ierr; + gmshModelOccSynchronize(&ierr); + device->need_synchro = 0; + return gmsh_err_to_res_T(ierr); } res_T -scad_conformal_mesh(void) +scad_run_ui(void) { - int* dimTags = NULL; - size_t dimTags_n; - int ierr = 0; - - gmshModelOccSynchronize(&ierr); - gmshModelOccGetEntities(&dimTags, &dimTags_n, 3, &ierr); - if( ierr == 0 && dimTags_n > 2) gmshModelOccRemoveAllDuplicates(&ierr); - gmshModelOccSynchronize(&ierr); - gmshModelMeshGenerate(2, &ierr); + int ierr; + gmshFltkRun(&ierr); return gmsh_err_to_res_T(ierr); } res_T -scad_stl_export(const scad_geom_T geom, char *prefix) +scad_stl_export + (struct scad_geometry* geometry, + const char* prefix, + const int binary) { struct str filename; int* tagout = NULL; int* tags = NULL; + 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; + int* dimTags = 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; - str_init(NULL, &filename); - str_set(&filename, prefix); - str_append(&filename, ".stl"); + if(!geometry) { + res = RES_BAD_ARG; + goto error; + } - if(geom[0] == 2) { - tagoutn = sa_size(geom); - FOR_EACH(i, 0, tagoutn/2){ - sa_push(tags, geom[2*i + 1]); - } - group = gmshModelAddPhysicalGroup(2, tags, tagoutn/2, -1, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + if(geometry->scene->device->need_synchro) { + ERR(scad_device_synchronize(geometry->scene->device)); + } + + sz = geometry->gmsh_dimTags_n; + data = geometry->gmsh_dimTags; + ASSERT(sz > 0 && sz % 2 == 0); + str_init(geometry->scene->device->allocator, &filename); + str_initialized = 1; + if(prefix) { + ERR(str_set(&filename, prefix)); } else { - FOR_EACH(i, 0, sa_size(geom)/2) { - gmshModelMeshSetOutwardOrientation(geom[2*i+1], &ierr); + if(str_len(&geometry->name) == 0) { + res = RES_BAD_ARG; + goto error; } - - gmshModelGetBoundary(geom, sa_size(geom),&tagout, &tagoutn, 1, 0, 0, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - - FOR_EACH(i, 0, tagoutn/2) { - sa_push(tags, tagout[2*i + 1]); + ERR(str_copy(&filename, &geometry->name)); + } + ERR(str_append(&filename, ".stl")); + + if(data[0] == 3) { /* geometry is 3D */ + size_t dimTags_n; + size_t tcount; + + ERR(scad_device_synchronize(geometry->scene->device)); + gmshModelOccGetEntities(&dimTags, &dimTags_n, 3, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + if(dimTags_n > 100000) { + size_t dt_n; + gmshModelOccRemoveAllDuplicates(&ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshModelOccGetEntities(&dimTags, &dt_n, 3, &ierr); + ERR(gmsh_err_to_res_T(ierr)); +#if 0 + if(dimTags_n != dt_n) { + /* Entities count changed when removing duplicates ! + * Some entities are overlapping: error. */ + fprintf(stderr, "Invalid model, overlappig entities detected.\n"); + res = RES_BAD_ARG; + goto error; + /* TODO: implement early detection in add_xxx functions and use logger. + * Use a non-destructive command (unlike remove duplicate). + * (could be an option, e.g. reject_overlapping_entities, default 0) */ + } +#endif + } + ERR(scad_device_synchronize(geometry->scene->device)); + gmshModelMeshGenerate(2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ERR(scad_device_synchronize(geometry->scene->device)); + + gmshModelGetBoundary(data, sz, &tagout, &tagoutn, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(tagoutn % 2 == 0); + + /* Allocate room for arrays + * TODO: use allocator */ + nodeTags = calloc(tagoutn/2, sizeof(*nodeTags)); + nodeTags_n = calloc(tagoutn/2, sizeof(*nodeTags_n)); + coord = calloc(tagoutn/2, sizeof(*coord)); + coord_n = calloc(tagoutn/2, sizeof(*coord_n)); + pCoord = calloc(tagoutn/2, sizeof(*pCoord)); + pCoord_n = calloc(tagoutn/2, sizeof(*pCoord_n)); + if(!nodeTags || !nodeTags_n || !coord || !coord_n || !pCoord || !pCoord_n) { + res = RES_MEM_ERR; + goto error; } - group = gmshModelAddPhysicalGroup(2, tags, tagoutn/2, -1, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - } + tcount = 0; + for(i = 0; i < tagoutn/2; i++) { + gmshModelMeshGetNodesByElementType(2, nodeTags+i, nodeTags_n+i, + coord+i, coord_n+i, pCoord+i, pCoord_n+i, tagout[2*i+1], 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(coord_n[i] % 9 == 0); + ASSERT(nodeTags_n[i] % 3 == 0); + tcount += coord_n[i]; + } + tcount /= 9; + ASSERT(tcount <= UINT_MAX); + + ERR(write_stl(str_cget(&filename), (unsigned)tcount, coord, coord_n, binary)); + } else { /* Geometry is 2D */ + FOR_EACH(i, 0, sz/2){ + ASSERT(data[2*i] == 2); + sa_push(tags, data[2*i + 1]); + } + group = gmshModelAddPhysicalGroup(2, tags, sz/2, -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); - gmshWrite(str_get(&filename), &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + gmshWrite(str_cget(&filename), &ierr); + ERR(gmsh_err_to_res_T(ierr)); - dimtag[0]=2; - dimtag[1]=group; - gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + dimtag[0]=2; + dimtag[1]=group; + gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } exit: - str_release(&filename); + if(str_initialized) str_release(&filename); if(tagout) free(tagout); if(tags) sa_release(tags); + if(nodeTags) { + for(i = 0; i < tagoutn/2; i++) free(nodeTags[i]); + free(nodeTags); + } + if(nodeTags_n) free(nodeTags_n); + if(coord) { + for(i = 0; i < tagoutn/2; i++) free(coord[i]); + free(coord); + } + if(coord_n) free(coord_n); + if(pCoord) { + for(i = 0; i < tagoutn/2; i++) free(pCoord[i]); + free(pCoord); + } + if(pCoord_n) free(pCoord_n); + if(dimTags) free(dimTags); return res; error: fprintf(stderr, "%s: could not export to STL -- %s\n", @@ -559,79 +352,98 @@ error: } res_T -scad_stl_export_split(const scad_geom_T geom, char *prefix) +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; - int* tags = 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; - str_init(NULL, &filename); + (void)binary; - if(geom[0] == 2) { - tagoutn = sa_size(geom); - FOR_EACH(i, 0, tagoutn/2) { - char num[32]; + if(!geometry) { + res = RES_BAD_ARG; + goto error; + } - group = gmshModelAddPhysicalGroup(2, &geom[2*i+1], 1, -1, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + if(geometry->scene->device->need_synchro) { + ERR(scad_device_synchronize(geometry->scene->device)); + } - /* TODO move in a function - * TODO check the str return code */ - str_set(&filename, prefix); - str_append_char(&filename, '_'); - sprintf(num, "%lu", (unsigned long)i); /* FIXME handle possible overflow */ - str_append(&filename, num); - str_append(&filename, ".stl"); - gmshWrite(str_get(&filename), &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + sz = geometry->gmsh_dimTags_n; + data = geometry->gmsh_dimTags; + ASSERT(sz % 2 == 0); + + str_init(geometry->scene->device->allocator, &filename_root); + str_init(geometry->scene->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); + 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)); dimtag[0]=2; dimtag[1]=group; gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + ERR(gmsh_err_to_res_T(ierr)); } - } else { - - FOR_EACH(i, 0, sa_size(geom)/2) { - gmshModelMeshSetOutwardOrientation(geom[2*i+1], &ierr); + FOR_EACH(i, 0, sz/2) { + ASSERT(data[2*i] == 3); + gmshModelMeshSetOutwardOrientation(data[2*i+1], &ierr); + ERR(gmsh_err_to_res_T(ierr)); } - gmshModelGetBoundary(geom, sa_size(geom), &tagout, &tagoutn, 1, 0, 0, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + gmshModelGetBoundary(data, sz, &tagout, &tagoutn, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); FOR_EACH(i, 0, tagoutn/2){ - char num[32]; - - group = gmshModelAddPhysicalGroup(2, &tagout[2*i+1], 1, -1, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; - - /* TODO move in a function - * TODO check the str return code */ - str_set(&filename, prefix); - str_append_char(&filename, '_'); - sprintf(num, "%lu", (unsigned long)i); /* FIXME handle possible overflow */ - str_append(&filename, num); - str_append(&filename, ".stl"); - gmshWrite(str_get(&filename), &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + 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)); dimtag[0]=2; dimtag[1]=group; gmshModelRemovePhysicalGroups(dimtag, 2, &ierr); - if((res = gmsh_err_to_res_T(ierr)) != RES_OK) goto error; + ERR(gmsh_err_to_res_T(ierr)); } } exit: - str_release(&filename); + if(str_initialized) { + str_release(&filename_root); + str_release(&filename); + } if(tagout) free(tagout); - if(tags) sa_release(tags); return res; error: goto exit; diff --git a/src/scad.h b/src/scad.h @@ -27,125 +27,245 @@ #define SCAD_API extern IMPORT_SYM #endif -/* 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__; +/* 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; + +/* Forward declaration of scad opaque data types */ +struct scad_device; /* Entry point of the library */ +struct scad_scene; /* Collection of geometry items */ +struct scad_geometry; /* Wrapping of dimTags gmsh description */ + +/* A type to specify options for devices */ +struct scad_device_param { + struct { + double StlOneSolidPerSurface; + double MeshSizeFromPoints; + double MeshSizeFromCurvature; + double MinimumElementsPerTwoPi; + double MeshSizeExtendFromBoundary; + } Mesh; +}; + +#define SCAD_INIT_DEFAULT__ \ + { { 2, 0, 1, 36, 0 } } + +static const struct scad_device_param SCAD_INIT_DEFAULT = SCAD_INIT_DEFAULT__; BEGIN_DECLS -/****************************************************************************** - * API - *****************************************************************************/ +/******************************************************************************* + * Device API - A device is the entry point of the scad library. + * Applications use a scad_device to create others scad resources. + ******************************************************************************/ SCAD_API res_T -scad_init - (void); +scad_device_create + (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 */ + struct scad_device** dev); + +res_T +scad_device_set_params + (struct scad_device* device, + const struct scad_device_param* params); +/* Explicitly synchronize current state with regard to recent geometry changes. + * Most synchronize calls should be automatically triggered when needed. */ SCAD_API res_T -scad_release - (void); +scad_device_synchronize + (struct scad_device* device); SCAD_API res_T -scad_synchronize - (void); +scad_device_ref_get + (struct scad_device* dev); -SCAD_API res_T /* FIXME remove this */ -scad_run_ui - (void); +SCAD_API res_T +scad_device_ref_put + (struct scad_device* dev); + +/******************************************************************************* + * Scene API - A scene is a collection of geometries. + ******************************************************************************/ +SCAD_API res_T +scad_scene_create + (struct scad_device* device, + struct scad_scene** out_scene); + +SCAD_API res_T +scad_scene_ref_get + (struct scad_scene* scene); -/* Remove the handler but not the geometry. Use scad_geom_remove to remove it. - * TODO where is scad_geom_remove? */ SCAD_API res_T -scad_geom_release - (scad_geom_T geom); +scad_scene_ref_put + (struct scad_scene* scene); + +/******************************************************************************* + * 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 by scene. + ******************************************************************************/ +/* 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_addbox - (const double xyz[3], +scad_scene_add_box + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + const double xyz[3], const double dxdydz[3], - scad_geom_T* geom); + struct scad_geometry** box); /* Can be NULL: no handler returned. */ +/* 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_addcylinder - (const double xyz[3], +scad_scene_add_cylinder + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + const double xyz[3], const double axis[3], const double rad, const double angle, - scad_geom_T* geom); + struct scad_geometry** cylinder); /* Can be NULL: no handler returned. */ +/* Add a sphere of center `xyz' and radius `rad' to the scene. */ SCAD_API res_T -scad_addsphere - (const double xyz[3], +scad_scene_add_sphere + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + const double xyz[3], const double rad, - scad_geom_T* geom); + struct scad_geometry** sphere); /* Can be NULL: no handler returned. */ +/* Create a group of geometries of dimension `dim' from an array of geometries. + * All of the geometries must have been added to scene and must have dimension + * dim. */ SCAD_API res_T -scad_remove - (scad_geom_T geom); +scad_scene_create_group + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + const int dim, + struct scad_geometry** geometries, + const size_t count, + struct scad_geometry** group); SCAD_API res_T -scad_concat - (scad_geom_T* geom1, - const scad_geom_T geom2); +scad_scene_conformal_mesh + (struct scad_scene* scene); +/* Compute the boolean union (the fusion) of the geometries `geom1' and `geom2'. + * Remove geom1 and geom2 from scene if `remove' is set. */ SCAD_API res_T -scad_fuse - (const scad_geom_T geom1, - const scad_geom_T geom2, - scad_geom_T* out, +scad_scene_fuse_geometries + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, const int remove); +/* Compute the boolean difference between the geometries `geom1' and `geom2'. + * Remove geom1 and geom2 from scene if `remove' is set. */ SCAD_API res_T -scad_cut - (const scad_geom_T geom1, - const scad_geom_T geom2, - scad_geom_T* out, +scad_scene_cut_geometries + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, const int remove); +/* Compute the boolean intersection (the common parts) of the geometries + * `geom1' and `geom2'. + * Remove geom1 and geom2 from scene if `remove' is set. */ SCAD_API res_T -scad_intersect - (const scad_geom_T geom1, - const scad_geom_T geom2, - scad_geom_T* out, +scad_scene_intersect_geometries + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, const int remove); +/* compute boundary intersection (the common part) of geom1 and geom2 + * Remove geom1 and geom2 from scene if `remove' is set. */ SCAD_API res_T -scad_fragment - (const scad_geom_T geom1, - const scad_geom_T geom2, - scad_geom_T* out, +scad_scene_geometries_common_boundaries + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, const int remove); +/* Compute the boolean fragments (general fuse) resulting from the + * intersection of the geometries `geom1' and `geom2', making all iterfaces + * conformal. 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. + * Remove geom1 and geom2 from scene if `remove' is set. */ SCAD_API res_T -scad_boundary - (const scad_geom_T geom1, - scad_geom_T* out); +scad_scene_geometries_fragment + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, + const int remove); + +/* Get the boundary of the geometry `geom'. */ +SCAD_API res_T +scad_scene_geometry_boundary + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom, + struct scad_geometry** out); +/* Translate the geometry `geom' along (`dx', `dy', `dz'). */ SCAD_API res_T -scad_translate - (scad_geom_T geom, +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_rotate - (scad_geom_T geom, +scad_geometry_rotate + (struct scad_geometry* geom, const double pt[3], - const double axis[3], + const double dir[3], const double angle); -SCAD_API res_T -scad_conformal_mesh - (void); - +/* 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 - (const scad_geom_T geom, - char* prefix); + (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_stl_export_split - (const scad_geom_T geom, - char* prefix); + (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 /* FIXME remove this */ +scad_run_ui + (void); END_DECLS 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,197 @@ +/* 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_c.h" +#include "scad_device.h" +#include "scad_scene.h" + +#include <rsys/logger.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> +#include <rsys/cstr.h> + +#include <gmshc.h> + +/* Count of devices */ +static size_t device_count = 0; + +/******************************************************************************* + * Local functions + ******************************************************************************/ +static void +device_release(ref_T* ref) +{ + struct scad_device* dev; + int ierr = 0; + ASSERT(ref); + dev = CONTAINER_OF(ref, struct scad_device, ref); + MEM_RM(dev->allocator, dev); + if(--device_count == 0) { + gmshFinalize(&ierr); + ASSERT(!ierr); + } +} + +void +log_error(struct scad_device* dev, const char* msg, ...) +{ + va_list vargs_list; + ASSERT(dev && msg); + + 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); + + va_start(vargs_list, msg); + log_msg(dev, LOG_WARNING, msg, vargs_list); + va_end(vargs_list); +} + +res_T +device_set_current_scene(struct scad_scene* scene) +{ + int ierr = 0; + ASSERT(scene); + if(scene->device->current_scene == scene) return RES_OK; + gmshModelSetCurrent(scene->name, &ierr); + if(ierr) scene->device->current_scene = NULL; + else scene->device->current_scene = scene; + return gmsh_err_to_res_T(ierr); +} + +/******************************************************************************* + * Exported scad_device functions + ******************************************************************************/ +res_T +scad_device_create + (struct logger* logger, + struct mem_allocator* mem_allocator, + const int verbose, + struct scad_device** out_dev) +{ + struct scad_device* dev = NULL; + struct mem_allocator* allocator; + /* Current gmsh implementation doesn't allow multiple devices + * Doesn't even define the device concept */ + int ierr = 0; + res_T res = RES_OK; + + if(!out_dev) { + res = RES_BAD_ARG; + goto error; + } + + if(device_count != 0) { + if(verbose && logger) { + res = logger_print(logger, LOG_ERROR, + "scad_device_create can only be called once in a process lifetime."); + ASSERT(res == RES_OK); + } + res = RES_BAD_OP; + goto error; + } + + if(device_count == 0) { + gmshInitialize(0, NULL, 1, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + + allocator = mem_allocator ? mem_allocator : &mem_default_allocator; + dev = (struct scad_device*)MEM_CALLOC(allocator, 1, sizeof(struct scad_device)); + if(!dev) { + res = RES_MEM_ERR; + goto error; + } + dev->logger = logger ? logger : LOGGER_DEFAULT; + dev->allocator = allocator; + dev->verbose = verbose; + ref_init(&dev->ref); + device_count++; + /* Init to default */ + scad_device_set_params(dev, NULL); + +exit: + if(out_dev) *out_dev = dev; + return res; +error: + if(dev) { + SCAD(device_ref_put(dev)); + dev = NULL; + } + goto exit; +} + +res_T +scad_device_set_params + (struct scad_device* device, + const struct scad_device_param* params) +{ + res_T res = RES_OK; + const struct scad_device_param* actual_params + = params ? params : &SCAD_INIT_DEFAULT; + int ierr = 0; + + if(!device) { + res= RES_BAD_ARG; + goto error; + } + + gmshOptionSetNumber("Mesh.StlOneSolidPerSurface", + actual_params->Mesh.StlOneSolidPerSurface, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshOptionSetNumber("Mesh.MeshSizeFromPoints", + actual_params->Mesh.MeshSizeFromPoints, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshOptionSetNumber("Mesh.MeshSizeFromCurvature", + actual_params->Mesh.MeshSizeFromCurvature, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshOptionSetNumber("Mesh.MinimumElementsPerTwoPi", + actual_params->Mesh.MinimumElementsPerTwoPi, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshOptionSetNumber("Mesh.MeshSizeExtendFromBoundary", + actual_params->Mesh.MeshSizeExtendFromBoundary, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + +exit: + return res; +error: + fprintf(stderr, "%s: can't initialize gmsh -- %s\n", + FUNC_NAME, res_to_cstr(res)); + goto exit; +} + +res_T +scad_device_ref_get(struct scad_device* dev) +{ + if(!dev) return RES_BAD_ARG; + ref_get(&dev->ref); + return RES_OK; +} + +res_T +scad_device_ref_put(struct scad_device* dev) +{ + if(!dev) return RES_BAD_ARG; + ref_put(&dev->ref, device_release); + return RES_OK; +} diff --git a/src/scad_device.h b/src/scad_device.h @@ -0,0 +1,82 @@ +/* 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> + +struct scad_device { + struct logger* logger; + struct mem_allocator* allocator; + struct scad_scene* current_scene; + unsigned next_scene_id; + 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 +; + +static INLINE void +log_msg + (struct scad_device* dev, + const enum log_type stream, + const char* msg, + va_list vargs) +{ + ASSERT(dev && msg); + if(dev->verbose) { + res_T res; (void)res; + res = logger_vprint(dev->logger, stream, msg, vargs); + ASSERT(res == RES_OK); + } +} + +extern LOCAL_SYM res_T +device_set_current_scene(struct scad_scene* scene); + +#endif diff --git a/src/scad_geometry.c b/src/scad_geometry.c @@ -0,0 +1,764 @@ +/* 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_c.h" +#include "scad_scene.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 <stdlib.h> +#include <gmshc.h> + +/******************************************************************************* + * Utility functions + ******************************************************************************/ +static res_T +scad_geometry_create + (struct scad_scene* scene, + const char* name, + const unsigned scad_ID, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + struct scad_geometry* geom = NULL; + + ASSERT(scene && out_geometry); + if(name && htable_names_find(&scene->geometry_names, &name)) { + /* if defined, names must be unique */ + res = RES_BAD_ARG; + goto error; + } + geom = (struct scad_geometry*)MEM_CALLOC(scene->device->allocator, 1, + sizeof(*geom)); + if(!geom) { + res = RES_MEM_ERR; + goto error; + } + + str_init(scene->device->allocator, &geom->name); + geom->scene = scene; + geom->scad_ID = scad_ID; + scene->device->need_synchro = 1; + ASSERT(!htable_geometries_find(&scene->attached_geometry, &geom->scad_ID)); + ERR(htable_geometries_set(&scene->attached_geometry, &geom->scad_ID, + &geom)); + if(name) { + ERR(str_set(&geom->name, name)); + ERR(htable_names_set(&scene->geometry_names, &name, &geom)); + } + +end: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto end; +} + + +static void +remove_dimTags + (struct scad_geometry* geom) +{ + ASSERT(geom); + geom->gmsh_dimTags_n = 0; + if(geom->gmsh_dimTags) free(geom->gmsh_dimTags); + geom->gmsh_dimTags = NULL; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +scad_geometry_release + (struct scad_geometry* geom) +{ + struct mem_allocator* allocator; + size_t sz; + int* data; + int ierr; + res_T res = RES_OK; + ASSERT(geom); + allocator = geom->scene->device->allocator; + geom->scene->device->need_synchro = 1; + + sz = geom->gmsh_dimTags_n; + data = geom->gmsh_dimTags; + if(geom->is_group) { + int dimTag[2]; + dimTag[0] = geom->group_dim; + dimTag[1] = geom->is_group; + /* FIXME: not sure this remove is needed! */ + gmshModelGeoRemovePhysicalGroups(dimTag, 2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + if(sz != 0) { + ASSERT(data); + free(data); + } + } + else if(sz != 0) { + gmshModelOccRemove(data, sz, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(data); + free(data); + } + str_release(&geom->name); + MEM_RM(allocator, geom); + +end: + return res; +error: + goto end; +} + +/****************************************************************************** + * Exported functions + *****************************************************************************/ +res_T +scad_scene_add_box + (struct scad_scene* scene, + const char* name, + const double xyz[3], + const double dxdydz[3], + struct scad_geometry** out_geometry) +{ + int ierr, gmsh_ID; + unsigned scad_ID; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !xyz || !dxdydz) { + res = RES_BAD_ARG; + goto error; + } + + ERR(device_set_current_scene(scene)); + gmsh_ID = gmshModelOccAddBox(SPLIT3(xyz), SPLIT3(dxdydz), -1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_ID = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_ID, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags = malloc(geom->gmsh_dimTags_n * sizeof(int)); + if(! geom->gmsh_dimTags_n) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 3; + geom->gmsh_dimTags[1] = gmsh_ID; + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_scene_add_cylinder + (struct scad_scene* scene, + const char* name, + const double xyz[3], + const double axis[3], + const double rad, + const double angle, + struct scad_geometry** out_geometry) +{ + int ierr, gmsh_ID; + unsigned scad_ID; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !xyz || !axis || rad <= 0 || angle < 0 || angle > 2*PI) { + res = RES_BAD_ARG; + goto error; + } + + ERR(device_set_current_scene(scene)); + gmsh_ID = gmshModelOccAddCylinder(SPLIT3(xyz), SPLIT3(axis), rad, -1, angle, + &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_ID = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_ID, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags = malloc(geom->gmsh_dimTags_n * sizeof(int)); + if(! geom->gmsh_dimTags_n) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 3; + geom->gmsh_dimTags[1] = gmsh_ID; + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto exit; +} + +int +scad_scene_add_sphere + (struct scad_scene* scene, + const char* name, + const double xyz[3], + const double rad, + struct scad_geometry** out_geometry) +{ + int ierr, gmsh_ID; + unsigned scad_ID; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !xyz || rad <= 0) { + res = RES_BAD_ARG; + goto error; + } + + ERR(device_set_current_scene(scene)); + gmsh_ID = gmshModelOccAddSphere(SPLIT3(xyz), rad, -1, -PI/2, PI/2, 2*PI, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_ID = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_ID, &geom)); + geom->gmsh_dimTags_n = 2; + geom->gmsh_dimTags = malloc(geom->gmsh_dimTags_n * sizeof(int)); + if(! geom->gmsh_dimTags_n) { + res = RES_MEM_ERR; + goto error; + } + geom->gmsh_dimTags[0] = 3; + geom->gmsh_dimTags[1] = gmsh_ID; + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_scene_create_group + (struct scad_scene* scene, + const char* name, + const int dim, + struct scad_geometry** geometries, + const size_t count, + struct scad_geometry** out_geometry) +{ + int ierr, gid; + struct scad_geometry* geom = NULL; + unsigned scad_id; + size_t sz, i, n; + int* ids = NULL; + res_T res = RES_OK; + + if(!scene || !geometries || (count == 0) || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + if(scene->device->need_synchro) { + ERR(scad_device_synchronize(scene->device)); + } + + sz = 0; + for(i = 0; i < count; i++) { + sz += geometries[i]->gmsh_dimTags_n / 2; + } + ids = malloc(sz * sizeof(int)); + if(! ids) { + res = RES_MEM_ERR; + goto error; + } + n = 0; + for(i = 0; i < count; i++) { + size_t j; + for(j = 1; j < geometries[i]->gmsh_dimTags_n; j+=2) { + ids[n++] = geometries[i]->gmsh_dimTags[j]; + } + } + ASSERT(n == sz); + ERR(device_set_current_scene(scene)); + gid = gmshModelAddPhysicalGroup(dim, ids, count, -1, &ierr); + ASSERT(gid > 0); + ERR(gmsh_err_to_res_T(ierr)); + + scad_id = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_id, &geom)); + geom->is_group = gid; + geom->group_dim = dim; + geom->gmsh_dimTags_n = 2 * sz; + geom->gmsh_dimTags = malloc(geom->gmsh_dimTags_n * sizeof(int)); + if(! geom->gmsh_dimTags_n) { + res = RES_MEM_ERR; + goto error; + } + n = 0; + for(i = 0; i < count; i++) { + size_t j; + for(j = 0; j < geometries[i]->gmsh_dimTags_n; j++) { + geom->gmsh_dimTags[n++] = geometries[i]->gmsh_dimTags[j]; + } + } + ASSERT(n == geom->gmsh_dimTags_n); + +exit: + if(out_geometry) *out_geometry = geom; + if(ids) free(ids); + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_scene_fuse_geometries + (struct scad_scene* scene, + const char* name, + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, + const int remove) +{ + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn, sz1, sz2; + int* data1; + int* data2; + int ierr = 0; + unsigned scad_id; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !geom1 || !geom2 || !out_geometry + || geom1->scene != scene || geom2->scene != scene) + { + res = RES_BAD_ARG; + goto error; + } + + if(scene->device->need_synchro) { + ERR(scad_device_synchronize(scene->device)); + } + + sz1 = geom1->gmsh_dimTags_n; + sz2 = geom2->gmsh_dimTags_n; + data1 = geom1->gmsh_dimTags; + data2 = geom2->gmsh_dimTags; + gmshModelOccFuse(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, remove, remove, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_id = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_id, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = tagout; + if(remove) { /* FIXME: not sure of this! */ + remove_dimTags(geom1); + remove_dimTags(geom2); + } + +exit: + if(out_geometry) *out_geometry = geom; + if(mapn) free(mapn); + if(map) free(map); + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_scene_cut_geometries + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, + const int remove) +{ + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn, sz1, sz2; + int* data1; + int* data2; + int ierr = 0; + unsigned scad_id; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !geom1 || !geom2 || !out_geometry + || geom1->scene != scene || geom2->scene != scene) + { + res = RES_BAD_ARG; + goto error; + } + + if(scene->device->need_synchro) { + ERR(scad_device_synchronize(scene->device)); + } + + sz1 = geom1->gmsh_dimTags_n; + sz2 = geom2->gmsh_dimTags_n; + data1 = geom1->gmsh_dimTags; + data2 = geom2->gmsh_dimTags; + gmshModelOccCut(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, remove, remove, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_id = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_id, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = tagout; + if(remove) { /* FIXME: not sure of this! */ + remove_dimTags(geom1); + remove_dimTags(geom2); + } + +exit: + if(out_geometry) *out_geometry = geom; + if(mapn) free(mapn); + if(map) free(map); + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_scene_intersect_geometries + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, + const int remove) +{ + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn, sz1, sz2; + int* data1; + int* data2; + int ierr = 0; + unsigned scad_id; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !geom1 || !geom2 || !out_geometry + || geom1->scene != scene || geom2->scene != scene) + { + res = RES_BAD_ARG; + goto error; + } + + if(scene->device->need_synchro) { + ERR(scad_device_synchronize(scene->device)); + } + + sz1 = geom1->gmsh_dimTags_n; + sz2 = geom2->gmsh_dimTags_n; + data1 = geom1->gmsh_dimTags; + data2 = geom2->gmsh_dimTags; + gmshModelOccIntersect(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, remove, remove, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_id = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_id, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = tagout; + if(remove) { /* FIXME: not sure of this! */ + remove_dimTags(geom1); + remove_dimTags(geom2); + } + +exit: + if(out_geometry) *out_geometry = geom; + if(mapn) free(mapn); + if(map) free(map); + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + goto exit; +} + +res_T +scad_scene_geometries_common_boundaries + (struct scad_scene* scene, + const char* name, /* Can be NULL */ + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, + const int remove) +{ + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn, sz1, sz2; + int* data1; + int* data2; + int ierr = 0; + unsigned scad_id; + int* bound1; + int* bound2; + size_t n1, n2; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !geom1 || !geom2 || !out_geometry + || geom1->scene != scene || geom2->scene != scene) + { + res = RES_BAD_ARG; + goto error; + } + + if(scene->device->need_synchro) { + ERR(scad_device_synchronize(scene->device)); + } + + sz1 = geom1->gmsh_dimTags_n; + sz2 = geom2->gmsh_dimTags_n; + data1 = geom1->gmsh_dimTags; + data2 = geom2->gmsh_dimTags; + 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)); + + scad_id = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_id, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = tagout; + if(remove) { /* FIXME: not sure of this! */ + remove_dimTags(geom1); + remove_dimTags(geom2); + } + +exit: + if(out_geometry) *out_geometry = geom; + if(bound1) free(bound1); + if(bound2) free(bound2); + if(mapn) free(mapn); + if(map) free(map); + return res; +error: + if(geom) { + SCAD(geometry_release(geom)); + geom = NULL; + } + 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; + } + + if(geom->scene->device->need_synchro) { + ERR(scad_device_synchronize(geom->scene->device)); + } + + sz = geom->gmsh_dimTags_n; + data = geom->gmsh_dimTags; + gmshModelOccRotate(data, sz, SPLIT3(pt), SPLIT3(axis), angle, &ierr); + geom->scene->device->need_synchro = 1; + ERR(gmsh_err_to_res_T(ierr)); + +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; + } + + if(geom->scene->device->need_synchro) { + ERR(scad_device_synchronize(geom->scene->device)); + } + + sz = geom->gmsh_dimTags_n; + data = geom->gmsh_dimTags; + gmshModelOccTranslate(data, sz, SPLIT3(dxdydz), &ierr); + geom->scene->device->need_synchro = 1; + ERR(gmsh_err_to_res_T(ierr)); + +exit: + return res; +error: + goto exit; +} + +res_T +scad_scene_geometries_fragment + (struct scad_scene* scene, + const char* name, + struct scad_geometry* geom1, + struct scad_geometry* geom2, + struct scad_geometry** out_geometry, + const int remove) +{ + int* tagout = NULL; + int** map = NULL; + size_t* mapn = NULL; + size_t tagoutn, mapnn, sz1, sz2; + int* data1; + int* data2; + int ierr = 0; + unsigned scad_id; + struct scad_geometry* geom = NULL; + res_T res = RES_OK; + + if(!scene || !geom1 || !geom2 || !out_geometry + || geom1->scene != scene || geom2->scene != scene) + { + res = RES_BAD_ARG; + goto error; + } + + if(scene->device->need_synchro) { + ERR(scad_device_synchronize(scene->device)); + } + + sz1 = geom1->gmsh_dimTags_n; + sz2 = geom2->gmsh_dimTags_n; + data1 = geom1->gmsh_dimTags; + data2 = geom2->gmsh_dimTags; + gmshModelOccFragment(data1, sz1, data2, sz2, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, remove, remove, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_id = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_id, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = tagout; + if(remove) { /* FIXME: not sure of this! */ + remove_dimTags(geom1); + remove_dimTags(geom2); + } + +exit: + if(out_geometry) *out_geometry = geom; + if(mapn) free(mapn); + if(map) free(map); + return res; +error: + goto exit; +} + +res_T +scad_scene_geometry_boundary + (struct scad_scene* scene, + const char* name, + struct scad_geometry* geom, + struct scad_geometry** out_geometry) +{ + int* tagout = NULL; +size_t tagoutn, sz; + int* data; + int ierr = 0; + unsigned scad_id; + res_T res = RES_OK; + + if(!scene || !geom || !out_geometry || geom->scene != scene) { + res = RES_BAD_ARG; + goto error; + } + + if(scene->device->need_synchro) { + ERR(scad_device_synchronize(scene->device)); + } + + sz = geom->gmsh_dimTags_n; + data = geom->gmsh_dimTags; + gmshModelGetBoundary(data, sz, &tagout, &tagoutn, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + scad_id = scene->current_ID++; + ERR(scad_geometry_create(scene, name, scad_id, &geom)); + geom->gmsh_dimTags_n = tagoutn; + geom->gmsh_dimTags = tagout; + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + goto exit; +} + diff --git a/src/scad_geometry.h b/src/scad_geometry.h @@ -0,0 +1,39 @@ +/* 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> + +struct scad_scene; + +struct scad_geometry { + int* gmsh_dimTags; + size_t gmsh_dimTags_n; + struct scad_scene* scene; + struct str name; + unsigned scad_ID; + int is_group, group_dim; +}; + +extern LOCAL_SYM res_T +scad_geometry_release + (struct scad_geometry* geom); + +#endif diff --git a/src/scad_scene.c b/src/scad_scene.c @@ -0,0 +1,172 @@ +/* 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 /* snprintf */ + +#include "scad.h" +#include "scad_c.h" +#include "scad_scene.h" +#include "scad_device.h" + +#include <rsys/rsys.h> + +#include <stdio.h> +#include <gmshc.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static res_T +set_name + (struct scad_scene* scene) +{ + int n; + ASSERT(scene); + n = snprintf(scene->name, sizeof(scene->name), "%x", + scene->device->next_scene_id++); + if((size_t)n >= sizeof(scene->name)) return RES_BAD_ARG; + return RES_OK; +} + +static void +scene_release(ref_T* ref) +{ + struct scad_scene* scn; + struct mem_allocator* allocator; + struct htable_geometries_iterator it, end; + int ierr = 0; + res_T res; + ASSERT(ref); + scn = CONTAINER_OF(ref, struct scad_scene, ref); + allocator = scn->device->allocator; + htable_geometries_begin(&scn->attached_geometry, &it); + htable_geometries_end(&scn->attached_geometry, &end); + while(!htable_geometries_iterator_eq(&it, &end)) { + struct scad_geometry* geom = *htable_geometries_iterator_data_get(&it); + ASSERT(geom->scene == scn); + SCAD(geometry_release(geom)); + htable_geometries_iterator_next(&it); + } + htable_geometries_release(&scn->attached_geometry); + htable_names_release(&scn->geometry_names); + res = device_set_current_scene(scn); + ASSERT(res == RES_OK); (void)res; + gmshModelRemove(&ierr); + ASSERT(!ierr); (void)ierr; + scn->device->current_scene = NULL; + SCAD(device_ref_put(scn->device)); + MEM_RM(allocator, scn); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ + +/******************************************************************************* + * Exported scad_scene functions + ******************************************************************************/ +res_T +scad_scene_create + (struct scad_device* device, + struct scad_scene** out_scene) +{ + struct scad_scene* scn = NULL; + int ierr = 0; + res_T res = RES_OK; + + if(!out_scene || !device) { + res = RES_BAD_ARG; + goto error; + } + + scn = (struct scad_scene*)MEM_CALLOC(device->allocator, 1, + sizeof(struct scad_scene)); + if(!scn) { + res = RES_MEM_ERR; + goto error; + } + htable_geometries_init(device->allocator, &scn->attached_geometry); + htable_names_init(device->allocator, &scn->geometry_names); + scn->device = device; + set_name(scn); + ref_init(&scn->ref); + SCAD(device_ref_get(scn->device)); + + gmshModelAdd(scn->name, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + device->current_scene = scn; + +exit: + if(out_scene) *out_scene = scn; + return res; +error: + if(scn) { + SCAD(scene_ref_put(scn)); + scn = NULL; + } + goto exit; +} + +res_T +scad_scene_conformal_mesh + (struct scad_scene* scene) +{ + int* dimTags = NULL; + size_t dimTags_n; + int ierr = 0; + res_T res = RES_OK; + + scene->device->need_synchro = 1; + device_set_current_scene(scene); + gmshModelOccSynchronize(&ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshModelOccGetEntities(&dimTags, &dimTags_n, 3, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + if(dimTags_n > 2) { + /* Remove all duplicate entities in the OpenCASCADE CAD representation + * (different entities at the same geometrical location) after intersecting + * (using boolean fragments) all highest dimensional entities. */ + gmshModelOccRemoveAllDuplicates(&ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + gmshModelOccSynchronize(&ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshModelMeshGenerate(2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + +exit: + if(dimTags) free(dimTags); + return res; +error: + goto exit; +} + + +res_T +scad_scene_ref_get(struct scad_scene* scene) +{ + if(!scene) return RES_BAD_ARG; + ref_get(&scene->ref); + return RES_OK; +} + +res_T +scad_scene_ref_put(struct scad_scene* scene) +{ + if(!scene) return RES_BAD_ARG; + ref_put(&scene->ref, scene_release); + return RES_OK; +} diff --git a/src/scad_scene.h b/src/scad_scene.h @@ -0,0 +1,55 @@ +/* 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_SCENE_H +#define SCAD_SCENE_H + +#include "scad.h" + +struct scad_device; +struct scad_geometry; + +#include <rsys/ref_count.h> +#include <rsys/hash_table.h> + +#define HTABLE_NAME geometries +#define HTABLE_DATA struct scad_geometry* +#define HTABLE_KEY unsigned /* scad ID */ +#include <rsys/hash_table.h> + +static INLINE char +name_eq(char* const* a, char* const* b) +{ + ASSERT(a && b); + return strcmp(*a, *b) == 0; +} + +#define HTABLE_NAME names +#define HTABLE_DATA struct scad_geometry* +#define HTABLE_KEY const char* +#define HTABLE_KEY_FUNCTOR_EQ name_eq +#include <rsys/hash_table.h> + +struct scad_scene { + struct scad_device* device; + struct htable_geometries attached_geometry; + struct htable_names geometry_names; + char name[8]; + unsigned current_ID; + + ref_T ref; +}; + +#endif diff --git a/src/test1.c b/src/test1.c @@ -14,8 +14,26 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "scad.h" +#include "scad_geometry.h" -#define ERR(Expr) if((res = (Expr)) != RES_OK) goto error; else (void)0 +#include <rsys/rsys.h> +#include <rsys/math.h> +#include <rsys/mem_allocator.h> + +#include <stdlib.h> + +#define OK(Expr) if((res = (Expr)) != RES_OK) goto error; else (void)0 +#define BAD(Expr) if((res = (Expr)) != RES_BAD_ARG) goto error; else (void)0 + +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"); + } +} int main(int argc, char* argv[]) @@ -25,28 +43,55 @@ main(int argc, char* argv[]) 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; + struct scad_geometry* geom1 = NULL; + struct scad_geometry* geom2 = NULL; + struct scad_geometry* cyl = NULL; + struct scad_geometry* group1 = NULL; + struct scad_geometry* f1 = NULL; + struct scad_geometry* f2 = NULL; + struct scad_geometry* sphere = NULL; + struct scad_geometry* geoms[2]; + struct scad_device* dev = NULL; + struct scad_scene* scene = NULL; + struct mem_allocator allocator; (void)argc; (void)argv; - ERR(scad_init()); - ERR(scad_addbox(p1, d1, &box1)); - ERR(scad_addbox(p2, d2, &box2)); + OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator)); + OK(scad_device_create(NULL, &allocator, 1, &dev)); + OK(scad_scene_create(dev, &scene)); + + OK(scad_scene_add_cylinder(scene, "c1", p1, d1, 2, PI, &geom1)); + OK(scad_scene_add_box(scene, "b1", p2, d2, &geom2)); + OK(scad_stl_export(geom1, NULL, 1)); + OK(scad_stl_export(geom2, NULL, 1)); + geoms[0] = geom1; + geoms[1] = geom2; + OK(scad_scene_create_group(scene, "g1", 3, geoms, 2, &group1)); + OK(scad_stl_export(group1, NULL, 1)); - ERR(scad_conformal_mesh()); -#if 0 - ERR(scad_common_interface(box1, box2, &cut, 0/*no delete*/)); - ERR(scad_stl_export(cut, "mesh.stl")); -#endif + OK(scad_scene_conformal_mesh(scene)); + OK(scad_stl_export_split(group1, "conformal", 1)); + + OK(scad_scene_add_cylinder(scene, "cyl", p1, d1, 1, 2*PI, &cyl)); + OK(scad_stl_export(cyl, NULL, 1)); + + OK(scad_scene_fuse_geometries(scene, "fused1", geom1, cyl, &f1, 0)); + OK(scad_stl_export(f1, NULL, 1)); + + OK(scad_scene_add_sphere(scene, "s1", p1, 1, &sphere)); + OK(scad_scene_fuse_geometries(scene, "fused2", group1, sphere, &f2, 0)); + OK(scad_stl_export(f2, NULL, 0)); exit: - scad_geom_release(box1); - scad_geom_release(box2); - scad_geom_release(cut); - scad_release(); - return res; + if(dev) SCAD(device_ref_put(dev)); + if(scene) SCAD(scene_ref_put(scene)); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + + return (res == RES_OK) ? EXIT_SUCCESS : EXIT_FAILURE; error: goto exit; }