star-cad

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

commit 230008e60500a5ca03cee2b0a131bb00d7aaf3f3
parent 960e7c5329933d5640dd58084801a86c8bafb20b
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Thu, 31 Aug 2023 10:30:59 +0200

Huge change (mesh orientation, lifetime manangemnt)

Mesh are no longer oriented from geometry properties, but when they are written

Lifetime management considers 2 different topics: geometry and underlying OCC entities. star-cad users can get and put references on geometries, but OCC entities are managed silently and are released when no geometry requires them. There is a trick there: 2D entities that are part of a 3D entity need to be protected because deleting a 3D entity also deletes its 2D constituents

Diffstat:
Mcmake/CMakeLists.txt | 11+++++++++--
Msrc/scad.c | 374++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/scad.h | 159+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/scad_device.c | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/scad_device.h | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/scad_geometry.c | 734++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/scad_geometry.h | 2--
Msrc/test_api.c | 79++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/test_export.c | 17++++++++++-------
Asrc/test_lifetime.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_partition.c | 8++++----
11 files changed, 1528 insertions(+), 509 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -26,12 +26,18 @@ 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 @@ -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 @@ -29,6 +29,9 @@ #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 +49,14 @@ 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; 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,15 +66,12 @@ 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], n[3], tmp[3], edge1[3], edge2[3], 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); @@ -104,19 +104,19 @@ 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 i; unsigned trg_count; char header[80] = "Binary STL"; FILE* stl_file = NULL; - 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,26 +126,30 @@ write_binary_stl goto error; } - trg_count = (unsigned)coord_n / 9; + trg_count = (unsigned)count; 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)) { + log_error(get_device(), "Error: nul triangle in exported geometry.\n"); + res = RES_BAD_ARG; + goto error; + } f3_normalize(trg.n, tmp); trg.attrib = 0; if(1 != fwrite(&trg, 50, 1, stl_file)) { @@ -189,8 +193,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 +208,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 +282,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) @@ -357,31 +371,37 @@ scad_stl_get_data_partial } /* Collect triangles */ - tcount = 0; + 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; exit: if(!allocator) { @@ -404,15 +424,232 @@ error: goto exit; } +static INLINE void +get_indices(const unsigned itri, unsigned ids[3], void* context) +{ + const double* ctx = context; + ASSERT(ids && 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 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 is 'unchanged' convention has no effect on the result */ + 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, SENC3D_NTHREADS_DEFAULT, + 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_enclosure_count(senc3d_scn, &ecount)); + if(orientation != Scad_keep_normals_unchanged && 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 +661,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 +684,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 +694,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 +704,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 +732,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 +743,7 @@ scad_stl_export_partial int initialized = 0; if(!geometry) { - res= RES_BAD_ARG; + res = RES_BAD_ARG; goto error; } @@ -500,10 +752,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 +763,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 +778,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 +789,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; 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,7 +107,7 @@ 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; }; @@ -116,7 +117,7 @@ struct scad_options { 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__; @@ -127,6 +128,13 @@ enum scad_swap_elements { 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 /******************************************************************************* @@ -151,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 @@ -318,9 +319,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. */ @@ -329,7 +328,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 */ + struct scad_geometry** out_geometries); /* Get the boundary of the geometry `geom'. */ SCAD_API res_T @@ -352,25 +351,34 @@ 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 @@ -381,8 +389,8 @@ scad_geometry_extrude 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. */ SCAD_API res_T scad_geometry_explode (const struct scad_geometry* geom, @@ -401,73 +409,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); -/* Open gmsh in GUI mode so that the model can be inspected and even modified. - * Can be called from a breakpoint in gdb: call scad_run_ui() - * (then close gmsh and gdb is back to the breakpoint). */ -SCAD_API res_T -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, @@ -476,15 +490,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 tag %d.%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, "Tag %d.%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 tag %d.%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, - "Tag %d.%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, "Tag %d.%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); + + if(!desc) { + res = RES_BAD_OP; + goto error; + } + + 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); - gmshModelOccRemove(dimTags+i, 2, 1, &ierr); - ERR(gmsh_err_to_res_T(ierr)); + 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)); + } } -end: + 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,14 +672,15 @@ 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.LogOpenCascadeTagsRefCounting== Scad_log_all); + dev->log + = (dev->options.Misc.LogRefCounting & Scad_log_dimTags_all); exit: return res; 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,6 @@ 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 MEM_RM(allocator, geom); } @@ -294,19 +304,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 +374,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 +382,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) { @@ -415,36 +465,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; @@ -507,7 +552,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)); @@ -555,7 +600,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)); @@ -632,7 +677,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) { @@ -683,7 +728,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) { @@ -733,7 +778,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) { @@ -781,7 +826,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) { @@ -837,12 +882,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) { @@ -905,12 +950,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) { @@ -978,12 +1023,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; @@ -1040,6 +1085,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; @@ -1053,7 +1103,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); @@ -1062,7 +1112,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; @@ -1077,7 +1127,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); @@ -1109,32 +1203,37 @@ 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; } @@ -1146,16 +1245,22 @@ scad_geometry_extrude 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(); - struct scad_geometry* tmp = NULL; + 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; @@ -1165,18 +1270,14 @@ scad_geometry_extrude ERR(check_device(FUNC_NAME)); allocator = dev->allocator; - /* Need to duplicate geom or deleting out_geometry will delete geom too */ - ERR(scad_geometry_copy(geom, NULL, &tmp)); - - gmshModelOccExtrude(tmp->gmsh_dimTags, tmp->gmsh_dimTags_n, SPLIT3(dxdydz), + 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)); - /* Output include both the 3D result and its 2D constituants. + /* Output includes both the 3D result and its 2D constituants. * Keep only 3D entities. */ - for(i=0; i<tagoutn; i+=2) { + for(i = 0; i < tagoutn; i += 2) { int dim = tagout[i]; if(dim == 3) extrude_sz += 2; } @@ -1185,22 +1286,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; @@ -1209,7 +1354,7 @@ error: SCAD(geometry_ref_put(extrude_geom)); extrude_geom = NULL; } - if(tmp) SCAD(geometry_ref_put(tmp)); + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); goto exit; } @@ -1253,13 +1398,14 @@ scad_geometry_explode 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 @@ -1320,7 +1466,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) { @@ -1367,55 +1513,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 @@ -1435,14 +1579,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; } @@ -1452,136 +1597,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); } - memcpy(out_geometries, geoms, geometries_count * sizeof(*geoms)); + 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(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; @@ -1745,8 +1925,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; @@ -1760,7 +1945,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) { @@ -1771,7 +1956,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); @@ -1828,9 +2050,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; @@ -1865,7 +2087,6 @@ error: goto exit; } - res_T scad_geometry_normal (struct scad_geometry* geom, @@ -1877,15 +2098,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; @@ -1895,36 +2119,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; @@ -1933,11 +2153,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; @@ -1948,9 +2186,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; @@ -1958,33 +2195,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; 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: