star-cad

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

commit 7dc0f8b4dfcabf20183834e53fac1e4a219c2ea8
parent acb875d4315820ef0da2f1daf90022a30dd914d6
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Fri, 24 Nov 2023 17:00:57 +0100

Merge branch 'release_0.3'

Diffstat:
MREADME.md | 15++++++++++++++-
Mcmake/CMakeLists.txt | 13++++++++++---
Msrc/scad.c | 445+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/scad.h | 238++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/scad_device.c | 417+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/scad_device.h | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/scad_geometry.c | 1135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/scad_geometry.h | 4++--
Msrc/test_api.c | 79++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/test_export.c | 17++++++++++-------
Asrc/test_lifetime.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_partition.c | 8++++----
12 files changed, 1980 insertions(+), 628 deletions(-)

diff --git a/README.md b/README.md @@ -21,7 +21,20 @@ project from the `cmake/CMakeLists.txt` file by appending to the ## Release notes -### Version 0.1.1 +### Version 0.3 + +This release largely breaks the API. + + - Add API calls to attach custom data to geometries + - Increase default min size for generated meshes + - Add API calls to deals with the entities hidden in geometries + - Change geometry swap API + - Remove redundant API calls + - BugFixes on geometries lifetime management + - Log messages improvement + - Many bug fixes + +### Version 0.2 - Introduce reference counting for geometries (API break). - Add the scad_stl_data_write API call to write a vector of coordinate as an STL diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -26,18 +26,24 @@ option(NO_TEST "Disable the test" OFF) find_package(gmsh 4.9.5 REQUIRED) find_package(RCMake 0.4.1 REQUIRED) find_package(RSys 0.12.1 REQUIRED) +find_package(StarGeom3D 0.1 REQUIRED) +find_package(StarEnc3D 0.5 REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) include(rcmake) include(rcmake_runtime) -include_directories(${GMSH_INCLUDE_DIR} ${RSys_INCLUDE_DIR}) +include_directories( + ${GMSH_INCLUDE_DIR} + ${RSys_INCLUDE_DIR} + ${StarGeom3D_INCLUDE_DIR} + ${StarEnc3D_INCLUDE_DIR}) ################################################################################ # Configure and define targets ################################################################################ set(VERSION_MAJOR 0) -set(VERSION_MINOR 2) +set(VERSION_MINOR 3) set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) @@ -58,7 +64,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_LIBRARY} m) +target_link_libraries(scad RSys ${GMSH_LIBRARY} m StarGeom3D StarEnc3D) set_target_properties(scad PROPERTIES DEFINE_SYMBOL SCAD_SHARED_BUILD @@ -94,6 +100,7 @@ if(NOT NO_TEST) new_test(test_api) new_test(test_export) new_test(test_partition) + new_test(test_lifetime) rcmake_copy_runtime_libraries(test) diff --git a/src/scad.c b/src/scad.c @@ -28,7 +28,9 @@ #include <rsys/double3.h> #include <rsys/float3.h> -#include <star/sstl.h> +#include <star/sg3d.h> +#include <star/sg3d_sencXd_helper.h> +#include <star/senc3d.h> #include <stdio.h> #include <stdlib.h> @@ -46,14 +48,15 @@ int_compare(const void *a_, const void *b_) { static res_T write_ascii_stl (const char* filename, - double* coord, - const size_t coord_n) + double* coords, + const size_t count) { res_T res = RES_OK; size_t i; + size_t actual_trg_count = 0; FILE* stl_file = NULL; - ASSERT(filename && (coord || coord_n == 0) && (coord_n % 9 == 0)); + ASSERT(filename && (coords || count == 0)); stl_file = fopen(filename, "w"); if(!stl_file) { @@ -63,23 +66,17 @@ write_ascii_stl OKP(fprintf(stl_file, "solid %s\n", filename)); - /* trg_count triangles split in coord_n blocs */ - for(i = 0; i < coord_n; i += 9) { + for(i = 0; i < count; i++) { 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+0); - d3_set(vtx[1], coord+i+3); - d3_set(vtx[2], coord+i+6); + double vtx[3][3], tmp[3], edge1[3], edge2[3], n[3] = { 0,0,0 }; + const double zero[3] = { 0,0,0 }; + d3_set(vtx[0], coords+9*i+0); + d3_set(vtx[1], coords+9*i+3); + d3_set(vtx[2], coords+9*i+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; - } + if(d3_eq(tmp, zero)) continue; d3_normalize(n, tmp); OKP(fprintf(stl_file, " facet normal %g %g %g\n", SPLIT3(n))); @@ -89,8 +86,16 @@ write_ascii_stl } OKP(fprintf(stl_file, " endloop\n")); OKP(fprintf(stl_file, " endfacet\n")); + actual_trg_count++; } OKP(fprintf(stl_file, "endsolid \n")); + if(actual_trg_count != count) { + long long unsigned del_count = count - actual_trg_count; + ASSERT(actual_trg_count < count); + log_warning(get_device(), + "In file '%s': %llu nul triangle(s) removed in exported geometry.\n", + filename, del_count); + } exit: if(stl_file) fclose(stl_file); @@ -104,19 +109,20 @@ error: static res_T write_binary_stl (const char* filename, - double* coord, - const size_t coord_n) + double* coords, + const size_t count) { res_T res = RES_OK; - size_t i; - unsigned trg_count; + unsigned i; + unsigned trg_count, actual_trg_count = 0; char header[80] = "Binary STL"; FILE* stl_file = NULL; + long int offset; - ASSERT(filename && (coord || coord_n == 0) && (coord_n % 9 == 0)); + ASSERT(filename && (coords || count == 0)); stl_file = fopen(filename, "wb"); - if(!stl_file) { + if(!stl_file || count > UINT_MAX) { res = RES_IO_ERR; goto error; } @@ -126,32 +132,49 @@ write_binary_stl goto error; } - trg_count = (unsigned)coord_n / 9; + trg_count = (unsigned)count; + offset = ftell(stl_file); if(1 != fwrite(&trg_count, 4, 1, stl_file)) { res = RES_IO_ERR; goto error; } - /* trg_count triangles split in coord_n blocs */ - for(i = 0; i < coord_n; i += 9) { + for(i = 0; i < trg_count; i++) { + float tmp[3], edge1[3], edge2[3], zero[3] = { 0,0,0 }; 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+0); - f3_set_d3(trg.vrtx[1], coord+i+3); - f3_set_d3(trg.vrtx[2], coord+i+6); + f3_set_d3(trg.vrtx[0], coords+9*i+0); + f3_set_d3(trg.vrtx[1], coords+9*i+3); + f3_set_d3(trg.vrtx[2], coords+9*i+6); f3_sub(edge1, trg.vrtx[1], trg.vrtx[0]); f3_sub(edge2, trg.vrtx[2], trg.vrtx[0]); f3_cross(tmp, edge1, edge2); + if(f3_eq(tmp, zero)) continue; f3_normalize(trg.n, tmp); trg.attrib = 0; if(1 != fwrite(&trg, 50, 1, stl_file)) { res = RES_IO_ERR; goto error; } + actual_trg_count++; + } + if(actual_trg_count != trg_count) { + ASSERT(actual_trg_count < trg_count); + log_warning(get_device(), + "In file '%s': %u nul triangle(s) removed in exported geometry.\n", + filename, trg_count - actual_trg_count); + /* Need to change count */ + if(0 != fseek(stl_file, offset, SEEK_SET)) { + res = RES_IO_ERR; + goto error; + } + if(1 != fwrite(&actual_trg_count, 4, 1, stl_file)) { + res = RES_IO_ERR; + goto error; + } } exit: @@ -189,8 +212,6 @@ get_2d_tags if(dim == 3) { int* face_dimTags = NULL; size_t face_dimTags_n, count; - gmshModelMeshSetOutwardOrientation(tag, &ierr); - ERR(gmsh_err_to_res_T(ierr)); /* Get 2d components of the 3d object */ gmshModelGetBoundary(data+i, 2, &face_dimTags, &face_dimTags_n, 1, 0, 0, &ierr); dimTags_to_free = face_dimTags; @@ -206,7 +227,7 @@ get_2d_tags gmshFree(dimTags_to_free); dimTags_to_free = NULL; } else if(dim == 2) { - ERR(darray_int_push_back(tags, &data[i+1])); + ERR(darray_int_push_back(tags, &tag)); } else { res = RES_BAD_ARG; goto error; @@ -280,6 +301,18 @@ error: goto exit; } +size_t +scad_get_dimtag_refcount + (const int dim, + const int tag) +{ + struct tag_desc* desc = device_get_description(dim, tag); + if(!desc) return SIZE_MAX; + + return desc->refcount; +} + + res_T scad_scene_write (const char* name) @@ -319,7 +352,7 @@ scad_stl_get_data_partial size_t* nodeTags = NULL; double* coord = NULL; double* pCoord = NULL; - size_t i, tcount, sz, dsz = 0; + size_t i, count0, tcount, sz, dsz = 0; int ierr = 0; int *data, *ddata = NULL; struct scad_device* dev = get_device(); @@ -357,31 +390,41 @@ scad_stl_get_data_partial } /* Collect triangles */ - tcount = 0; + count0 = tcount = darray_double_size_get(triangles); for(i = 0; i < sz; i++) { - size_t count, j, coord_n, pCoord_n, nodeTags_n; + size_t j, coord_n, pCoord_n, nodeTags_n; const int type = 2; /* 3-node triangles (see src/common/GmshDefines.h) */ - if(bsearch(data+i, ddata, dsz, sizeof(*ddata), int_compare)) { + const int tag = data[i]; + if(bsearch(&tag, ddata, dsz, sizeof(*ddata), int_compare)) { /* this tag is part of the dont list: don't collect */ continue; } gmshModelMeshGetNodesByElementType(type, &nodeTags, &nodeTags_n, - &coord, &coord_n, &pCoord, &pCoord_n, data[i], 0, &ierr); + &coord, &coord_n, &pCoord, &pCoord_n, tag, 0, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ASSERT(nodeTags_n % 3 == 0); - ASSERT(coord_n == nodeTags_n * 3); tcount += coord_n; - count = darray_double_size_get(triangles); - ERR(darray_double_reserve(triangles, count + coord_n)); - for(j = 0; j < coord_n; j++) { - ERR(darray_double_push_back(triangles, &coord[j])); + ERR(darray_double_reserve(triangles, tcount)); + for(j = 0; j < coord_n; j += 9) { +#define PUSH3(A, D) { \ + ERR(darray_double_push_back((A), (D)+0)); \ + ERR(darray_double_push_back((A), (D)+1)); \ + ERR(darray_double_push_back((A), (D)+2)); \ +} + /* Keep the triangle; if its normal is reversed, change vertices' order */ + PUSH3(triangles, coord+j+0); + PUSH3(triangles, coord+j+3); + PUSH3(triangles, coord+j+6); } +#undef PUSH3 gmshFree(nodeTags); nodeTags = NULL; gmshFree(coord); coord = NULL; gmshFree(pCoord); pCoord = NULL; } ASSERT(tcount == darray_double_size_get(triangles)); - tcount /= 9; + if(count0 == tcount) { + log_message(dev, "No triangle collected for geometry '%s'.\n", + str_cget(&geometry->name)); + } exit: if(!allocator) { @@ -404,15 +447,243 @@ error: goto exit; } +static INLINE void +get_indices(const unsigned itri, unsigned ids[3], void* context) +{ + const double* ctx = context; + ASSERT(ids && ctx); (void)ctx; + ids[0] = itri * 3 + 0; + ids[1] = itri * 3 + 1; + ids[2] = itri * 3 + 2; +} + +static INLINE void +get_position(const unsigned ivert, double pos[3], void* context) +{ + const double* ctx = context; + ASSERT(pos && ctx); + pos[0] = ctx[ivert * 3 + 0]; + pos[1] = ctx[ivert * 3 + 1]; + pos[2] = ctx[ivert * 3 + 2]; +} + +static res_T +scad_stl_sort_orientation + (struct darray_double* triangles, + const char* set_name, + const enum scad_normals_orientation orientation) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = dev->allocator; + struct logger* logger = dev->logger; + double* coord; + size_t coord_n; + unsigned i, ecount, tcount_in, vcount_in, tcount, vcount; + struct sg3d_device* sg3d = NULL; + struct sg3d_geometry* geom = NULL; + struct senc3d_device* senc3d = NULL; + struct senc3d_scene* senc3d_scn = NULL; + struct senc3d_enclosure* enclosure = NULL; + struct senc3d_enclosure_header header; + int convention, initialized = 0; + struct sg3d_geometry_add_callbacks callbacks = SG3D_ADD_CALLBACKS_NULL__; + struct darray_double new_triangles; + char *process = NULL, *contact_0 = NULL; + unsigned ocount = 0, process_count = 0; + + if(!triangles || !set_name) { + res = RES_BAD_ARG; + goto error; + } + + coord_n = darray_double_size_get(triangles); + if(coord_n % 9 || coord_n > UINT_MAX) { + res = RES_BAD_ARG; + goto error; + } + if(coord_n == 0 || orientation == Scad_keep_normals_unchanged) { + goto exit; + } + + tcount_in = (unsigned)(coord_n/9); + vcount_in = tcount_in * 3; + coord = darray_double_data_get(triangles); + + ERR(sg3d_device_create(logger, allocator, dev->verbose, &sg3d)); + ERR(sg3d_geometry_create(sg3d, &geom)); + + callbacks.get_indices = get_indices; + callbacks.get_position = get_position; + + ERR(sg3d_geometry_reserve(geom, vcount_in, tcount_in, 0)); + ERR(sg3d_geometry_add(geom, vcount_in, tcount_in, &callbacks, coord)); + + ERR(sg3d_geometry_get_unique_vertices_count(geom, &vcount)); + ERR(sg3d_geometry_get_unique_triangles_count(geom, &tcount)); + if(tcount != tcount_in) { + ASSERT(tcount_in > tcount); + log_warning(get_device(), + "Triangles duplicates were found when sorting out normals (%u / %u) " + "in set '%s'.\n", + tcount_in - tcount, tcount_in, set_name); + } + if(orientation == Scad_force_normals_outward) + convention = SENC3D_CONVENTION_NORMAL_BACK | SENC3D_CONVENTION_NORMAL_OUTSIDE; + else + convention = SENC3D_CONVENTION_NORMAL_BACK | SENC3D_CONVENTION_NORMAL_INSIDE; + + ERR(senc3d_device_create(logger, allocator, + dev->options.Geometry.OCCParallel ? SENC3D_NTHREADS_DEFAULT : 1, + dev->verbose, &senc3d)); + ERR(senc3d_scene_create(senc3d, convention, + tcount, sg3d_sencXd_geometry_get_indices, NULL, + vcount, sg3d_sencXd_geometry_get_position, geom, &senc3d_scn)); + + ERR(senc3d_scene_get_overlapping_triangles_count(senc3d_scn, &ocount)); + if(ocount) { + logger_print(logger, LOG_ERROR, + "Overlapping triangles where found when sorting out normals (%u / %u) " + "in set '%s'.\n", + tcount_in - ocount, tcount_in, set_name); + res = RES_BAD_ARG; + goto error; + } + + ERR(senc3d_scene_get_enclosure_count(senc3d_scn, &ecount)); + if(ecount < 2) { + /* Must define a closed volume */ + log_error(get_device(), + "Triangle set '%s' doesn't define a closed volume.\n", set_name); + res = RES_BAD_ARG; + goto error; + } else { + /* Enclosure 0 is allways present and represents the outside of the whole + * scene. Enclosures with rank 1+ are closed enclosures: normal orientation + * makes sense there. + * We will only process triangles that limit the enclosures that have + * enclosure 0 on the other side. If we processed any enclosure, possible + * internal enclosures would trigger a double processing of some triangles. + * Finally, no enclosure is allowed other than 1) enclosures in contact with + * enclosure 0, or 2) enclosures in contact with enclosures in 1). */ + unsigned e, enclosures[2]; + darray_double_init(allocator, &new_triangles); + initialized = 1; + process = MEM_CALLOC(allocator, ecount, sizeof(*process)); + contact_0 = MEM_CALLOC(allocator, ecount, sizeof(*contact_0)); + if(!process || !contact_0) { + res = RES_MEM_ERR; + goto error; + } + for(e = 0; e < ecount; e++) { + ERR(senc3d_scene_get_enclosure(senc3d_scn, e, &enclosure)); + ERR(senc3d_enclosure_get_header(enclosure, &header)); + if(header.primitives_count != header.unique_primitives_count) { + log_error(get_device(), + "Triangle set '%s' define an invalid closed volume " + "(both sides of a triangle are in).\n", + set_name); + res = RES_BAD_ARG; + goto error; + } + if(e == 0) { + ERR(senc3d_enclosure_ref_put(enclosure)); + enclosure = NULL; + continue; + } + for(i = 0; i < header.unique_primitives_count; i++) { + enum senc3d_side side; + unsigned gid; + ERR(senc3d_enclosure_get_triangle_id(enclosure, i, &gid, &side)); + ERR(senc3d_scene_get_triangle_enclosures(senc3d_scn, gid, enclosures)); + if(enclosures[0] == 0 || enclosures[1] == 0) { + process_count++; + process[e] = 1; + contact_0[e] = 1; + break; + } + } + ERR(senc3d_enclosure_ref_put(enclosure)); + enclosure = NULL; + } + for(e = 1; e < ecount; e++) { + if(process[e]) continue; + ERR(senc3d_scene_get_enclosure(senc3d_scn, e, &enclosure)); + ERR(senc3d_enclosure_get_header(enclosure, &header)); + for(i = 0; i < header.unique_primitives_count; i++) { + enum senc3d_side side; + unsigned gid; + ERR(senc3d_enclosure_get_triangle_id(enclosure, i, &gid, &side)); + ERR(senc3d_scene_get_triangle_enclosures(senc3d_scn, gid, enclosures)); + if(contact_0[enclosures[0]] || contact_0[enclosures[1]]) { + process_count++; + process[e] = 1; + break; + } + } + ERR(senc3d_enclosure_ref_put(enclosure)); + enclosure = NULL; + } + if(process_count != ecount -1) { + log_error(get_device(), + "Triangle set '%s' defines a topology that doesn't allow forcing normals.\n", + set_name); + res = RES_BAD_ARG; + goto error; + } + /* Just a guess */ + ERR(darray_double_reserve(&new_triangles, 9 * tcount_in)); + for(e = 1; e < ecount; e++) { + ERR(senc3d_scene_get_enclosure(senc3d_scn, e, &enclosure)); + ERR(senc3d_enclosure_get_header(enclosure, &header)); + for(i = 0; i < header.unique_primitives_count; i++) { + unsigned n, k, vrt[3]; + /* Get the vertices as they are ordered in the enclosure's mesh */ + ERR(senc3d_enclosure_get_triangle(enclosure, i, vrt)); + /* Rewrite vertices according to enclosure's mesh */ + for(n = 0; n < 3; n++) { + double pos[3]; + ERR(senc3d_enclosure_get_vertex(enclosure, vrt[n], pos)); + for(k = 0; k < 3; k++) { + ERR(darray_double_push_back(&new_triangles, pos+k)); + } + } + } + ERR(senc3d_enclosure_ref_put(enclosure)); + enclosure = NULL; + } + ERR(darray_double_copy_and_release(triangles, &new_triangles)); + initialized = 0; + } + +exit: + MEM_RM(allocator, process); + MEM_RM(allocator, contact_0); + if(initialized) darray_double_release(&new_triangles); + if(sg3d) SG3D(device_ref_put(sg3d)); + if(geom) SG3D(geometry_ref_put(geom)); + if(senc3d) SENC3D(device_ref_put(senc3d)); + if(senc3d_scn) SENC3D(scene_ref_put(senc3d_scn)); + if(enclosure) SENC3D(enclosure_ref_put(enclosure)); + return res; +error: + goto exit; +} + res_T scad_stl_data_write - (struct darray_double* triangles, + (const struct darray_double* triangles, const char* filename, + const enum scad_normals_orientation orientation, const int binary) { res_T res = RES_OK; - size_t coord_n; + res_T tmp_res = RES_OK; + struct scad_device* dev = get_device(); + struct darray_double sorted; + int initialized = 0; double* coord; + size_t coord_n; if(!triangles || !filename) { res = RES_BAD_ARG; @@ -424,12 +695,21 @@ scad_stl_data_write res = RES_BAD_ARG; goto error; } - coord = darray_double_data_get(triangles); - if(binary) ERR( write_binary_stl(filename, coord, coord_n)); - else ERR(write_ascii_stl(filename, coord, coord_n)); + darray_double_init(dev->allocator, &sorted); + initialized = 1; + ERR(darray_double_copy(&sorted, triangles)); + + /* If sort_orientation fails, try to write the file anyway to allow debugging */ + tmp_res = scad_stl_sort_orientation(&sorted, filename, orientation); + coord = darray_double_data_get(&sorted); + coord_n = darray_double_size_get(triangles); + if(binary) ERR(write_binary_stl(filename, coord, coord_n/9)); + else ERR(write_ascii_stl(filename, coord, coord_n/9)); + ERR(tmp_res); exit: + if(initialized) darray_double_release(&sorted); return res; error: goto exit; @@ -438,7 +718,8 @@ error: res_T scad_stl_export (struct scad_geometry* geometry, - const char* prefix, + const char* file_name, + const enum scad_normals_orientation orientation, const int binary) { res_T res = RES_OK; @@ -447,8 +728,8 @@ scad_stl_export struct str filename; int initialized = 0; - if(!geometry || (!prefix && str_is_empty(&geometry->name))) { - res= RES_BAD_ARG; + if(!geometry || (!file_name && str_is_empty(&geometry->name))) { + res = RES_BAD_ARG; goto error; } @@ -457,14 +738,18 @@ scad_stl_export darray_double_init(dev->allocator, &trg); str_init(dev->allocator, &filename); initialized = 1; - if(prefix) { - ERR(str_set(&filename, prefix)); + if(file_name) { + ERR(str_set(&filename, file_name)); } else { + if(str_is_empty(&geometry->name)) { + res = RES_BAD_ARG; + goto error; + } ERR(str_copy(&filename, &geometry->name)); } ERR(str_append(&filename, ".stl")); ERR(scad_stl_get_data(geometry, &trg)); - ERR(scad_stl_data_write(&trg, str_cget(&filename), binary)); + ERR(scad_stl_data_write(&trg, str_cget(&filename), orientation, binary)); exit: if(initialized) { @@ -481,7 +766,8 @@ scad_stl_export_partial (struct scad_geometry* geometry, struct scad_geometry** dont, const size_t dont_count, - const char* prefix, + const char* file_name, + const enum scad_normals_orientation orientation, const int binary) { res_T res = RES_OK; @@ -491,7 +777,7 @@ scad_stl_export_partial int initialized = 0; if(!geometry) { - res= RES_BAD_ARG; + res = RES_BAD_ARG; goto error; } @@ -500,10 +786,10 @@ scad_stl_export_partial darray_double_init(dev->allocator, &trg); str_init(dev->allocator, &filename); initialized = 1; - if(prefix) { - ERR(str_set(&filename, prefix)); + if(file_name) { + ERR(str_set(&filename, file_name)); } else { - if(str_len(&geometry->name) == 0) { + if(str_is_empty(&geometry->name)) { res = RES_BAD_ARG; goto error; } @@ -511,7 +797,7 @@ scad_stl_export_partial } ERR(str_append(&filename, ".stl")); ERR(scad_stl_get_data_partial(geometry, dont, dont_count, &trg)); - ERR(scad_stl_data_write(&trg, str_cget(&filename), binary)); + ERR(scad_stl_data_write(&trg, str_cget(&filename), orientation, binary)); exit: if(initialized) { @@ -526,7 +812,8 @@ error: res_T scad_stl_export_split (struct scad_geometry* geometry, - const char* prefix, + const char* file_name, + const enum scad_normals_orientation orientation, const int binary) { size_t i; @@ -536,21 +823,24 @@ scad_stl_export_split res_T res = RES_OK; (void)binary; - if(!geometry || (!prefix && str_is_empty(&geometry->name))) { + if(!geometry || (!file_name && str_is_empty(&geometry->name))) { res = RES_BAD_ARG; goto error; } ERR(check_device(FUNC_NAME)); - ERR(scad_geometry_explode(geometry, prefix, &parts, &count)); + ERR(scad_geometry_explode(geometry, file_name, &parts, &count)); ASSERT(count*2 == geometry->gmsh_dimTags_n); for(i = 0; i < count; i++) { - ERR(scad_stl_export(parts[i], NULL, binary)); + ERR(scad_stl_export(parts[i], NULL, orientation, binary)); } exit: - MEM_RM(dev->allocator, parts); + if(parts) { + for(i = 0; i < count; i++) SCAD(geometry_ref_put(parts[i])); + MEM_RM(dev->allocator, parts); + } return res; error: goto exit; @@ -573,24 +863,3 @@ exit: error: goto exit; } - -res_T -scad_scene_partition - (void) -{ - int ierr = 0; - res_T res = RES_OK; - - ERR(check_device(FUNC_NAME)); - - gmshModelOccRemoveAllDuplicates(&ierr); - ERR(gmsh_err_to_res_T(ierr)); - - gmshModelOccSynchronize(&ierr); - ERR(gmsh_err_to_res_T(ierr)); - -exit: - return res; -error: - goto exit; -} diff --git a/src/scad.h b/src/scad.h @@ -78,9 +78,10 @@ enum scad_stl_solids { }; enum scad_log_refcounting { - Scad_log_none, - Scad_log_only_undeleted, - Scad_log_all + Scad_log_none = 0, + Scad_log_dimTags_only_undeleted = BIT(0), + Scad_log_dimTags_all = BIT(1), + Scad_log_geometry = BIT(2) }; /* A type to specify options for the gmsh library */ @@ -106,21 +107,34 @@ struct scad_options { struct { int Step; /* Run UI when entering any scad API function; requires a FLTK-enabled gmsh build */ int SynchronizeOnRunUI; - enum scad_log_refcounting LogOpenCascadeTagsRefCounting; + enum scad_log_refcounting LogRefCounting; 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, \ + { { Scad_frontal_Delaunay, Scad_surfaces_and_volumes, 1, 36, 1, 1e+22, 1e-6, \ 1, Scad_one_solid_per_physical_surface }, \ { Scad_verbosity_errors, 1 }, \ { 1 }, \ - { 0, Scad_log_none, 0, 0 } \ + { 0, 0, Scad_log_none, 0 } \ } static const struct scad_options SCAD_DEFAULT_OPTIONS = SCAD_DEFAULT_OPTIONS__; +/* A type to specify what to swap in geometries_swap calls */ +enum scad_swap_elements { + Scad_swap_name = BIT(0), + Scad_swap_geometry = BIT(1) +}; + +/* A type to specify normals' orientation when writing STL files. */ +enum scad_normals_orientation { + Scad_keep_normals_unchanged, /* The only one allowed with non closed shapes */ + Scad_force_normals_outward, + Scad_force_normals_inward +}; + BEGIN_DECLS /******************************************************************************* @@ -145,17 +159,10 @@ 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. + * If provided, name must be unique. ******************************************************************************/ SCAD_API res_T @@ -176,6 +183,22 @@ scad_geometry_get_count (const struct scad_geometry* geom, size_t* count); +/* Attach some custom data `data' to geometry `geom'. + * If provided, release() is called when `geom' is released or if + * set_custom_data is called again. */ +SCAD_API res_T +scad_geometry_set_custom_data + (struct scad_geometry* geom, + void (*release) (void* data), /* Can be NULL */ + void* data); /* Can be NULL */ + +/* Get the custom data attached to geometry `geom'. + * If set_custom_data has not been called before, return NULL. */ +SCAD_API res_T +scad_geometry_get_custom_data + (struct scad_geometry* geom, + void** data); + /* 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') */ @@ -184,11 +207,14 @@ scad_geometry_get_name (const struct scad_geometry* geom, const char** name); -/* Swap names of `geom1' and `geom2' */ +/* Swap the internals of geometry pools (swap pool1[i] and pool2[i]); what is + * swapped is set usig flags. Pools must have the same count. */ SCAD_API res_T -scad_geometry_swap_names - (struct scad_geometry* geom1, - struct scad_geometry* geom2); +scad_geometries_swap + (struct scad_geometry** pool1, + struct scad_geometry** pool2, + const size_t count, + const int flags); /* Get the `mass' of the geometry `geom'. It means area for a 2D geometry and * volume for a 3D geometry. */ @@ -262,6 +288,34 @@ scad_add_sphere const double radius, struct scad_geometry** sphere); +/* Check if geometries `geom1' and `geom2' have the same content, that is: + * - are the same scad_geometries (trivial case), + * - contain the same internal entities. + * To check if 2 geometries are "equivalent", one as to apply boolean operators + * (e.g. cut) and check the result accordingly (e.g. empty result). */ +SCAD_API res_T +scad_geometries_equal + (struct scad_geometry* geom1, + struct scad_geometry* geom2, + int* equal); + +/* Check if all the entities of `geometry' are part of the geometries in + * `geometries'. */ +SCAD_API res_T +scad_geometry_is_included + (struct scad_geometry* geometry, + struct scad_geometry** geometries, + const size_t geometries_count, + int* included); + +/* Create a new geometry made from all the entities from `geometries'. */ +SCAD_API res_T +scad_collect_geometries + (const char* name, /* Can be NULL */ + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** out_geometry); + /* Compute the boolean union (the fusion) of the geometries in `geometries' and * `tools'. */ SCAD_API res_T @@ -309,9 +363,7 @@ scad_geometries_common_boundaries /* 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. + * The output geometries are created unnamed. * 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. */ @@ -320,22 +372,7 @@ 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); + struct scad_geometry** out_geometries); /* Get the boundary of the geometry `geom'. */ SCAD_API res_T @@ -358,37 +395,48 @@ scad_geometry_rename (struct scad_geometry* geom, const char* name); /* Can be NULL */ -/* Reverse the orientation of the geomery `geome' */ +/* 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_reverse - (struct scad_geometry* geom); +scad_geometry_dilate + (const struct scad_geometry* geom, + const double center[3], + const double scale[3], + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry); /* Translate the geometry `geom' along (`dx', `dy', `dz'). */ SCAD_API res_T scad_geometry_translate - (struct scad_geometry* geom, - const double dxdydz[3]); + (const struct scad_geometry* geom, + const double dxdydz[3], + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry); /* 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 struct scad_geometry* geom, const double pt[3], const double dir[3], - const double angle); + const double angle, + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry); /* Extrude the geometry `geom' using a translation along (`dx', `dy', `dz'). */ SCAD_API res_T scad_geometry_extrude - (const struct scad_geometry* geom, + (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'. - * Note that `out_geometries' is allocated using the allocator provided when - * initializing star-cad and should be freed accordingly. */ + * The output geometries are named <base>_<rank>, where <base> is either + * prefix_name or geom's name, and <rank> counting from 0. + * The result `out_geometries' being allocated using the allocator provided when + * initializing star-cad, it should be freed accordingly. */ SCAD_API res_T scad_geometry_explode (const struct scad_geometry* geom, @@ -407,70 +455,79 @@ scad_step_import struct scad_geometry*** out_geometries, size_t* out_geometry_n); -/* Export the geometry `geom' to an STL file. +/* Export the mesh of geometry `geom' to an STL file. * In order to get a mesh, one has to call scad_scene_mesh before calling this. - * If `prefix' is provided it is used to name the file (just adding .stl), + * If `file_name' 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. */ + * file_name nor the geometry name is defined). Mesh orientation can be forced + * inward or outward only if it defines a closed volume. 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 char* file_name, + const enum scad_normals_orientation orientation, const int binary); /* File format */ -/* Same as previous, but geometries that are part of `exclude' is not exported */ +/* Same as previous, but geometries that are part of `exclude' are not exported. */ SCAD_API res_T scad_stl_export_partial (struct scad_geometry* geometry, struct scad_geometry** exclude, const size_t exclude_count, - const char* prefix, + const char* file_name, + const enum scad_normals_orientation orientation, const int binary); -/* Export the geometry `geom' in as many files than its count. */ +/* Export the geometry `geom' in as many files than its count. + * The files are named <base>_<rank>.stl, where <base> is either file_name or + * geom's name, and <rank> counting from 0. */ 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 char* file_name, + const enum scad_normals_orientation orientation, const int binary); /* File format */ -/* Get the mesh of the geometry `geom' into a darray_double, each triangle - * described * by 9 doubles in a STL way. - * In order to get a mesh, one has to call scad_scene_mesh before calling this. */ +/* Accumulate the mesh of the geometry `geom' into `triangles', each triangle + * being described by 9 doubles in a STL way. + * In order to get a mesh, one has to call scad_scene_mesh first. */ SCAD_API res_T scad_stl_get_data (struct scad_geometry* geom, - struct darray_double* triangles); /* Can contain some triangles already */ + struct darray_double* triangles); -/* Same as previous, but geometries that are part of `exclude' is not collected */ +/* Same as previous, but geometries in `exclude', that can be 2D and/or 3D, are + * not collected. */ SCAD_API res_T scad_stl_get_data_partial (struct scad_geometry* geometry, struct scad_geometry** exclude, const size_t exclude_count, - struct darray_double* triangles); /* Can contain some triangles already */ + struct darray_double* triangles); -/* Write the mesh the same way stl_export do, using data as returned by - * stl_get_data[_partial] */ +/* Write a mesh the same way stl_export do, using `triangles' as returned by + * stl_get_data[_partial]. */ SCAD_API res_T scad_stl_data_write - (struct darray_double* triangles, + (const struct darray_double* triangles, const char* filename, + const enum scad_normals_orientation orientation, const int binary); +/* Write the whole scene in a format that depends on the file name extension. */ SCAD_API res_T scad_scene_write (const char* name); -SCAD_API res_T /* FIXME remove this */ -scad_run_ui - (void); - +/* Create the mesh of the whole scene. */ SCAD_API res_T scad_scene_mesh (void); +/* Get the normal of the geometry `geom' at position `p'. + * The normal is set in `N' and the underlying 2D entity to which `p' belongs is + * returned as a new geometry in `out_geometry'. */ SCAD_API res_T scad_geometry_normal (struct scad_geometry* geom, @@ -479,15 +536,46 @@ scad_geometry_normal 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. */ +/* The following API calls are meant for debugging purposes. + * They can be called from gdb. */ + +/* Open gmsh in GUI mode so that the model can be inspected and even modified. + * To use it from gdb: + * (gdb) call scad_run_ui() + * (then close gmsh and gdb is back to the breakpoint). */ SCAD_API res_T -scad_geometry_dilate - (struct scad_geometry* geom, - double center[3], - double scale[3]); +scad_run_ui + (void); +/* Explicitly synchronize the current state with regard to recent geometry changes. + * Note however that synchronize calls should be automatically triggered when + * needed. + * To use it from gdb: + * (gdb) call scad_synchronize() + * Only provided as a way to check for auto-synchronize bugs! */ +SCAD_API res_T +scad_synchronize + (void); +/* Get the refcount, as managed by star-cad to trigger remove calls on dim.tag + * OCC internal entities. Return SIZE_MAX if dim.tag is not valid in the OCC + * context. + * To use it from gdb: + * (gdb) call scad_get_dimtag_refcount(3, 1) + * $5 = 7 + */ +SCAD_API size_t +scad_get_dimtag_refcount + (const int dim, + const int tag); + +/* Dump all the geometries with address/name, ref count and tags. + * To use it from gdb: + * (gdb) call scad_dump_geometries() + */ +SCAD_API void +scad_dump_geometries + (void); END_DECLS #endif /* SCAD_H */ diff --git a/src/scad_device.c b/src/scad_device.c @@ -31,8 +31,61 @@ * Local functions ******************************************************************************/ static void +device_release_tags_of_dim + (struct scad_device* dev, + const int dim) +{ + struct htable_tags2desc* table; + struct htable_tags2desc_iterator it, end; + int fst = 1; + int ierr; + ASSERT(dev); + CHK(dim == 2 || dim == 3); /* other dims not managed yet */ + + table = dev->tags2desc + dim - 2; + htable_tags2desc_begin(table, &it); + htable_tags2desc_end(table, &end); + while(!htable_tags2desc_iterator_eq(&it, &end)) { + int dt[2], tag = *htable_tags2desc_iterator_key_get(&it); + struct tag_desc* desc = device_get_description(dim, tag); + ASSERT(desc->refcount > 0); + htable_tags2desc_iterator_next(&it); + /* desc is a descriptor for a non-released tag */ + if(fst) { + fst = 0; + logger_print(dev->logger, dev->log_type, + "Some tags were not removed properly.\n"); + } + logger_print(dev->logger, dev->log_type, "Tag %d.%d (refcount = %lu).\n", + dim, tag, (long unsigned)desc->refcount); + /* Remove tag according to policy */ + dt[0] = dim; + dt[1] = tag; + switch(desc->delete_policy) { + case Scad_do_not_delete: + logger_print(dev->logger, dev->log_type, + "Tag %d.%d not deleted due to policy.\n", + dim, tag); + break; + case Scad_delete_non_recursive: + logger_print(dev->logger, dev->log_type, + "Tag %d.%d non-recursively deleted due to policy.\n", + dim, tag); + gmshModelOccRemove(dt, 2, 0, &ierr); + break; + case Scad_delete_recursive: + gmshModelOccRemove(dt, 2, 1, &ierr); + break; + default: FATAL("Invalid enum value"); + } + } + htable_tags2desc_release(table); +} + +static res_T device_release(struct scad_device* dev) { + res_T res = RES_OK; struct htable_geometries tmp; struct htable_geometries_iterator it, end; int log, empty; @@ -41,10 +94,11 @@ device_release(struct scad_device* dev) ASSERT(dev); - option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + option = dev->options.Misc.LogRefCounting; 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); + log = (option & Scad_log_dimTags_all) + || (!empty && (option & Scad_log_dimTags_only_undeleted)); dev->log = log; dev->log_type = log_type; @@ -63,14 +117,16 @@ device_release(struct scad_device* dev) 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); + device_release_tags_of_dim(dev, 2); + device_release_tags_of_dim(dev, 3); if(log) { logger_print(dev->logger, log_type, "End finalizing scad.\n"); } MEM_RM(dev->allocator, dev); htable_geometries_release(&tmp); + + return res; } void @@ -157,126 +213,318 @@ 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); + int log = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); ASSERT(geom); - dimTags = geom->gmsh_dimTags; - count = geom->gmsh_dimTags_n; - if(log) { + if(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)); + } + } + ERR(do_device_tags_ref_get(geom->gmsh_dimTags, geom->gmsh_dimTags_n)); + } + +exit: + return res; +error: + goto exit; +} + +struct tag_desc* +device_get_description + (const int dim, + const int tag) +{ + struct scad_device* dev = get_device(); + struct htable_tags2desc* t2d; + struct tag_desc* desc; + CHK(dim == 2 || dim == 3); /* other dims not managed yet */ + + t2d = g_device->tags2desc + (dim-2); + desc = htable_tags2desc_find(t2d, &tag); + if(!desc || desc->refcount == 0) { + logger_print(dev->logger, LOG_ERROR, + "SCAD internal error: tag %d.%d not registered.\n", dim, tag); + } + return desc; +} + +void +scad_dump_geometries + (void) +{ + struct scad_device* dev = get_device(); + struct htable_geometries_iterator it, end; + + if(!dev) { + printf("Error: star-cad is not initialized.\n"); + return; + } + if(htable_geometries_is_empty(&dev->allgeom)) { + printf("No geometry defined.\n"); + return; + } + htable_geometries_begin(&dev->allgeom, &it); + htable_geometries_end(&dev->allgeom, &end); + while(!htable_geometries_iterator_eq(&it, &end)) { + struct scad_geometry* geom = *htable_geometries_iterator_key_get(&it); + size_t i; + htable_geometries_iterator_next(&it); if(str_is_empty(&geom->name)) { - log_message(dev, "Registering tags for unnamed geometry %p.\n", - (void*)geom); + printf("Unnamed geometry %p (count is %lu), tags: ", + (void*)geom, (long unsigned)geom->ref); } else { - log_message(dev, "Registering tags for geometry '%s'.\n", - str_cget(&geom->name)); + printf("Geometry '%s' (%p, count is %lu), tags: ", + str_cget(&geom->name), (void*)geom, (long unsigned)geom->ref); } + for(i = 0; i < geom->gmsh_dimTags_n; i += 2) { + int dim = geom->gmsh_dimTags[i]; + int tag = geom->gmsh_dimTags[i+1]; + printf((i ? ", %d.%d" : "%d.%d"), dim, tag); + } + printf(".\n"); + } +} + +static void +device_remove_description + (const int dim, + const int tag) +{ + struct scad_device* dev = get_device(); + struct htable_tags2desc* t2d; + struct tag_desc* desc; + CHK(dim == 2 || dim == 3); /* other dims not managed yet */ + + t2d = g_device->tags2desc + (dim-2); + desc = htable_tags2desc_find(t2d, &tag); + if(!desc) { + logger_print(dev->logger, LOG_ERROR, + "SCAD internal error: tag %d.%d not registered.\n", dim, tag); + } + if(desc->refcount != 0) { + logger_print(dev->logger, LOG_ERROR, + "SCAD internal error: erasing tag %d.%d that still has references.\n", + dim, tag); } + CHK(1 == htable_tags2desc_erase(t2d, &tag)); +} + +res_T +do_device_tags_ref_get + (const int* dimTags, + const size_t count) +{ + res_T res = RES_OK; + size_t i; + struct scad_device* dev = get_device(); + int log = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); + enum log_type log_type = dev->log_type; + + ASSERT(dimTags || count == 0); 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; + struct htable_tags2desc* t2d; + struct tag_desc* desc; 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); + + t2d = dev->tags2desc + (dim-2); + desc = htable_tags2desc_find(t2d, &tag); + if(!desc) { + /* First ref to dim.tag: create the description */ + struct tag_desc d; + tag_desc_init(dev->allocator, &d); + ERR(htable_tags2desc_set(t2d, &tag, &d)); if(log) { - log_message(dev, "New dim %d tag %d (count set to 1).\n", dim, tag); + logger_print(dev->logger, log_type, "New tag %d.%d (count set to 1).\n", + dim, tag); } } else { + desc->refcount++; 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); + if(desc->refcount > 1) { + logger_print(dev->logger, log_type, "Tag %d.%d (count increased to %lu).\n", + dim, tag, desc->refcount); } else { - log_message(dev, "Reuse dim %d tag %d (count set to 1).\n", dim, tag); + logger_print(dev->logger, log_type, "Reuse tag %d.%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: +exit: return res; error: - goto end; + goto exit; } res_T -device_unregister_tags +do_device_tags_ref_put (const int log, const enum log_type log_type, - struct scad_geometry* geom) + int* dimTags, + size_t count) { res_T res = RES_OK; - int* dimTags; - size_t count, i; + size_t i; struct scad_device* dev = get_device(); + struct str msg; - 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)); - } - } + ASSERT(dimTags || count == 0); + if(log) str_init(dev->allocator, &msg); 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) { + struct tag_desc* desc = device_get_description(dim, tag); + + if(!desc) { + res = RES_BAD_OP; + goto error; + } + + /* Check if still in use after this unregistration */ + desc->refcount--; + if(desc->refcount > 0) { if(log) { logger_print(dev->logger, log_type, - "Dim %d tag %d (count decreased to %lu).\n", dim, tag, (unsigned long)n); + "Tag %d.%d (count decreased to %lu).\n", + dim, tag, (unsigned long)desc->refcount); } 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); + switch(desc->delete_policy) { + case Scad_do_not_delete: + if(log) { + logger_print(dev->logger, log_type, + "Tag %d.%d not deleted due to policy.\n", + dim, tag); + } + break; + case Scad_delete_non_recursive: + if(log) { + logger_print(dev->logger, log_type, + "Tag %d.%d non-recursively deleted due to policy.\n", + dim, tag); + } + gmshModelOccRemove(dimTags+i, 2, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + break; + case Scad_delete_recursive: + if(log) { + logger_print(dev->logger, log_type, "Tag %d.%d recursively deleted.\n", + dim, tag); + } + gmshModelOccRemove(dimTags+i, 2, 1, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + break; + default: FATAL("Invalid enum value"); } + /* Release associated tags in tags_to_refput */ + if(darray_int_size_get(&desc->tags_to_refput)) { + size_t j; + const int* dt = darray_int_cdata_get(&desc->tags_to_refput); + if(log) { + ERR(str_set(&msg, "Putting a reference to tags: ")); + for(j = 0; j < darray_int_size_get(&desc->tags_to_refput); j += 2) { + int d = dt[j]; + int t = dt[j+1]; + ERR(str_append_printf(&msg, (j ? ", %d.%d" : "%d.%d"), d, t)); + } + logger_print(dev->logger, log_type, "%s\n", str_cget(&msg)); + } + ERR(do_device_tags_ref_put(log, log_type, + darray_int_data_get(&desc->tags_to_refput), + darray_int_size_get(&desc->tags_to_refput))); + } + device_remove_description(dim, tag); + } + +exit: + if(log) str_release(&msg); + return res; +error: + goto exit; +} + +res_T +device_register_ref_to_tags + (const int dim, + const int tag, + const int* dimTags, + const size_t count) +{ + res_T res = RES_OK; + struct tag_desc* desc = device_get_description(dim, tag); + size_t i, prev_count, c = 0; + const int* dt; + ASSERT(dimTags); - gmshModelOccRemove(dimTags+i, 2, 1, &ierr); - ERR(gmsh_err_to_res_T(ierr)); + if(!desc) { + res = RES_BAD_OP; + goto error; } -end: + prev_count = darray_int_size_get(&desc->tags_to_refput); + ERR(darray_int_reserve(&desc->tags_to_refput, count + prev_count)); + for(i = 0; i < count; i += 2) { + int d = dimTags[i]; + int t = dimTags[i+1]; + if(d == dim && t == tag) continue; + ERR(darray_int_push_back(&desc->tags_to_refput, &d)); + ERR(darray_int_push_back(&desc->tags_to_refput, &t)); + c += 2; + } + /* As refences will be put, need to get them now */ + dt = darray_int_cdata_get(&desc->tags_to_refput); + ERR(do_device_tags_ref_get(dt + prev_count, c)); + +exit: + return res; +error: + goto exit; +} + +res_T +device_unregister_tags + (const int log, + const enum log_type log_type, + struct scad_geometry* geom) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + ASSERT(geom); + + 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)); + } + } + + ERR(do_device_tags_ref_put(log, log_type, geom->gmsh_dimTags, + geom->gmsh_dimTags_n)); + +exit: return res; error: - goto end; + goto exit; } /******************************************************************************* @@ -318,12 +566,11 @@ scad_initialize g_device->need_synchro = g_device->options.Misc.DebugOpenCascadeSync; g_device->verbose = verbose; g_device->log_type = LOG_OUTPUT; - g_device->log - = (g_device->options.Misc.LogOpenCascadeTagsRefCounting== Scad_log_all); + g_device->log = (g_device->options.Misc.LogRefCounting != Scad_log_none); 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]); + htable_tags2desc_init(allocator, &g_device->tags2desc[0]); + htable_tags2desc_init(allocator, &g_device->tags2desc[1]); /* Init to default */ scad_set_options(NULL); @@ -341,7 +588,7 @@ res_T scad_finalize (void) { - res_T res = RES_OK; + res_T tmp_res = RES_OK, res = RES_OK; int ierr; struct scad_device* dev = get_device(); int log, empty; @@ -349,22 +596,24 @@ scad_finalize enum log_type log_type; ERR(check_device(FUNC_NAME)); - option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + option = dev->options.Misc.LogRefCounting; 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); + log = (option & Scad_log_dimTags_all) + || (!empty && (option & Scad_log_dimTags_only_undeleted)); if(log) { logger_print(dev->logger, log_type, "Finalizing scad; undeleted tags will be automatically unregistered.\n"); } - device_release(g_device); + tmp_res = device_release(g_device); g_device = NULL; gmshFinalize(&ierr); ERR(gmsh_err_to_res_T(ierr)); exit: + if(tmp_res != RES_OK) res = tmp_res; return res; error: goto exit; @@ -423,12 +672,16 @@ scad_set_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.LogRefCounting; /* int boolean: always OK */ (void)actual_options->Misc.DebugOpenCascadeSync; /* int boolean: always OK */ } dev->options = *actual_options; + /* Update logging policy */ + dev->log + = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); + exit: return res; error: diff --git a/src/scad_device.h b/src/scad_device.h @@ -23,6 +23,8 @@ #include <rsys/ref_count.h> #include <rsys/logger.h> #include <rsys/hash_table.h> +#include <rsys/dynamic_array.h> +#include <rsys/dynamic_array_int.h> #include <rsys/str.h> static INLINE char @@ -38,7 +40,6 @@ hash_str(const struct str* 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 @@ -46,20 +47,72 @@ hash_str(const struct str* a) #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 +#define HTABLE_DATA struct scad_geometry* #include <rsys/hash_table.h> #define HTABLE_NAME geometries -#define HTABLE_DATA char #define HTABLE_KEY struct scad_geometry* +#define HTABLE_DATA char #include <rsys/hash_table.h> -#define HTABLE_NAME tags2geom -#define HTABLE_DATA struct htable_geometries +enum delete_policy { + Scad_delete_recursive, + Scad_delete_non_recursive, + Scad_do_not_delete +}; + +struct tag_desc { + enum delete_policy delete_policy; + size_t refcount; + struct darray_int tags_to_refput; +}; + +static INLINE void +tag_desc_init + (struct mem_allocator* allocator, /* May be NULL <=> use default allocator */ + struct tag_desc* data) +{ + ASSERT(data); + data->delete_policy = Scad_delete_recursive; + data->refcount = 1; + darray_int_init(allocator, &data->tags_to_refput); +} +static INLINE void +tag_desc_release + (struct tag_desc* data) +{ + ASSERT(data); + darray_int_release(&data->tags_to_refput); +} +static FINLINE res_T +tag_desc_copy + (struct tag_desc* dst, + struct tag_desc const* src) +{ + ASSERT(dst && src); + dst->delete_policy = src->delete_policy; + dst->refcount = src->refcount; + darray_int_copy(&dst->tags_to_refput, &src->tags_to_refput); + return RES_OK; +} +static FINLINE res_T +tag_desc_copy_and_release + (struct tag_desc* dst, + struct tag_desc* src) + { + ASSERT(dst && src); + dst->delete_policy = src->delete_policy; + dst->refcount = src->refcount; + darray_int_copy_and_release(&dst->tags_to_refput, &src->tags_to_refput); + return RES_OK; +} +#define HTABLE_NAME tags2desc #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 +#define HTABLE_DATA struct tag_desc +#define HTABLE_DATA_FUNCTOR_INIT tag_desc_init +#define HTABLE_DATA_FUNCTOR_RELEASE tag_desc_release +#define HTABLE_DATA_FUNCTOR_COPY tag_desc_copy +#define HTABLE_DATA_FUNCTOR_COPY_AND_RELEASE tag_desc_copy_and_release #include <rsys/hash_table.h> struct scad_device { @@ -68,7 +121,7 @@ struct scad_device { struct scad_options options; struct htable_names geometry_names; struct htable_geometries allgeom; - struct htable_tags2geom tags2geom[2]; + struct htable_tags2desc tags2desc[2]; /* Only geoms for 2D and 3D tags for now */ int verbose; int need_synchro; @@ -141,21 +194,38 @@ LOCAL_SYM struct scad_device* get_device (void); +LOCAL_SYM struct tag_desc* +device_get_description + (const int dim, + const int tag); + +LOCAL_SYM res_T +do_device_tags_ref_get + (const int* dimTags, + const size_t count); + LOCAL_SYM res_T device_register_tags (struct scad_geometry* geom); LOCAL_SYM res_T -device_unregister_tags +do_device_tags_ref_put (const int log, const enum log_type log_type, - struct scad_geometry* geom); + int* dimTags, + size_t count); + +LOCAL_SYM res_T +device_register_ref_to_tags + (const int dim, + const int tag, + const int* dimTags, + const size_t count); 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 */ +device_unregister_tags + (const int log, + const enum log_type log_type, + struct scad_geometry* geom); #endif diff --git a/src/scad_geometry.c b/src/scad_geometry.c @@ -13,7 +13,6 @@ * 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" @@ -24,7 +23,7 @@ #include <rsys/str.h> #include <rsys/math.h> #include <rsys/double3.h> - +#include <rsys/logger.h> #include <rsys/hash_table.h> #include <stdlib.h> @@ -91,10 +90,9 @@ geom_set_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; + ERR(str_set(&geom->name, name)); } - if(name) { - str_set(&geom->name, name); ERR(htable_names_set(&dev->geometry_names, &geom->name, &geom)); } else { str_clear(&geom->name); @@ -134,7 +132,7 @@ mixed_dim_err_msg } static res_T -scad_geometry_create +geometry_create (const char* name, struct scad_geometry** out_geometry) { @@ -158,6 +156,17 @@ scad_geometry_create ERR(geom_set_name(geom, name)); dev->need_synchro = 1; + if(dev->options.Misc.LogRefCounting & Scad_log_geometry) { + if(str_is_empty(&geom->name)) { + logger_print(dev->logger, dev->log_type, + "Creating unnamed geometry %p (count set to 1).\n", (void*)geom); + } else { + logger_print(dev->logger, dev->log_type, + "Creating geometry '%s' (%p, count set to 1).\n", + str_cget(&geom->name), (void*)geom); + } + } + end: *out_geometry = geom; return res; @@ -191,6 +200,10 @@ gather_tags /* list tags and remove duplicates */ for(i = 0; i < geometries_count; i++) { + if(!geometries[i]) { + res = RES_BAD_ARG; + goto error; + } for(j = 0; j < geometries[i]->gmsh_dimTags_n; j += 2) { char one = 1; int dim = geometries[i]->gmsh_dimTags[j]; @@ -281,9 +294,9 @@ geometry_release(ref_T* ref) str_release(&geom->name); n = htable_geometries_erase(&dev->allgeom, &geom); ASSERT(n == 1); (void)n; -#ifndef NDEBUG - geom->gmsh_dimTags = (int*)0xdeadbeef; -#endif + if(geom->release && geom->custom) { + (*geom->release)(geom->custom); + } MEM_RM(allocator, geom); } @@ -294,19 +307,58 @@ res_T scad_geometry_ref_get (struct scad_geometry* geom) { + res_T res = RES_OK; + struct scad_device* dev = get_device(); + if(!geom) return RES_BAD_ARG; + ERR(check_device(FUNC_NAME)); + ref_get(&geom->ref); - return RES_OK; + if(dev->options.Misc.LogRefCounting & Scad_log_geometry) { + if(str_is_empty(&geom->name)) { + logger_print(dev->logger, dev->log_type, + "Getting a reference on unnamed geometry %p (count set to %lu).\n", + (void*)geom, (long unsigned)geom->ref); + } else { + logger_print(dev->logger, dev->log_type, + "Getting a reference on geometry '%s' (count set to %lu).\n", + str_cget(&geom->name), (long unsigned)geom->ref); + } + } + +end: + return res; +error: + goto end; } res_T scad_geometry_ref_put (struct scad_geometry* geom) { + res_T res = RES_OK; + struct scad_device* dev = get_device(); + if(!geom) return RES_BAD_ARG; + ERR(check_device(FUNC_NAME)); + + if(dev->options.Misc.LogRefCounting & Scad_log_geometry) { + if(str_is_empty(&geom->name)) { + logger_print(dev->logger, dev->log_type, + "Putting a reference on unnamed geometry %p (count set to %lu).\n", + (void*)geom, (long unsigned)geom->ref - 1); + } else { + logger_print(dev->logger, dev->log_type, + "Putting a reference on geometry '%s' (count set to %lu).\n", + str_cget(&geom->name), (long unsigned)geom->ref - 1); + } + } ref_put(&geom->ref, geometry_release); - CHK(RES_OK == check_device(FUNC_NAME)); - return RES_OK; + +end: + return res; +error: + goto end; } res_T @@ -325,7 +377,7 @@ scad_scene_clear ERR(check_device(FUNC_NAME)); allocator = dev->allocator; - option = dev->options.Misc.LogOpenCascadeTagsRefCounting; + option = dev->options.Misc.LogRefCounting; htable_geometries_init(allocator, &tmp); ERR(htable_geometries_copy(&tmp, &dev->allgeom)); @@ -333,7 +385,8 @@ scad_scene_clear 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); + log = (option & Scad_log_dimTags_all) + || (!empty && (option & Scad_log_dimTags_only_undeleted)); SWAP(int, dev->log, log); SWAP(enum log_type, dev->log_type, log_type); if(dev->log) { @@ -388,20 +441,22 @@ error: } res_T -scad_geometry_get_name - (const struct scad_geometry* geom, - const char** name) +scad_geometry_set_custom_data + (struct scad_geometry* geom, + void (*release) (void* data), + void* data) { res_T res = RES_OK; - if(!geom || !name) { + if(!geom) { res = RES_BAD_ARG; goto error; } ERR(check_device(FUNC_NAME)); - *name = str_cget(&geom->name); + geom->custom = data; + geom->release = release; exit: return res; @@ -410,39 +465,47 @@ error: } res_T -scad_geometry_swap_names - (struct scad_geometry* geom1, - struct scad_geometry* geom2) +scad_geometry_get_custom_data + (struct scad_geometry* geom, + void** data) { 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) { + if(!geom || !data) { 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(geom->release && geom->custom) { + (*geom->release)(geom->custom); } - if(!str_is_empty(&geom2->name)) { - ERR(htable_names_set(&dev->geometry_names, &geom2->name, &geom1)); + *data = geom->custom; + +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; } - 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)); + ERR(check_device(FUNC_NAME)); + + *name = str_cget(&geom->name); exit: - if(init) str_release(&tmp); return res; error: goto exit; @@ -454,36 +517,31 @@ scad_geometry_get_mass double* mass) { res_T res = RES_OK; - int dim = 0; - size_t i, count = 0; + int ref_dim = 0; + size_t i; 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)); + data = geom->gmsh_dimTags; + sz = geom->gmsh_dimTags_n; - dim = data[0]; + ref_dim = data[0]; *mass = 0; - for(i=0; i<count; ++i) { + for(i = 0; i < sz; i += 2) { double geom_mass = 0; + int dim = data[i], tag = data[i+1]; int ierr = 0; - if(data[2*i] != dim) goto error; - gmshModelOccGetMass(data[2*i], data[2*i + 1], &geom_mass, &ierr); + if(ref_dim != dim) goto error; + gmshModelOccGetMass(dim, tag, &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; @@ -546,7 +604,7 @@ scad_add_rectangle gmsh_ID = gmshModelOccAddRectangle(SPLIT3(xyz), SPLIT2(dxdy), -1, 0, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &geom)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = 2; geom->gmsh_dimTags = MEM_ALLOC(allocator, geom->gmsh_dimTags_n * sizeof(*geom->gmsh_dimTags)); @@ -594,7 +652,7 @@ scad_add_disk gmsh_ID = gmshModelOccAddDisk(SPLIT3(xyz), radius, radius, -1, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &geom)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = 2; geom->gmsh_dimTags = MEM_ALLOC(allocator, geom->gmsh_dimTags_n * sizeof(*geom->gmsh_dimTags)); @@ -671,7 +729,7 @@ scad_add_polygon gmsh_ID = gmshModelOccAddPlaneSurface(&loop, 1, -1, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &geom)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = 2; geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); if(!geom->gmsh_dimTags) { @@ -722,7 +780,7 @@ scad_add_box gmsh_ID = gmshModelOccAddBox(SPLIT3(xyz), SPLIT3(dxdydz), -1, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &geom)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = 2; geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); if(!geom->gmsh_dimTags) { @@ -772,7 +830,7 @@ scad_add_cylinder angle, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &geom)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = 2; geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); if(!geom->gmsh_dimTags) { @@ -820,7 +878,7 @@ scad_add_sphere 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)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = 2; geom->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*geom->gmsh_dimTags)); if(!geom->gmsh_dimTags) { @@ -843,6 +901,144 @@ error: goto exit; } +SCAD_API res_T +scad_geometries_equal + (struct scad_geometry* geom1, + struct scad_geometry* geom2, + int* equal) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + struct htable_tags t2, t3; + int eq = 1, initialized = 0; + + if(!geom1 || !geom2 || !equal) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + /* Trivial cases */ + if(geom1 == geom2) { + eq = 1; + } + else if(geom1->gmsh_dimTags_n != geom2->gmsh_dimTags_n) { + eq = 0; + } else { + size_t i; + htable_tags_init(allocator, &t2); + htable_tags_init(allocator, &t3); + initialized = 1; + /* Create tables from geom1 tags */ + for(i = 0; i < geom1->gmsh_dimTags_n; i += 2) { + char one = 1; + int d = geom1->gmsh_dimTags[i]; + int t = geom1->gmsh_dimTags[i+1]; + struct htable_tags* tn = (d == 2) ? &t2 : &t3; + ERR(htable_tags_set(tn, &t, &one)); + } + ASSERT((htable_tags_size_get(&t2) + htable_tags_size_get(&t3)) * 2 + == geom1->gmsh_dimTags_n); + /* Check if tags from geom2 are included */ + for(i = 0; i < geom2->gmsh_dimTags_n; i += 2) { + char* found; + int d = geom2->gmsh_dimTags[i]; + int t = geom2->gmsh_dimTags[i+1]; + struct htable_tags* tn = (d == 2) ? &t2 : &t3; + found = htable_tags_find(tn, &t); + if(!found) { + eq = 0; + break; + } + } + } + + *equal = eq; + +exit: + if(initialized) { + htable_tags_release(&t2); + htable_tags_release(&t3); + } + return res; +error: + goto exit; +} + +SCAD_API res_T +scad_geometry_is_included + (struct scad_geometry* geometry, + struct scad_geometry** geometries, + const size_t geometries_count, + int* included) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + struct htable_tags t2, t3; + int initialized = 0; + size_t i, n; + + if(!geometry || !geometries || !included) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + /* Trivial case */ + for(i = 0; i < geometries_count; i++) { + if(geometry == geometries[i]) { + *included = 1; + goto exit; + } + } + + /* Create tables from geometries tags */ + htable_tags_init(allocator, &t2); + htable_tags_init(allocator, &t3); + initialized = 1; + for(n = 0; n < geometries_count; n++) { + const struct scad_geometry* geom = geometries[n]; + for(i = 0; i < geometries[n]->gmsh_dimTags_n; i += 2) { + char one = 1; + int d = geom->gmsh_dimTags[i]; + int t = geom->gmsh_dimTags[i+1]; + struct htable_tags* tn = (d == 2) ? &t2 : &t3; + ERR(htable_tags_set(tn, &t, &one)); + } + } + + /* Check if tags from geometry are included */ + for(i = 0; i < geometry->gmsh_dimTags_n; i += 2) { + char* found; + int d = geometry->gmsh_dimTags[i]; + int t = geometry->gmsh_dimTags[i+1]; + struct htable_tags* tn = (d == 2) ? &t2 : &t3; + found = htable_tags_find(tn, &t); + if(!found) { + *included = 0; + goto exit; + } + } + + /* If here, no not-included tag was found */ + *included = 1; + +exit: + if(initialized) { + htable_tags_release(&t2); + htable_tags_release(&t3); + } + return res; +error: + goto exit; +} + res_T scad_fuse_geometries (const char* name, @@ -876,12 +1072,12 @@ scad_fuse_geometries 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 */ + * no longer 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)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = tagoutn; geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); if(!geom->gmsh_dimTags) { @@ -912,6 +1108,44 @@ error: } res_T +scad_collect_geometries + (const char* name, + struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** out_geometry) +{ + res_T res = RES_OK; + size_t sz; + int* data = NULL; + struct scad_geometry* geom = NULL; + + if(!geometries || !geometries_count || !out_geometry) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + ERR(gather_tags(geometries, geometries_count, &data, &sz)); + + ERR(geometry_create(name, &geom)); + geom->gmsh_dimTags_n = sz; + geom->gmsh_dimTags = data; + + ERR(device_register_tags(geom)); + +exit: + if(out_geometry) *out_geometry = geom; + return res; +error: + if(geom) { + SCAD(geometry_ref_put(geom)); + geom = NULL; + } + goto exit; +} + +res_T scad_cut_geometries (const char* name, /* Can be NULL */ struct scad_geometry** geometries, @@ -944,12 +1178,12 @@ scad_cut_geometries 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 */ + * no longer 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)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = tagoutn; geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); if(!geom->gmsh_dimTags) { @@ -1017,12 +1251,12 @@ scad_intersect_geometries 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 */ + * no longer 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)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = tagoutn; if (tagoutn == 0){ geom->gmsh_dimTags = NULL; @@ -1079,6 +1313,11 @@ scad_geometries_common_boundaries struct scad_geometry* geom = NULL; struct mem_allocator* allocator = NULL; struct scad_device* dev = get_device(); + int log = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); + enum log_type log_type = dev->log_type; + size_t i; + struct str msg; + int init = 0; if(!geometries || !geometries_count || !tools || !tools_count || !out_geometry) { res = RES_BAD_ARG; @@ -1092,7 +1331,7 @@ scad_geometries_common_boundaries 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 */ + * no longer 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); @@ -1101,7 +1340,7 @@ scad_geometries_common_boundaries &mapn, &mapnn, -1, 0/*no delete*/, 0/*no delete*/, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &geom)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = tagoutn; if (tagoutn == 0) { geom->gmsh_dimTags = NULL; @@ -1116,7 +1355,51 @@ scad_geometries_common_boundaries ERR(device_register_tags(geom)); + if(log) { + str_init(allocator, &msg); + init = 1; + logger_print(dev->logger, log_type, + "Common boundaries specific tag management:\n"); + ERR(str_printf(&msg, " tags [")); + for(i = 0; i < tagoutn; i += 2) { + const int dim = tagout[i]; + const int tag = tagout[i+1]; + ERR(str_append_printf(&msg, (i ? ", %d.%d" : "%d.%d"), dim, tag)); + } + ERR(str_append_printf(&msg, "] getting a ref to tags [")); + for(i = 0; i < sz1; i += 2) { + const int dim = data1[i]; + const int tag = data1[i+1]; + ERR(str_append_printf(&msg, (i ? ", %d.%d" : "%d.%d"), dim, tag)); + } + for(i = 0; i < sz2; i += 2) { + const int dim = data2[i]; + const int tag = data2[i+1]; + ERR(str_append_printf(&msg, ", %d.%d", dim, tag)); + } + logger_print(dev->logger, log_type, "%s].\n", str_cget(&msg)); + } + for(i = 0; i < tagoutn; i += 2) { + int dim = tagout[i]; + int tag = tagout[i+1]; + struct tag_desc* desc = device_get_description(dim, tag); + ASSERT(dim == 2); + if(!desc) { + res = RES_BAD_OP; + goto error; + } + /* Need to protect out_geometry's tags by getting a ref on input tags or + * deleting input geometry will possibly delete them */ + ERR(device_register_ref_to_tags(dim, tag, data1, sz1)); + ERR(device_register_ref_to_tags(dim, tag, data2, sz2)); + /* As the 2D tags will be deleted when the 3D tag they are part of are + * deleted, they shouldn't be deleted when the geometry they belongs to are + * released. */ + desc->delete_policy = Scad_do_not_delete; + } + exit: + if(init) str_release(&msg); if(out_geometry) *out_geometry = geom; if(allocator) { MEM_RM(allocator, data1); @@ -1148,52 +1431,64 @@ error: res_T scad_geometry_rotate - (struct scad_geometry* geom, + (const struct scad_geometry* geom, const double pt[3], const double axis[3], - const double angle) + const double angle, + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry) { - int* data; - size_t sz; int ierr = 0; + struct scad_geometry* out = NULL; res_T res = RES_OK; - if(!geom || !pt || !axis) { + if(!geom || !pt || !axis || ! out_geometry) { 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); + ERR(scad_geometry_copy(geom, name, &out)); + + gmshModelOccRotate(out->gmsh_dimTags, out->gmsh_dimTags_n, SPLIT3(pt), + SPLIT3(axis), angle, &ierr); get_device()->need_synchro = 1; ERR(gmsh_err_to_res_T(ierr)); exit: + if(out_geometry) *out_geometry = out; return res; error: + if(out) SCAD(geometry_ref_put(out)); + out = NULL; goto exit; } res_T scad_geometry_extrude - (const struct scad_geometry* geom, + (struct scad_geometry* geom, const char* name, const double dxdydz[3], struct scad_geometry** out_geometry) { res_T res = RES_OK; - int* tagout = NULL; + int *tagout = NULL; size_t tagoutn; - size_t i, j; - int* extrude_data = NULL; + size_t i; +#ifndef NDEBUG + size_t j; +#endif + int *ed, *extrude_data = NULL; size_t extrude_sz = 0; int ierr = 0; struct scad_geometry* extrude_geom = NULL; struct scad_device* dev = get_device(); + int log = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); + enum log_type log_type = dev->log_type; struct mem_allocator* allocator = NULL; + struct str msg; + int init = 0; if(!geom || !dxdydz || !out_geometry) { res = RES_BAD_ARG; @@ -1205,13 +1500,12 @@ scad_geometry_extrude 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)); + get_device()->need_synchro = 1; - ERR(scad_geometry_create(name, &extrude_geom)); - /* keep only 3D entities */ - /* TODO : NOT SURE OF THE CONCEPT */ - for(i=0; i<tagoutn; i+=2) { + /* Output includes both the 3D result and its 2D constituants. + * Keep only 3D entities. */ + for(i = 0; i < tagoutn; i += 2) { int dim = tagout[i]; if(dim == 3) extrude_sz += 2; } @@ -1220,22 +1514,66 @@ scad_geometry_extrude res = RES_MEM_ERR; goto error; } - j = 0; - for(i=0; i<tagoutn; i+=2) { + ed = extrude_data; + for(i = 0; i < tagoutn; i += 2) { int dim = tagout[i]; int tag = tagout[i+1]; +#ifndef NDEBUG + /* Expecting geom's tags not part of tagout */ + for(j = 0; j < geom->gmsh_dimTags_n; j += 2) { + ASSERT(dim != geom->gmsh_dimTags[j] || tag != geom->gmsh_dimTags[j+1]); + } +#endif if(dim == 3) { - extrude_data[j] = dim; - extrude_data[j+1] = tag; - j += 2; + *ed++ = dim; + *ed++ = tag; } } - ASSERT(j == extrude_sz); + + ERR(geometry_create(name, &extrude_geom)); extrude_geom->gmsh_dimTags_n = extrude_sz; extrude_geom->gmsh_dimTags = extrude_data; + ERR(device_register_tags(extrude_geom)); + if(log) { + str_init(allocator, &msg); + init = 1; + logger_print(dev->logger, log_type, "Extrude specific tag management:\n"); + ERR(str_printf(&msg, " tags [")); + for(i = 0; i < geom->gmsh_dimTags_n; i += 2) { + const int dim = geom->gmsh_dimTags[i]; + const int tag = geom->gmsh_dimTags[i+1]; + ERR(str_append_printf(&msg, (i ? ", %d.%d" : "%d.%d"), dim, tag)); + } + ERR(str_append_printf(&msg, "] getting a ref to tags [")); + for(i = 0; i < extrude_sz; i += 2) { + const int dim = extrude_data[i]; + const int tag = extrude_data[i+1]; + ERR(str_append_printf(&msg, (i ? ", %d.%d" : "%d.%d"), dim, tag)); + } + logger_print(dev->logger, log_type, "%s].\n", str_cget(&msg)); + } + for(i = 0; i < geom->gmsh_dimTags_n; i += 2) { + const int dim = geom->gmsh_dimTags[i]; + const int tag = geom->gmsh_dimTags[i+1]; + struct tag_desc* desc = device_get_description(dim, tag); + ASSERT(dim == 2); + if(!desc) { + res = RES_BAD_OP; + goto error; + } + /* Need to protect input geometry's tags by getting a ref on output tags or + * deleting out_geometry will possibly delete them */ + ERR(device_register_ref_to_tags(dim, tag, extrude_data, extrude_sz)); + /* As the 2D tags will be deleted when the 3D tag they are part of are + * deleted, they shouldn't be deleted when the geometry they belongs to are + * released. */ + desc->delete_policy = Scad_do_not_delete; + } + exit: + if(init) str_release(&msg); if(out_geometry) *out_geometry = extrude_geom; gmshFree(tagout); return res; @@ -1244,6 +1582,7 @@ error: SCAD(geometry_ref_put(extrude_geom)); extrude_geom = NULL; } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); goto exit; } @@ -1282,18 +1621,19 @@ scad_geometry_explode goto error; } - base_name = prefix_name ? prefix_name : str_cget(&geom->name); - if(base_name) { + if(prefix_name || !str_is_empty(&geom->name)) { + base_name = prefix_name ? prefix_name : str_cget(&geom->name); str_init(allocator, &name); name_initialized = 1; } - for(i=0; i<sz/2; ++i) { + + for(i = 0; i < sz/2; ++i) { if(base_name) { ERR(str_set(&name, base_name)); ERR(str_append_printf(&name,"_%lu", (unsigned long)i)); - ERR(scad_geometry_create(str_cget(&name), geom_array+i)); + ERR(geometry_create(str_cget(&name), geom_array+i)); } else { - ERR(scad_geometry_create(NULL, geom_array+i)); + ERR(geometry_create(NULL, geom_array+i)); } geom_array[i]->gmsh_dimTags_n = 2; geom_array[i]->gmsh_dimTags @@ -1354,7 +1694,7 @@ scad_geometry_copy get_device()->need_synchro = 1; ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &copy)); + ERR(geometry_create(name, &copy)); copy->gmsh_dimTags_n = tagoutn; copy->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); if(!copy->gmsh_dimTags) { @@ -1401,55 +1741,53 @@ error: } res_T -scad_geometry_reverse - (struct scad_geometry* geom) +scad_geometry_translate + (const struct scad_geometry* geom, + const double dxdydz[3], + const char* name, /* Can be NULL */ + struct scad_geometry** out_geometry) { int ierr = 0; + struct scad_geometry* out = NULL; res_T res = RES_OK; - if(!geom) { + if(!geom || !dxdydz || ! out_geometry) { res = RES_BAD_ARG; goto error; } ERR(check_device(FUNC_NAME)); - gmshModelMeshReverse(geom->gmsh_dimTags, geom->gmsh_dimTags_n, &ierr); + ERR(scad_geometry_copy(geom, name, &out)); + + gmshModelOccTranslate(out->gmsh_dimTags, out->gmsh_dimTags_n, SPLIT3(dxdydz), + &ierr); + get_device()->need_synchro = 1; ERR(gmsh_err_to_res_T(ierr)); exit: + if(out_geometry) *out_geometry = out; return res; error: + if(out) SCAD(geometry_ref_put(out)); + out = NULL; goto exit; } -res_T -scad_geometry_translate - (struct scad_geometry* geom, - const double dxdydz[3]) +static char +geom_uses_dimTag + (const struct scad_geometry* g, + const int dim, + const int tag) { - int* data; - size_t sz; - int ierr = 0; - res_T res = RES_OK; - - if(!geom || !dxdydz) { - res = RES_BAD_ARG; - goto error; + size_t i; + ASSERT(g); + for(i = 0; i < g->gmsh_dimTags_n; i+=2) { + int d = g->gmsh_dimTags[i]; + int t = g->gmsh_dimTags[i+1]; + if(dim == d && tag == t) return 1; } - - 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; + return 0; } res_T @@ -1469,14 +1807,15 @@ scad_geometries_partition 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 dont_call_fragment = 0; + char* overlap = NULL; - if(!geometries || !geometries_count || (allow_overlapping && !out_geometries)) { + if(!geometries || !geometries_count || !out_geometries) { res = RES_BAD_ARG; goto error; } @@ -1486,136 +1825,171 @@ scad_geometries_partition 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_ref_put 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 */ + /* Create output geometries */ + geoms = MEM_CALLOC(allocator, geometries_count, sizeof(*geoms)); + if(!geoms) { + res = RES_MEM_ERR; + goto error; + } - get_device()->need_synchro = 1; + dont_call_fragment = (sz == 2); + if(dont_call_fragment) { + /* gmshModelOccFragment doesn't allow to process a single entity: need to + * create the result (same as input) here. + * Not so simple as one could have provided more than 1 input geometry with + * identical or no dim.tag(s). */ + mapnn = 1; + map = gmshMalloc(sizeof(*map)); + map[0] = gmshMalloc(2 * sizeof(**map)); + mapn = gmshMalloc(sizeof(*mapn)); + if(!map || !map[0] || !mapn) { + res = RES_MEM_ERR; + goto error; + } + mapn[0] = 2; + map[0][0] = data[0]; + map[0][1] = data[1]; + } else { + /* As a general principle, we don't remove gmsh objects directly; they are + * only removed from scad_geometry_ref_put when their tags are no longer + * used by any star-cad geometry. */ + gmshModelOccFragment(data, sz, NULL, 0, &tagout, &tagoutn, &map, &mapn, + &mapnn, -1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(sz == 2*mapnn); /* Because input tags where deduplicated */ - /* 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)); + 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 */ + size_t ov = 0; + overlap = MEM_CALLOC(allocator, geometries_count, sizeof(*overlap)); + if(!overlap) { + res = RES_MEM_ERR; + goto error; + } + for(i = 0; i < mapnn; i++) { + if(mapn[i] != 2) { + int dim = data[2*i]; + int tag = data[2*i+1]; + size_t k; + ov++; + res = RES_BAD_ARG; + /* dim.tag #i overlaps: search geometries using it in input */ + for(k = 0; k < geometries_count; k++) { + overlap[k] |= geom_uses_dimTag(geometries[k], dim, tag); + } } } - /* 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(ov) { + size_t k; + for(k = 0; k < geometries_count; k++) { + struct scad_geometry* g = geometries[k]; + if(!overlap[k]) continue; + if(str_is_empty(&g->name)) { + log_error(get_device(), "Unnamed geometry '%p' overlapping.\n", + (void*)g); + } else { + log_error(get_device(), "Geometry '%s' overlapping.\n", + str_cget(&g->name)); + } + } + } + if(ov) { + res = RES_BAD_ARG; + goto error; + } } - 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; + } + + /* Create htables (mappings are used to ease access) */ + htable_mappings_init(allocator, &m2); + htable_mappings_init(allocator, &m3); + htable_tags_init(allocator, &t2); + htable_tags_init(allocator, &t3); + ht_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)); + } + + 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. + * Because of the overlapping case, 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); - ERR(htable_mappings_set(mn, &tag, &mapping)); + 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)); + } } - - /* Create output geometries from mapping */ - geoms = MEM_CALLOC(allocator, geometries_count, sizeof(*geoms)); - if(!geoms) { + /* 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; } - 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])); + /* 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); } - memcpy(out_geometries, geoms, geometries_count * sizeof(*geoms)); + ASSERT(c == 2*n); + + /* Create geometry */ + ERR(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) { + if(ht_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); + MEM_RM(allocator, overlap); } gmshFree(tagout); return res; @@ -1634,70 +2008,135 @@ error: } 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) +scad_geometries_swap + (struct scad_geometry** pool1, + struct scad_geometry** pool2, + const size_t count, + const int flags) { 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(); + size_t i; struct mem_allocator* allocator = NULL; + struct str tmp, msg; - if(!geometries || !geometries_count || !tools || !tools_count || !out_geometry) { + if(!pool1 || !pool2) { res = RES_BAD_ARG; goto error; } - ERR(check_device(FUNC_NAME)); allocator = dev->allocator; - - 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; + if(flags & Scad_swap_name) str_init(allocator, &tmp); + if(flags & Scad_swap_geometry && dev->log) str_init(allocator, &msg); + for(i = 0; i < count; i++) { + struct scad_geometry *g1 = pool1[i], *g2 = pool2[i]; + size_t c1 = g1->gmsh_dimTags_n, c2 = g2->gmsh_dimTags_n; + int *dt1 = g1->gmsh_dimTags, *dt2 = g2->gmsh_dimTags; + if(pool1[i] == pool2[i]) continue; + /* Swap content according to flags. Don't swap refcount! */ + if(flags & Scad_swap_name) { + if(dev->log) { + if(str_is_empty(&g1->name) && str_is_empty(&g2->name)) { + /* Do nothing */ + } + else if(str_is_empty(&g1->name)) { + logger_print(dev->logger, dev->log_type, + "Swapping names for geometry %p and geometry '%s'.\n", + (void*)g1, str_cget(&g2->name)); + logger_print(dev->logger, dev->log_type, + "Geometry '%s' is now unnamed geometry %p.\n", + str_cget(&g2->name), (void*)g2); + } + else if(str_is_empty(&g2->name)) { + logger_print(dev->logger, dev->log_type, + "Swapping names for geometry %p and geometry '%s'.\n", + (void*)g2, str_cget(&g1->name)); + logger_print(dev->logger, dev->log_type, + "Geometry '%s' is now unnamed geometry %p.\n", + str_cget(&g1->name), (void*)g1); + } else { /* Both named */ + logger_print(dev->logger, dev->log_type, + "Swapping names for geometries '%s' and '%s'.\n", + str_cget(&g1->name), str_cget(&g1->name)); + } + } + if(!str_is_empty(&g1->name)) { + ERR(htable_names_set(&dev->geometry_names, &g1->name, &g2)); + } + if(!str_is_empty(&g2->name)) { + ERR(htable_names_set(&dev->geometry_names, &g2->name, &g1)); + } + if(!str_is_empty(&g1->name) || !str_is_empty(&g2->name)) { + ERR(str_copy(&tmp, &g1->name)); + ERR(str_copy(&g1->name, &g2->name)); + ERR(str_copy(&g2->name, &tmp)); + } + } + if(flags & Scad_swap_geometry) { + /* Swap in tag2geom tables */ + size_t n; + if(dev->log) { + if(str_is_empty(&g1->name) && str_is_empty(&g2->name)) { + logger_print(dev->logger, dev->log_type, + "Swapping tags for unnamed geometries %p and %p.\n", + (void*)g1, (void*)g2); + } + else if(str_is_empty(&g1->name)) { + logger_print(dev->logger, dev->log_type, + "Swapping tags for unnamed geometry %p and geometry '%s'.\n", + (void*)g1, str_cget(&g2->name)); + } + else if(str_is_empty(&g2->name)) { + logger_print(dev->logger, dev->log_type, + "Swapping tags for unnamed geometry %p and geometry '%s'.\n", + (void*)g2, str_cget(&g1->name)); + } + else { + logger_print(dev->logger, dev->log_type, + "Swapping tags for geometries '%s' and '%s'.\n", + str_cget(&g1->name), str_cget(&g1->name)); + } + if(str_is_empty(&g1->name)) { + ERR(str_printf(&msg, + "Tags now registered against unnamed geometry '%p': ", + (void*)g1)); + } else { + ERR(str_printf(&msg, "Tags now registered against geometry '%s': ", + str_cget(&g1->name))); + } + for(n = 0; n < c2; n += 2) { + int dim = dt2[n]; + int tag = dt2[n+1]; + if(n) { ERR(str_append_printf(&msg, ",")); } + ERR(str_append_printf(&msg, " %d.%d", dim, tag)); + } + logger_print(dev->logger, dev->log_type, "%s.\n", str_cget(&msg)); + if(str_is_empty(&g2->name)) { + ERR(str_printf(&msg, + "Tags now registered against unnamed geometry '%p': ", + (void*)g2)); + } else { + ERR(str_printf(&msg, "Tags now registered against geometry '%s': ", + str_cget(&g2->name))); + } + for(n = 0; n < c1; n += 2) { + int dim = dt1[n]; + int tag = dt1[n+1]; + ERR(str_append_printf(&msg, (n ? ", %d.%d" : "%d.%d"), dim, tag)); + } + logger_print(dev->logger, dev->log_type, "%s.\n", str_cget(&msg)); + } + /* Swap tags */ + SWAP(int*, g1->gmsh_dimTags, g2->gmsh_dimTags); + SWAP(size_t, g1->gmsh_dimTags_n, g2->gmsh_dimTags_n); + } } - 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); + if(flags & Scad_swap_name) str_release(&tmp); + if(flags & Scad_swap_geometry && dev->log) str_release(&msg); return res; error: - if(geom) { - SCAD(geometry_ref_put(geom)); - geom = NULL; - } - if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); goto exit; } @@ -1714,8 +2153,13 @@ scad_geometry_boundary int* data = NULL; int ierr = 0; struct scad_geometry* geom = NULL; - struct scad_device* dev = get_device(); struct mem_allocator* allocator = NULL; + struct scad_device* dev = get_device(); + int log = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); + enum log_type log_type = dev->log_type; + size_t i; + struct str msg; + int init = 0; if(!geometries || !out_geometry) { res = RES_BAD_ARG; @@ -1729,7 +2173,7 @@ scad_geometry_boundary gmshModelGetBoundary(data, sz, &tagout, &tagoutn, 1, 0, 0, &ierr); ERR(gmsh_err_to_res_T(ierr)); - ERR(scad_geometry_create(name, &geom)); + ERR(geometry_create(name, &geom)); geom->gmsh_dimTags_n = tagoutn; geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * 2 * sizeof(*tagout)); if(!geom->gmsh_dimTags) { @@ -1740,7 +2184,44 @@ scad_geometry_boundary ERR(device_register_tags(geom)); + if(log) { + str_init(allocator, &msg); + init = 1; + logger_print(dev->logger, log_type, "Boundary specific tag management:\n"); + ERR(str_printf(&msg, " tags [")); + for(i = 0; i < tagoutn; i += 2) { + const int dim = tagout[i]; + const int tag = tagout[i+1]; + ERR(str_append_printf(&msg, (i ? ", %d.%d" : "%d.%d"), dim, tag)); + } + ERR(str_append_printf(&msg, "] getting a ref to tags [")); + for(i = 0; i < sz; i += 2) { + const int dim = data[i]; + const int tag = data[i+1]; + ERR(str_append_printf(&msg, (i ? ", %d.%d" : "%d.%d"), dim, tag)); + } + logger_print(dev->logger, log_type, "%s].\n", str_cget(&msg)); + } + for(i = 0; i < tagoutn; i += 2) { + int dim = tagout[i]; + int tag = tagout[i+1]; + struct tag_desc* desc = device_get_description(dim, tag); + ASSERT(dim == 2); + if(!desc) { + res = RES_BAD_OP; + goto error; + } + /* Need to protect out_geometry's tags by getting a ref on input tags or + * deleting input geometry will possibly delete them */ + ERR(device_register_ref_to_tags(dim, tag, data, sz)); + /* As the 2D tags will be deleted when the 3D tag they are part of are + * deleted, they shouldn't be deleted when the geometry they belongs to are + * released. */ + desc->delete_policy = Scad_do_not_delete; + } + exit: + if(init) str_release(&msg); if(allocator) MEM_RM(allocator, data); if(out_geometry) *out_geometry = geom; gmshFree(tagout); @@ -1797,9 +2278,9 @@ scad_step_import 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)); + ERR(geometry_create(str_cget(&strname), geom_array+i)); } else { - ERR(scad_geometry_create(NULL, geom_array+i)); + ERR(geometry_create(NULL, geom_array+i)); } geom_array[i]->gmsh_dimTags_n = 2; @@ -1834,7 +2315,6 @@ error: goto exit; } - res_T scad_geometry_normal (struct scad_geometry* geom, @@ -1846,15 +2326,18 @@ scad_geometry_normal res_T res = RES_OK; int ierr = 0; size_t i; - int* data = NULL; + const int* data = NULL; size_t sz = 0; - struct scad_geometry* surface = NULL; struct scad_geometry* out = NULL; struct scad_device* dev = get_device(); + int log = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); + enum log_type log_type = dev->log_type; struct mem_allocator* allocator = NULL; double* coord = NULL; double* pcoord = NULL; double* normals = NULL; + struct darray_int tags; + int initialized = 0; if(!geom || !p || !N || !out_geometry) { res = RES_BAD_ARG; @@ -1864,36 +2347,32 @@ scad_geometry_normal 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; - } + darray_int_init(dev->allocator, &tags); + initialized = 1; - ERR(gather_tags(&surface, 1, &data, &sz)); + ERR(get_2d_tags(geom, &tags)); + data = darray_int_cdata_get(&tags); + sz = darray_int_size_get(&tags); - for(i=0; sz/2; ++i) { + for(i = 0; i < sz; ++i) { size_t pcoord_n; size_t coord_n; size_t normals_n; - int dim = data[2*i]; - int tag = data[2*i + 1]; + const int dim = 2; + int tag = data[i]; - 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); + gmshModelGetClosestPoint(dim, tag, p, 3, &coord, &coord_n, &pcoord, + &pcoord_n, &ierr); ERR(gmsh_err_to_res_T(ierr)); + ASSERT(pcoord_n == (size_t)dim); + ASSERT(coord_n == 3); if(d3_eq_eps(p, coord, 1e-6)) { gmshModelGetNormal(tag, pcoord, pcoord_n, &normals, &normals_n, &ierr); ERR(gmsh_err_to_res_T(ierr)); + ASSERT(normals_n == 3); - ERR(scad_geometry_create(name, &out)); + ERR(geometry_create(name, &out)); out->gmsh_dimTags = MEM_ALLOC(allocator, 2 * sizeof(*out->gmsh_dimTags)); if(!out->gmsh_dimTags) { res = RES_MEM_ERR; @@ -1902,11 +2381,29 @@ scad_geometry_normal out->gmsh_dimTags_n = 2; out->gmsh_dimTags[0] = dim; out->gmsh_dimTags[1] = tag; + d3_set(N, normals); + ERR(device_register_tags(out)); - d3_set(N, normals); + /* Need to protect geometries' tags or deleting out geometry will possibly + * delete them */ + if(log) { + logger_print(dev->logger, log_type, + "Tag %d.%d getting a reference to other tags.\n", dim, tag); + } + ERR(device_register_ref_to_tags(dim, tag, geom->gmsh_dimTags, + geom->gmsh_dimTags_n)); + if(log) { + logger_print(dev->logger, log_type, + "Tag %d.%d getting a reference to other tags done.\n", dim, tag); + } + break; } + + gmshFree(coord); + gmshFree(pcoord); + coord = pcoord = NULL; } if(!out) { /* Could not find a matching surface */ res = RES_BAD_ARG; @@ -1917,9 +2414,8 @@ exit: gmshFree(coord); gmshFree(pcoord); gmshFree(normals); + if(initialized) darray_int_release(&tags); if(out_geometry) *out_geometry = out; - if(allocator) MEM_RM(allocator, data); - if(surface) SCAD(geometry_ref_put(surface)); return res; error: goto exit; @@ -1927,33 +2423,34 @@ error: res_T scad_geometry_dilate - (struct scad_geometry* geom, - double center[3], - double scale[3]) + (const struct scad_geometry* geom, + const double center[3], + const double scale[3], + const char* name, + struct scad_geometry** out_geometry) { 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; + struct scad_geometry* out = NULL; - if(!geom || !scale|| !center) { + if(!geom || !scale|| !center || ! out_geometry) { res = RES_BAD_ARG; goto error; } ERR(check_device(FUNC_NAME)); - allocator = dev->allocator; - ERR(gather_tags(&geom, 1, &data, &sz)); + ERR(scad_geometry_copy(geom, name, &out)); - gmshModelOccDilate(data, sz, SPLIT3(center), SPLIT3(scale), &ierr); + gmshModelOccDilate(out->gmsh_dimTags, out->gmsh_dimTags_n, SPLIT3(center), + SPLIT3(scale), &ierr); ERR(gmsh_err_to_res_T(ierr)); exit: - if(allocator) MEM_RM(allocator, data); + if(out_geometry) *out_geometry = out; return res; error: + if(out) SCAD(geometry_ref_put(out)); + out = NULL; goto exit; } diff --git a/src/scad_geometry.h b/src/scad_geometry.h @@ -18,9 +18,7 @@ #include <stdlib.h> -#include <rsys/rsys.h> #include <rsys/str.h> -#include <rsys/logger.h> #include <rsys/ref_count.h> struct mem_allocator; @@ -29,6 +27,8 @@ struct scad_geometry { int* gmsh_dimTags; size_t gmsh_dimTags_n; struct str name; + void* custom; + void (*release) (void* data); ref_T ref; }; diff --git a/src/test_api.c b/src/test_api.c @@ -56,6 +56,7 @@ main(int argc, char* argv[]) struct mem_allocator allocator; struct logger logger; struct darray_double trg; + struct scad_options options = SCAD_DEFAULT_OPTIONS; const char* name; size_t i, c; @@ -103,6 +104,10 @@ main(int argc, char* argv[]) BAD(scad_initialize(&logger, &allocator, -1)); OK(scad_initialize(&logger, &allocator, 3)); + + OK(scad_set_options(NULL)); + OK(scad_set_options(&options)); + OK(scad_add_sphere("sphere 1", p1, .1, &geom1)); OK(scad_add_sphere(NULL, p2, .2, &geom2)); geoms[0] = geom1; @@ -213,14 +218,32 @@ main(int argc, char* argv[]) 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_translate(NULL, NULL, NULL, NULL)); + BAD(scad_geometry_translate(NULL, NULL, NULL, &geom)); + BAD(scad_geometry_translate(NULL, d1, NULL, NULL)); + BAD(scad_geometry_translate(NULL, d1, NULL, &geom)); + BAD(scad_geometry_translate(geom1, NULL, NULL, NULL)); + BAD(scad_geometry_translate(geom1, NULL, NULL, &geom)); + BAD(scad_geometry_translate(geom1, d1, NULL, NULL)); + OK(scad_geometry_translate(geom1, d1, NULL, &geom)); + OK(scad_geometry_ref_put(geom)); - 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_rotate(NULL, NULL, NULL, 1, NULL, NULL)); + BAD(scad_geometry_rotate(NULL, NULL, NULL, 1, NULL, &geom)); + BAD(scad_geometry_rotate(NULL, NULL, d1, 1, NULL, NULL)); + BAD(scad_geometry_rotate(NULL, NULL, d1, 1, NULL, &geom)); + BAD(scad_geometry_rotate(NULL, p1, NULL, 1, NULL, NULL)); + BAD(scad_geometry_rotate(NULL, p1, NULL, 1, NULL, &geom)); + BAD(scad_geometry_rotate(NULL, p1, d1, 1, NULL, NULL)); + BAD(scad_geometry_rotate(NULL, p1, d1, 1, NULL, &geom)); + BAD(scad_geometry_rotate(geom1, NULL, NULL, 1, NULL, NULL)); + BAD(scad_geometry_rotate(geom1, NULL, NULL, 1, NULL, &geom)); + BAD(scad_geometry_rotate(geom1, NULL, d1, 1, NULL, NULL)); + BAD(scad_geometry_rotate(geom1, NULL, d1, 1, NULL, &geom)); + BAD(scad_geometry_rotate(geom1, p1, NULL, 1, NULL, NULL)); + BAD(scad_geometry_rotate(geom1, p1, NULL, 1, NULL, &geom)); + BAD(scad_geometry_rotate(geom1, p1, d1, 1, NULL, NULL)); + OK(scad_geometry_rotate(geom1, p1, d1, 1, NULL, &geom)); BAD(scad_geometry_extrude(NULL, NULL, d1, &geom)); BAD(scad_geometry_extrude(poly, NULL, NULL, &geom)); @@ -247,39 +270,33 @@ main(int argc, char* argv[]) BAD(scad_stl_get_data(NULL, NULL)); BAD(scad_stl_get_data(geom2, NULL)); OK(scad_stl_get_data(geom2, &trg)); - BAD(scad_stl_data_write(NULL, NULL, 0)); - BAD(scad_stl_data_write(&trg, NULL, 0)); - BAD(scad_stl_data_write(NULL, "/tmp/test", 0)); - OK(scad_stl_data_write(&trg, "/tmp/test", 0)); - OK(scad_stl_data_write(&trg, "/tmp/test", 1)); + BAD(scad_stl_data_write(NULL, NULL, Scad_keep_normals_unchanged, 0)); + BAD(scad_stl_data_write(&trg, NULL, Scad_keep_normals_unchanged, 0)); + BAD(scad_stl_data_write(NULL, "/tmp/test", Scad_keep_normals_unchanged, 0)); + OK(scad_stl_data_write(&trg, "/tmp/test", Scad_keep_normals_unchanged, 0)); + OK(scad_stl_data_write(&trg, "/tmp/test", Scad_keep_normals_unchanged, 1)); darray_double_release(&trg); - BAD(scad_stl_export(NULL, NULL, 0)); - BAD(scad_stl_export(geom2, NULL, 0)); /* geom2 has no name */ - OK(scad_stl_export(geom2, "/tmp/test", 0)); - OK(scad_stl_export(geom2, "/tmp/test", 1)); - OK(scad_stl_export(geom1, NULL, 0)); - OK(scad_stl_export(geom1, NULL, 1)); - - 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)); - BAD(scad_stl_export_split(geom2, "/tmp/test", 0)); /* Produce same names */ - OK(scad_stl_export_split(geom2, "/tmp/testb", 1)); - OK(scad_stl_export_split(geom1, NULL, 0)); - BAD(scad_stl_export_split(geom1, NULL, 1)); /* Produce same names */ - OK(scad_stl_export_split(geom1, "different_names", 1)); - - BAD(scad_geometry_reverse(NULL)); - OK(scad_geometry_reverse(geom1)); + BAD(scad_stl_export(NULL, NULL, Scad_keep_normals_unchanged, 0)); + BAD(scad_stl_export(geom2, NULL, Scad_keep_normals_unchanged, 0)); /* geom2 has no name */ + OK(scad_stl_export(geom2, "/tmp/test", Scad_keep_normals_unchanged, 0)); + OK(scad_stl_export(geom2, "/tmp/test", Scad_force_normals_inward, 1)); + OK(scad_stl_export(geom1, NULL, Scad_force_normals_outward, 1)); + + BAD(scad_stl_export_split(NULL, NULL, Scad_keep_normals_unchanged, 0)); + BAD(scad_stl_export_split(geom2, NULL, Scad_keep_normals_unchanged, 0)); /* geom2 has no name */ + OK(scad_stl_export_split(geom2, "/tmp/test", Scad_keep_normals_unchanged, 0)); + OK(scad_stl_export_split(geom2, "/tmp/testb", Scad_keep_normals_unchanged, 1)); + OK(scad_stl_export_split(geom1, NULL, Scad_keep_normals_unchanged, 0)); + OK(scad_stl_export_split(geom1, "different_names", Scad_keep_normals_unchanged, 1)); 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()); + logger_release(&logger); check_memory_allocator(&allocator); mem_shutdown_proxy_allocator(&allocator); diff --git a/src/test_export.c b/src/test_export.c @@ -42,7 +42,6 @@ main(int argc, char* 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)); @@ -54,17 +53,21 @@ main(int argc, char* argv[]) OK(scad_scene_mesh()); - OK(scad_stl_export(rectangle, NULL, 0)); - OK(scad_stl_export(rectangle, "bin_rectangle", 1)); + /* Do not define a volume */ + BAD(scad_stl_export(rectangle, "not-a-volume.stl", Scad_force_normals_outward, 0)); + + OK(scad_stl_export(rectangle, NULL, Scad_keep_normals_unchanged, 0)); + OK(scad_stl_export(rectangle, "bin_rectangle", Scad_keep_normals_unchanged, 1)); - OK(scad_stl_export(cube, NULL, 0)); - OK(scad_stl_export(cube, "bin_cube", 1)); + OK(scad_stl_export(cube, NULL, Scad_force_normals_outward, 0)); + OK(scad_stl_export(cube, "bin_cube", Scad_force_normals_outward, 1)); - OK(scad_stl_export(cube2, NULL, 0)); - OK(scad_stl_export(cube2, "bin_cube2", 1)); + OK(scad_stl_export(cube2, NULL, Scad_force_normals_outward, 0)); + OK(scad_stl_export(cube2, "bin_cube2", Scad_force_normals_outward, 1)); OK(scad_finalize()); + logger_release(&logger); check_memory_allocator(&allocator); mem_shutdown_proxy_allocator(&allocator); CHK(mem_allocated_size() == 0); diff --git a/src/test_lifetime.c b/src/test_lifetime.c @@ -0,0 +1,135 @@ +/* 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 <rsys/dynamic_array_double.h> + +#include <stdlib.h> +#include <stdio.h> + +static void +alive_and_well + (struct scad_geometry* g, + struct mem_allocator* allocator) +{ + struct scad_geometry** geom_array = NULL; + size_t i, c; + + ASSERT(g && allocator); + + OK(scad_geometry_explode(g, "alive_and_well", &geom_array, &c)); + OK(scad_geometry_ref_put(g)); + OK(scad_synchronize()); + for(i = 0; i < c; i++) { + double dir1[3] = {0, 0, 1}; + struct scad_geometry* gg; + OK(scad_geometry_extrude(geom_array[i], "a_n_w_extruded", dir1, &gg)); + OK(scad_geometry_ref_put(geom_array[i])); + OK(scad_geometry_ref_put(gg)); + } + MEM_RM(allocator, geom_array); +} + +int +main(int argc, char* argv[]) +{ + res_T res = RES_OK; + double p1[3] = {0, 0, 0}; + double diago[] = {1, 1, 1}, diago_[] = {.5, .5, -1}, base[] = {1, 1, 0}; + double dir1[3] = {0, 0, 1}; + struct scad_geometry* geom1 = NULL; + struct scad_geometry* geom2 = NULL; + struct scad_geometry* geom = NULL; + struct scad_geometry* geoms[2]; + struct scad_geometry* out_geoms[2]; + struct mem_allocator allocator; + struct scad_geometry** list = NULL; + size_t list_n, center_n, i; + double center[3], N[3]; + struct scad_options options = SCAD_DEFAULT_OPTIONS; + + (void)argc; (void)argv; + + OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator)); + + OK(scad_initialize(NULL, &allocator, 3)); + options.Misc.LogRefCounting = Scad_log_dimTags_all | Scad_log_geometry; + OK(scad_set_options(&options)); + + /* Check that 2D constituants of a deleted 3D object are alive and well */ + OK(scad_add_box("box", p1, diago, &geom)); + OK(scad_geometry_boundary("boundary", &geom, 1, &geom1)); + OK(scad_geometry_ref_put(geom)); + alive_and_well(geom1, &allocator); + + /* Check that a 3D derivative of a deleted 2D object is alive and well */ + OK(scad_add_rectangle("rect", p1, base, &geom)); + OK(scad_geometry_extrude(geom, "cube", dir1, &geom1)); + OK(scad_geometry_ref_put(geom)); + OK(scad_geometry_boundary("boundary", &geom1, 1, &geom2)); + OK(scad_geometry_ref_put(geom1)); + alive_and_well(geom2, &allocator); + + /* Check that a 2D part of a deleted 3D object is alive and well */ + OK(scad_add_rectangle("rect2", p1, base, &geom)); + OK(scad_geometry_extrude(geom, "cube2", dir1, &geom1)); + OK(scad_geometry_ref_put(geom1)); + alive_and_well(geom, &allocator); + + OK(scad_add_box("cavity", p1, diago, &geom1)); + OK(scad_geometry_boundary("bcavity", &geom1, 1, &geom2)); + OK(scad_geometry_explode(geom2, "explode", &list, &list_n)); + OK(scad_geometry_ref_put(geom2)); + OK(scad_geometry_get_count(list[0], &center_n)); + ASSERT(center_n == 1); + OK(scad_geometry_get_centerofmass(list[0], center)); + OK(scad_geometry_normal(list[0], center, N, "surface", &geom)); + OK(scad_geometry_ref_put(geom)); + for(i = 0; i < list_n; i++) { + OK(scad_geometry_ref_put(list[i])); + } + MEM_RM(&allocator, list); + OK(scad_geometry_ref_put(geom1)); + + /* Check that 2D constituants of a deleted 3D object are alive and well after + * a partition */ + OK(scad_add_box("box1", p1, diago, geoms+0)); + OK(scad_add_box("box2", p1, diago_, geoms+1)); + OK(scad_geometry_boundary(NULL, geoms+0, 1, &geom1)); + OK(scad_geometry_boundary(NULL, geoms+1, 1, &geom2)); + OK(scad_geometries_partition(geoms, 2, 0, out_geoms)); + OK(scad_geometry_ref_put(geoms[0])); + OK(scad_geometry_ref_put(geoms[1])); + alive_and_well(geom1, &allocator); + alive_and_well(geom2, &allocator); + OK(scad_geometry_ref_put(out_geoms[0])); + OK(scad_geometry_ref_put(out_geoms[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 @@ -65,14 +65,14 @@ two_geoms if(allow_overlapping) { snprintf(name, sizeof(name), "part_%g_1o", x); - OK(scad_stl_export(out_geoms[0], name, 0)); + OK(scad_stl_export(out_geoms[0], name, Scad_force_normals_outward, 0)); snprintf(name, sizeof(name), "part_%g_2o", x); - OK(scad_stl_export(out_geoms[1], name, 1)); + OK(scad_stl_export(out_geoms[1], name, Scad_force_normals_outward, 1)); } else { snprintf(name, sizeof(name), "part_%g_1", x); - OK(scad_stl_export(geoms[0], name, 0)); + OK(scad_stl_export(geoms[0], name, Scad_force_normals_outward, 0)); snprintf(name, sizeof(name), "part_%g_2", x); - OK(scad_stl_export(geoms[1], name, 1)); + OK(scad_stl_export(geoms[1], name, Scad_force_normals_outward, 1)); } end: