star-cad

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

commit bb36c865c041d5608a5882169589c951ed82e883
parent c127175d8610c030bd6aa997832c1152abb46cdb
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Wed, 15 Oct 2025 11:50:09 +0200

Reintroduce the collect version of scad_geometries_[common_]boundaries

Namely scad_geometries_boundary and scad_geometries_common_boundary.

While upgrading codes to star-cad 0.6, it appeared that the list
versions could not fit all needs.

Diffstat:
MREADME.md | 10++++------
Msrc/scad.h | 20++++++++++++++++++++
Msrc/scad_geometry.c | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_api.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 311 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md @@ -31,11 +31,9 @@ Edit config.mk as needed, then run: - API changes for geometry creation: geometry name is no longer provided to the various add_<something> functions, that now create unnamed geometries; instead names have to be set after geometry creation through `scad_geometry_set_name'. -- API change for `scad_geometries_boundaries' and - `scad_geometries_common_boundaries': now return a list of geometries - (that could be grouped through `scad_collect_geometries'); used to return a - single geometry containing the (possibly empty) boundary elements (that could - be ungrouped using `scad_geometry_explode'). +- Add `scad_geometries_boundaries' and `scad_geometries_common_boundaries' that + return a list of geometries instead of a (possibly empty) single geometry + containing all the boundary elements. - API uniformization (involving args order, naming, etc.). - Fix geometry naming. Now get_name returns NULL if name was set to NULL or let unset (used to return ""). Allow to set same name again (to the same @@ -45,7 +43,7 @@ Edit config.mk as needed, then run: point, fixing a memleak in the process. - Fix `scad_scene_clear' (geometries with multiple references where not released). -- Fix `scad_geometries_common_boundaries'. Used to trigger OccIntersect that, as +- Fix `scad_geometries_common_boundary'. Used to trigger OccIntersect that, as a side effet, partitionned geometries. - Fix `scad_step_import' (arguments where inverted). - Change `scad_geometry_get_centerofmass'. Now returns the unique center of mass diff --git a/src/scad.h b/src/scad.h @@ -581,6 +581,17 @@ scad_geometries_common_boundaries struct scad_geometry*** out_boundaries, size_t *out_count); +/* Same as above, with the boundary entities collected in a single geometry. + * Note that there is always an output geometry returned is `out_boundary', that + * can be empty (scad_geometry_get_count = 0). */ +SCAD_API res_T +scad_geometries_common_boundary + (struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_boundary); + /* Get the boundaries of the geometries in `geometries', considered as a whole. * The output geometries are created unnamed. * The result `out_boundaries' being allocated using the allocator provided when @@ -592,6 +603,15 @@ scad_geometries_boundaries struct scad_geometry*** out_boundaries, size_t *out_count); +/* Same as above, with the boundary entities collected in a single geometry. + * Note that there is always an output geometry returned is `out_boundary', that + * can be empty (scad_geometry_get_count = 0). */ +SCAD_API res_T +scad_geometries_boundary + (struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** out_boundary); + /* Copy the geometry `geometry', except for its name. * The new geometry remains unnamed. */ SCAD_API res_T diff --git a/src/scad_geometry.c b/src/scad_geometry.c @@ -2104,6 +2104,134 @@ error: } res_T +scad_geometries_common_boundary + (struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** tools, + const size_t tools_count, + struct scad_geometry** out_boundary) +{ + res_T res = RES_OK; + int* tagout = NULL; + size_t tagoutn, sz1, sz2, u_sz; + int* data1 = NULL; + int* data2 = NULL; + int* unique = NULL; + int ierr = 0; + int* bound1 = NULL; + int* bound2 = NULL; + size_t n1, n2; + struct scad_geometry* out_geom = NULL; + struct mem_allocator* allocator = NULL; + struct scad_device* dev = get_device(); + int log; + enum log_type log_type; + size_t i, n; + struct str msg; + int msg_initialized = 0; + + if(!geometries || !geometries_count || !tools || !tools_count || !out_boundary) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + ERR(gather_tags(geometries, geometries_count, &data1, &sz1)); + ERR(gather_tags(tools, tools_count, &data2, &sz2)); + + /* data1 and data2 can have tags in common: deduplicate them! + * (even if the refcounting stuff can manage duplicates) */ + ERR(process_tag_list(data1, sz1, data2, sz2, UNIQUE_TAGS, &unique, &u_sz)); + + ERR(sync_device()); + gmshModelGetBoundary(data1, sz1, &bound1, &n1, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + gmshModelGetBoundary(data2, sz2, &bound2, &n2, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + ERR(process_tag_list(bound1, n1, bound2, n2, COMMON_TAGS, &tagout, &tagoutn)); + ASSERT(tagoutn % 2 == 0); + + if(tagoutn == 0) { + log_message(dev, "Common boundary list is empty.\n"); + } else { + log = (dev->options.Misc.LogRefCounting & SCAD_LOG_DIMTAGS_ALL); + log_type = dev->log_type; + if(log) { + str_init(allocator, &msg); + msg_initialized = 1; + logger_print(dev->logger, log_type, + "Common 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 < u_sz; i += 2) { + const int dim = unique[i]; + const int tag = unique[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)); + } + } + + ERR(geometry_create(&out_geom)); + out_geom->gmsh_dimTags_n = tagoutn; + out_geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!out_geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(out_geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + ERR(device_register_tags(out_geom)); + + for(i = 0, n = 0; i < tagoutn; i += 2, n++) { + int dim = tagout[i]; + int tag = tagout[i+1]; + struct tag_desc* desc = device_get_description(dim, tag); + ASSERT(desc); + ASSERT(dim == 2); + /* 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, unique, u_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(msg_initialized) str_release(&msg); + if(out_boundary) *out_boundary = out_geom; + if(allocator) { + MEM_RM(allocator, data1); + MEM_RM(allocator, data2); + MEM_RM(allocator, unique); + MEM_RM(allocator, tagout); + } + gmshFree(bound1); + gmshFree(bound2); + return res; +error: + if(ierr) { + int dim = INT_MAX; + if(!mixed_dim_err_msg("common boundary", geometries, geometries_count, &dim)) + mixed_dim_err_msg("common boundary", tools, tools_count, &dim); + } + if(out_geom) { + SCAD(geometry_ref_put(out_geom)); + out_geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + +res_T scad_geometry_rotate (const struct scad_geometry* geom, const double pt[3], @@ -3023,6 +3151,101 @@ error: goto exit; } + +res_T +scad_geometries_boundary + (struct scad_geometry** geometries, + const size_t geometries_count, + struct scad_geometry** out_boundary) +{ + res_T res = RES_OK; + int* tagout = NULL; + size_t tagoutn, sz; + int* data = NULL; + int ierr = 0; + struct scad_geometry* out_geom = NULL; + struct mem_allocator* allocator = NULL; + struct scad_device* dev = get_device(); + int log; + enum log_type log_type; + size_t i; + struct str msg; + int msg_initialized = 0; + + if(!geometries || geometries_count == 0 || !out_boundary) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + allocator = dev->allocator; + + ERR(gather_tags(geometries, geometries_count, &data, &sz)); + ERR(sync_device()); + gmshModelGetBoundary(data, sz, &tagout, &tagoutn, 1, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ASSERT(tagoutn % 2 == 0); + + log = (dev->options.Misc.LogRefCounting & SCAD_LOG_DIMTAGS_ALL); + log_type = dev->log_type; + if(log) { + str_init(allocator, &msg); + msg_initialized = 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)); + } + + ERR(geometry_create(&out_geom)); + out_geom->gmsh_dimTags_n = tagoutn; + out_geom->gmsh_dimTags = MEM_ALLOC(allocator, tagoutn * sizeof(*tagout)); + if(!out_geom->gmsh_dimTags) { + res = RES_MEM_ERR; + goto error; + } + memcpy(out_geom->gmsh_dimTags, tagout, tagoutn * sizeof(*tagout)); + ERR(device_register_tags(out_geom)); + for(i = 0; i < out_geom->gmsh_dimTags_n; i += 2) { + const int dim = out_geom->gmsh_dimTags[i]; + const int tag = out_geom->gmsh_dimTags[i+1]; + struct tag_desc* desc = device_get_description(dim, tag); + ASSERT(dim == 2); + ASSERT(desc != NULL); + /* 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, 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 is + * released. */ + desc->delete_policy = Scad_do_not_delete; + } + +exit: + if(msg_initialized) str_release(&msg); + if(allocator) MEM_RM(allocator, data); + if(out_boundary) *out_boundary = out_geom; + gmshFree(tagout); + return res; +error: + if(out_geom) { + SCAD(geometry_ref_put(out_geom)); + out_geom = NULL; + } + if(tagout) gmshModelOccRemove(tagout, tagoutn, 1, &ierr); + goto exit; +} + res_T scad_step_import (const char* filename, diff --git a/src/test_api.c b/src/test_api.c @@ -134,7 +134,9 @@ main(int argc, char* argv[]) BAD(scad_geometries_intersect(&geom1, 1, &geom2, 1, &geom)); BAD(scad_geometries_partition(&geom1, 1, 0, &geom)); BAD(scad_geometries_common_boundaries(&geom1, 1, &geom2, 1, &geom_array, &c)); + BAD(scad_geometries_common_boundary(&geom1, 1, &geom2, 1, &geom)); BAD(scad_geometries_boundaries(&geom1, 1, &geom_array, &c)); + BAD(scad_geometries_boundary(&geom1, 1, &geom)); BAD(scad_geometry_copy(geom1, &geom2)); BAD(scad_geometry_set_visibility(geom1, 0, 0)); BAD(scad_geometries_set_periodic(&geom1, 1, &geom2, 1, affine)); @@ -202,7 +204,9 @@ main(int argc, char* argv[]) BAD(scad_geometries_intersect(&geom1, 1, &geom2, 1, &geom)); BAD(scad_geometries_partition(&geom1, 1, 0, &geom)); BAD(scad_geometries_common_boundaries(&geom1, 1, &geom2, 1, &geom_array, &c)); + BAD(scad_geometries_common_boundary(&geom1, 1, &geom2, 1, &geom)); BAD(scad_geometries_boundaries(&geom1, 1, &geom_array, &c)); + BAD(scad_geometries_boundary(&geom1, 1, &geom)); BAD(scad_geometry_copy(geom1, &geom2)); BAD(scad_geometry_set_visibility(geom1, 0, 0)); BAD(scad_geometries_set_periodic(&geom1, 1, &geom2, 1, affine)); @@ -1375,6 +1379,51 @@ main(int argc, char* argv[]) OK(scad_scene_count(&c)); CHK(c == 0); + OK(scad_add_sphere(p1, 0.1, &geoms[0])); + OK(scad_add_sphere(p3, 0.1, &geoms[1])); + OK(scad_geometries_partition(geoms, 2, SCAD_ALLOW_OVERLAPPING, out_geoms)); + OK(scad_geometries_swap(geoms, out_geoms, 2, SCAD_SWAP_GEOMETRY)); + OK(scad_geometry_ref_put(out_geoms[0])); + OK(scad_geometry_ref_put(out_geoms[1])); + BAD(scad_geometries_common_boundary(NULL, 0, NULL, 0, NULL)); + BAD(scad_geometries_common_boundary(NULL, 0, NULL, 0, &geom)); + BAD(scad_geometries_common_boundary(NULL, 0, NULL, 1, NULL)); + BAD(scad_geometries_common_boundary(NULL, 0, NULL, 1, &geom)); + BAD(scad_geometries_common_boundary(NULL, 0, geoms+1, 0, NULL)); + BAD(scad_geometries_common_boundary(NULL, 0, geoms+1, 0, &geom)); + BAD(scad_geometries_common_boundary(NULL, 0, geoms+1, 1, NULL)); + BAD(scad_geometries_common_boundary(NULL, 0, geoms+1, 1, &geom)); + BAD(scad_geometries_common_boundary(NULL, 1, NULL, 0, NULL)); + BAD(scad_geometries_common_boundary(NULL, 1, NULL, 0, &geom)); + BAD(scad_geometries_common_boundary(NULL, 1, NULL, 1, NULL)); + BAD(scad_geometries_common_boundary(NULL, 1, NULL, 1, &geom)); + BAD(scad_geometries_common_boundary(NULL, 1, geoms+1, 0, NULL)); + BAD(scad_geometries_common_boundary(NULL, 1, geoms+1, 0, &geom)); + BAD(scad_geometries_common_boundary(NULL, 1, geoms+1, 1, NULL)); + BAD(scad_geometries_common_boundary(NULL, 1, geoms+1, 1, &geom)); + BAD(scad_geometries_common_boundary(geoms, 0, NULL, 0, NULL)); + BAD(scad_geometries_common_boundary(geoms, 0, NULL, 0, &geom)); + BAD(scad_geometries_common_boundary(geoms, 0, NULL, 1, NULL)); + BAD(scad_geometries_common_boundary(geoms, 0, NULL, 1, &geom)); + BAD(scad_geometries_common_boundary(geoms, 0, geoms+1, 0, NULL)); + BAD(scad_geometries_common_boundary(geoms, 0, geoms+1, 0, &geom)); + BAD(scad_geometries_common_boundary(geoms, 0, geoms+1, 1, NULL)); + BAD(scad_geometries_common_boundary(geoms, 0, geoms+1, 1, &geom)); + BAD(scad_geometries_common_boundary(geoms, 1, NULL, 0, NULL)); + BAD(scad_geometries_common_boundary(geoms, 1, NULL, 0, &geom)); + BAD(scad_geometries_common_boundary(geoms, 1, NULL, 1, NULL)); + BAD(scad_geometries_common_boundary(geoms, 1, NULL, 1, &geom)); + BAD(scad_geometries_common_boundary(geoms, 1, geoms+1, 0, NULL)); + BAD(scad_geometries_common_boundary(geoms, 1, geoms+1, 0, &geom)); + BAD(scad_geometries_common_boundary(geoms, 1, geoms+1, 1, NULL)); + OK(scad_geometries_common_boundary(geoms, 1, geoms+1, 1, &geom)); + OK(scad_geometry_ref_put(geoms[0])); + OK(scad_geometry_ref_put(geoms[1])); + OK(scad_geometry_ref_put(geom)); + + OK(scad_scene_count(&c)); + CHK(c == 0); + OK(scad_add_sphere(p1, 0.1, &geom1)); BAD(scad_geometries_boundaries(NULL, 0, NULL, NULL)); BAD(scad_geometries_boundaries(NULL, 0, NULL, &c)); @@ -1403,6 +1452,21 @@ main(int argc, char* argv[]) CHK(c == 0); OK(scad_add_sphere(p1, 0.1, &geom1)); + BAD(scad_geometries_boundary(NULL, 0, NULL)); + BAD(scad_geometries_boundary(NULL, 0, &geom)); + BAD(scad_geometries_boundary(NULL, 1, NULL)); + BAD(scad_geometries_boundary(&geom1, 0, NULL)); + BAD(scad_geometries_boundary(NULL, 1, &geom)); + BAD(scad_geometries_boundary(&geom1, 0, &geom)); + BAD(scad_geometries_boundary(&geom1, 1, NULL)); + OK(scad_geometries_boundary(&geom1, 1, &geom)); + OK(scad_geometry_ref_put(geom1)); + OK(scad_geometry_ref_put(geom)); + + OK(scad_scene_count(&c)); + CHK(c == 0); + + OK(scad_add_sphere(p1, 0.1, &geom1)); BAD(scad_geometry_copy(NULL, &geom)); BAD(scad_geometry_copy(geom1, NULL)); OK(scad_geometry_copy(geom1, &geom));