star-cad

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

commit 12dcc05afc4230809589aa199c1480bc792f418f
parent ff863c4029a9dc0b90e2af6de6826a5da29b0866
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Mon,  3 Mar 2025 10:50:08 +0100

Merge branch 'release_0.5.1' into develop

Diffstat:
Mconfig.mk | 2+-
Msrc/scad.h | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/scad_device.c | 40+++++++++++++++++++++++++++++++++++++---
Msrc/scad_device.h | 6++++++
Msrc/scad_geometry.c | 431++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 565 insertions(+), 12 deletions(-)

diff --git a/config.mk b/config.mk @@ -1,4 +1,4 @@ -VERSION = 0.5.0 +VERSION = 0.5.1 PREFIX = /usr/local LIB_TYPE = SHARED diff --git a/src/scad.h b/src/scad.h @@ -60,8 +60,10 @@ enum scad_verbosity_levels { enum scad_mesh_algorithm { Scad_meshAdapt = 1, Scad_Automatic = 2, /* Delaunay on planes, meshAdapt elsewhere */ + Scad_Initial_Mesh_Only = 3, /* Avoid new point creation */ Scad_Delaunay = 5, - Scad_frontal_Delaunay = 6 + Scad_frontal_Delaunay = 6, + Scad_Quasi_Structured = 11 }; enum scad_sizes_extend_from_boundary { @@ -85,17 +87,25 @@ enum scad_log_refcounting { Scad_log_geometry = BIT(2) }; +/* A type to specify the kind of mesh size specification set by a call to the + * scad_geometries_set_mesh_size_modifier API call */ +enum scad_size_modifier_type { + Scad_absolute_size, + Scad_size_factor +}; + /* A type to specify options for the gmsh library */ struct scad_options { struct { - enum scad_mesh_algorithm Algorithm; - enum scad_sizes_extend_from_boundary MeshSizeExtendFromBoundary; double MeshSizeFactor; double MeshSizeFromCurvature; - int MeshSizeFromPoints; double MeshSizeMax; double MeshSizeMin; double Smoothing; + int MeshSizeFromPoints; + int MeshOnlyVisible; + enum scad_mesh_algorithm Algorithm; + enum scad_sizes_extend_from_boundary MeshSizeExtendFromBoundary; enum scad_stl_solids StlOneSolidPerSurface; } Mesh; struct { @@ -114,8 +124,10 @@ struct scad_options { }; #define SCAD_DEFAULT_OPTIONS__ \ - { { Scad_frontal_Delaunay, Scad_surfaces_and_volumes, 1, 36, 1, 1e+22, 1e-6, \ - 1, Scad_one_solid_per_physical_surface }, \ + { { 1, 36, 1e+22, 1e-6, 1, 1, 1, \ + Scad_frontal_Delaunay, \ + Scad_surfaces_and_volumes, \ + Scad_one_solid_per_physical_surface }, \ { Scad_verbosity_errors, 1 }, \ { 1 }, \ { 0, 0, Scad_log_none, 0 } \ @@ -166,6 +178,10 @@ SCAD_API res_T scad_set_options (const struct scad_options* options); /* May be NULL: set default */ +SCAD_API res_T +scad_get_options + (struct scad_options* options); + /******************************************************************************* * Geometry API - A geometry is a primitive, a group of primitives, or the * result of an operation on geometries. @@ -238,6 +254,20 @@ scad_geometry_get_centerofmass (struct scad_geometry* geom, double* center); +SCAD_API res_T +scad_geometry_get_closest_point + (struct scad_geometry* geom, + const double* from, + double* closest, + double* closest_distance); + +/* Get the Boundig Box of geometry `geom' */ +SCAD_API res_T +scad_geometry_get_bounding_box + (struct scad_geometry* geom, + double min[3], + double max[3]); + /* Add a rectangle to the scene, defined by a point `xyz' and * `dxdy' the extents along the x-, y-axes. */ SCAD_API res_T @@ -383,7 +413,7 @@ scad_geometries_partition const int flags, struct scad_geometry** out_geometries); -/* Get the boundary of the geometry `geom'. */ +/* Get the boundary of the geometries in `geometries'. */ SCAD_API res_T scad_geometry_boundary (const char* name, /* Can be NULL */ @@ -398,6 +428,13 @@ scad_geometry_copy const char* name, /* Can be NULL */ struct scad_geometry** out_copy); +/* copy the geometry `geom'. */ +SCAD_API res_T +scad_geometry_set_visibility + (const struct scad_geometry* geom, + int visible, + int recursive); + /* Change the name of geometry `geom'. */ SCAD_API res_T scad_geometry_rename @@ -534,6 +571,53 @@ SCAD_API res_T scad_scene_mesh (void); +/* Flag `target' geometries as being the result of applying the `affine' + * tranform to `source' geometries. + * The result is that the mesh generated for `target' is the image on the mesh + * generated for `source' through the `affine' transform. + * Only apply to surfaces (dimension 2). If called on a volume, it applies to + * its 2D constituents. + * The two sets of surfaces must match exactly (same number of points, etc.). */ +SCAD_API res_T +scad_geometries_set_periodic + (struct scad_geometry** source, + const size_t source_count, + struct scad_geometry** target, + const size_t target_count, + double affine[16]); + +/* Set a size modifier for geometries in `geometries'. + * When meshing these geometries, triangles' size will be either size*modifier, + * or modifier where size would be the size of the triangle in the absence of a + * size modifier. + * The size modifier is applied recursively down to dimension 0 (points). + * If multiple size modifiers are applied, the order matters as the last applied + * size modifier remains. + * To reset a size modifier, just apply a new Scad_size_factor modifier of 1. */ +SCAD_API res_T +scad_geometries_set_mesh_size_modifier + (struct scad_geometry** geometries, + const size_t geometries_count, + enum scad_size_modifier_type type, + double modifier); + +/* Set a specific mesh algorithm for geometries in `geometries'. + * Only apply to surfaces (dimension 2). If called on a volume, it applies to + * its 2D constituents. */ +SCAD_API res_T +scad_geometries_set_mesh_algorithm + (struct scad_geometry** geometries, + const size_t geometries_count, + enum scad_mesh_algorithm algorithm); + +/* Clear the mesh of the geometries in `geometries'. + * Note that the mesh of a geometry can only be cleared if it is not on the + * boundary of another geometry with a non-empty mesh. */ +SCAD_API res_T +scad_geometries_clear_mesh + (struct scad_geometry** geometries, + const size_t geometries_count); + /* 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'. */ diff --git a/src/scad_device.c b/src/scad_device.c @@ -120,6 +120,10 @@ device_release(struct scad_device* dev) htable_geometries_release(&dev->allgeom); device_release_tags_of_dim(dev, 2); device_release_tags_of_dim(dev, 3); + htable_size_modifiers_release(&dev->size_modifiers_by_dim[0]); + htable_size_modifiers_release(&dev->size_modifiers_by_dim[1]); + htable_size_modifiers_release(&dev->size_modifiers_by_dim[2]); + htable_size_modifiers_release(&dev->size_modifiers_by_dim[3]); if(log) { logger_print(dev->logger, log_type, "End finalizing scad.\n"); } @@ -597,6 +601,10 @@ scad_initialize htable_geometries_init(allocator, &g_device->allgeom); htable_tags2desc_init(allocator, &g_device->tags2desc[0]); htable_tags2desc_init(allocator, &g_device->tags2desc[1]); + htable_size_modifiers_init(allocator, &g_device->size_modifiers_by_dim[0]); + htable_size_modifiers_init(allocator, &g_device->size_modifiers_by_dim[1]); + htable_size_modifiers_init(allocator, &g_device->size_modifiers_by_dim[2]); + htable_size_modifiers_init(allocator, &g_device->size_modifiers_by_dim[3]); /* Init to default */ scad_set_options(NULL); @@ -679,15 +687,16 @@ scad_set_options dev = get_device(); keep = dev->options; - SET_GMSH_OPTION_INT(Mesh.Algorithm); - SET_GMSH_OPTION_INT(Mesh.MeshSizeExtendFromBoundary); SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeFactor); SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeFromCurvature); - SET_GMSH_OPTION_INT(Mesh.MeshSizeFromPoints); SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeMax); SET_GMSH_OPTION_DOUBLE(Mesh.MeshSizeMin); SET_GMSH_OPTION_DOUBLE(Mesh.Smoothing); SET_GMSH_OPTION_INT(Mesh.StlOneSolidPerSurface); + SET_GMSH_OPTION_INT(Mesh.MeshOnlyVisible); + SET_GMSH_OPTION_INT(Mesh.Algorithm); + SET_GMSH_OPTION_INT(Mesh.MeshSizeExtendFromBoundary); + SET_GMSH_OPTION_INT(Mesh.MeshSizeFromPoints); SET_GMSH_OPTION_INT(General.Verbosity); SET_GMSH_OPTION_INT(General.ExpertMode); @@ -715,4 +724,29 @@ error: goto exit; } +#undef SET_GMSH_OPTION_INT #undef SET_GMSH_OPTION_DOUBLE + +res_T +scad_get_options + (struct scad_options* options) +{ + res_T res = RES_OK; + struct scad_device* dev = NULL; + + if(! options) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + dev = get_device(); + + *options = dev->options; + +exit: + return res; +error: + goto exit; +} diff --git a/src/scad_device.h b/src/scad_device.h @@ -115,6 +115,11 @@ tag_desc_copy_and_release #define HTABLE_DATA_FUNCTOR_COPY_AND_RELEASE tag_desc_copy_and_release #include <rsys/hash_table.h> +#define HTABLE_NAME size_modifiers +#define HTABLE_KEY int +#define HTABLE_DATA double +#include <rsys/hash_table.h> + struct scad_device { struct logger* logger; struct mem_allocator* allocator; @@ -122,6 +127,7 @@ struct scad_device { struct htable_names geometry_names; struct htable_geometries allgeom; struct htable_tags2desc tags2desc[2]; /* Only geoms for 2D and 3D tags for now */ + struct htable_size_modifiers size_modifiers_by_dim[4]; int verbose; int need_synchro; diff --git a/src/scad_geometry.c b/src/scad_geometry.c @@ -251,6 +251,123 @@ error: goto exit; } +static res_T +store_tags + (int* dimTags, + size_t count, + struct htable_tags t[4]) +{ + res_T res = RES_OK; + char one = 1; + size_t i; + ASSERT((dimTags || count == 0) && t); + for(i = 0; i < count; i += 2) { + int dim = dimTags[i]; + int tag = dimTags[i+1]; + ASSERT(dim >= 0 && dim <= 3); + ERR(htable_tags_set(t+dim, &tag, &one)); + } + +exit: + return res; +error: + goto exit; +} + +static res_T +gather_tags_recursive + (struct scad_geometry** geometries, + const size_t geometries_count, + const int down_to_dim, + int* out_dimTags[4], + size_t out_dimTags_n[4]) +{ + res_T res = RES_OK; + int* dimTags[4] = { NULL, NULL, NULL, NULL }, *sub = NULL; + size_t i, sz[4]; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = dev->allocator; + struct htable_tags t[4]; + struct htable_tags_iterator it, end; + int dim; + + ASSERT((geometries || geometries_count == 0) && out_dimTags && out_dimTags_n); + ASSERT(0 <= down_to_dim && down_to_dim <= 3); + + for(i = (size_t)down_to_dim; i < 4; i++) { + htable_tags_init(allocator, t+i); + } + + /* list tags by dimension and remove duplicates */ + for(i = 0; i < geometries_count; i++) { + if(!geometries[i]) { + res = RES_BAD_ARG; + goto error; + } + ERR(store_tags(geometries[i]->gmsh_dimTags, geometries[i]->gmsh_dimTags_n, t)); + } + + /* Recursively build result by dimension and list constituents, + * begining with dim==3 */ + for(dim = 3; dim >= down_to_dim; dim--) { + size_t c = 0; + sz[dim] = 2 * htable_tags_size_get(t+dim); + if(sz[dim] == 0) continue; + dimTags[dim] = MEM_ALLOC(allocator, sz[dim] * sizeof(*dimTags)); + if(!dimTags[dim]) { + res = RES_MEM_ERR; + goto error; + } + htable_tags_begin(t+dim, &it); + htable_tags_end(t+dim, &end); + while(!htable_tags_iterator_eq(&it, &end)) { + dimTags[dim][c++] = dim; + dimTags[dim][c++] = *htable_tags_iterator_key_get(&it); + htable_tags_iterator_next(&it); + } + if(dim > down_to_dim) { + int ierr; + size_t subn; + gmshModelGetBoundary(dimTags[dim], sz[dim], &sub, &subn, 0, 0, 0, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + ERR(store_tags(sub, subn, t)); + gmshFree(sub); sub = NULL; + } + ASSERT(sz[dim] == c); + } + + for(i = (size_t)down_to_dim; i < 4; i++) { + out_dimTags_n[i] = sz[i]; + out_dimTags[i] = dimTags[i]; + } + +exit: + if(sub) gmshFree(sub); + for(i = (size_t)down_to_dim; i < 4; i++) { + htable_tags_release(t+i); + } + return res; +error: + for(i = (size_t)down_to_dim; i < 4; i++) { + MEM_RM(allocator, dimTags[i]); + } + goto exit; +} + +static double size_callback + (int dim, int tag, double x, double y, double z, double lc, void* data_) +{ + struct scad_device* dev = get_device(); + double *modifier = + htable_size_modifiers_find(dev->size_modifiers_by_dim + dim, &tag); + (void)x;(void)y;(void)z;(void)data_; + if(!modifier) + return lc; /* No modifier defined */ + if(*modifier < 0) + return -(*modifier); /* Absolute size modifier */ + return (*modifier) * lc; /* Size factor modifier */ +} + /* gmsh documentation states that memory allocated by gmsh should be freed using * gmshFree. * According to valgrind map results as allocated by gmsh are not fully freed if @@ -490,6 +607,71 @@ error: } res_T +scad_geometry_set_visibility + (const struct scad_geometry* geom, + int visible, + int recursive) +{ + res_T res = RES_OK; + int ierr; + int* data = NULL; + size_t sz; + + if(!geom) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + data = geom->gmsh_dimTags; + sz = geom->gmsh_dimTags_n; + + gmshModelSetVisibility(data, sz, visible, recursive, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + get_device()->need_synchro = 1; + +exit: + return res; +error: + goto exit; +} + +res_T +scad_geometries_clear_mesh + (struct scad_geometry** geometries, + const size_t geometries_count) +{ + res_T res = RES_OK; + int ierr; + size_t sz; + int* data = NULL; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = NULL; + + if(!geometries) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + allocator = dev->allocator; + ERR(gather_tags(geometries, geometries_count, &data, &sz)); + gmshModelMeshClear(data, sz, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + + dev->need_synchro = 1; + +exit: + if(allocator) MEM_RM(allocator, data); + return res; +error: + goto exit; +} + +res_T scad_geometry_get_name (const struct scad_geometry* geom, const char** name) @@ -548,6 +730,59 @@ error: } res_T +scad_geometry_get_closest_point + (struct scad_geometry* geom, + const double* from, + double* closest, + double* closest_distance) +{ + res_T res = RES_OK; + size_t i = 0; + double* coord = NULL; + double* pcoord = NULL; + double tmp[3], min[3], min_d = DBL_MAX; + + if(!geom || !from || !closest) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + for(i = 0; i < geom->gmsh_dimTags_n; i += 2) { + double d; + int ierr = 0; + int dim = geom->gmsh_dimTags[i]; + int tag = geom->gmsh_dimTags[i + 1]; + size_t pcoord_n; + size_t coord_n; + + gmshModelGetClosestPoint(dim, tag, from, 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); + d = d3_len(d3_sub(tmp, from, coord)); + if(d < min_d) { + min_d = d; + d3_set(min, tmp); + } + gmshFree(coord); + gmshFree(pcoord); + coord = pcoord = NULL; + } + d3_set(closest, min); + *closest_distance = min_d; + +exit: + gmshFree(coord); + gmshFree(pcoord); + return res; +error: + goto exit; +} + +res_T scad_geometry_get_centerofmass (struct scad_geometry* geom, double* center) @@ -581,6 +816,44 @@ error: } res_T +scad_geometry_get_bounding_box + (struct scad_geometry* geom, + double min[3], + double max[3]) +{ + res_T res = RES_OK; + size_t n = 0; + double mi[3], ma[3]; + + if(!geom || !min || !max) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + d3_splat(mi, DBL_MAX); + d3_splat(ma, -DBL_MAX); + for(n = 0; n < geom->gmsh_dimTags_n; n += 2) { + double i[3], a[3]; + int ierr = 0; + int dim = geom->gmsh_dimTags[n]; + int tag = geom->gmsh_dimTags[n + 1]; + gmshModelOccGetBoundingBox(dim, tag, i, i+1, i+2, a, a+1, a+2, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + d3_min(mi, mi, i); + d3_max(ma, ma, a); + } + d3_set(min, mi); + d3_set(max, ma); + +exit: + return res; +error: + goto exit; +} + +res_T scad_add_rectangle (const char* name, const double xyz[3], @@ -1509,7 +1782,7 @@ scad_geometry_extrude ERR(gmsh_err_to_res_T(ierr)); get_device()->need_synchro = 1; - /* Output includes both the 3D result and its 2D constituants. + /* Output includes both the 3D result and its 2D constituents. * Keep only 3D entities. */ for(i = 0; i < tagoutn; i += 2) { int dim = tagout[i]; @@ -2514,3 +2787,159 @@ error: out = NULL; goto exit; } + +res_T +scad_geometries_set_mesh_size_modifier + (struct scad_geometry** geometries, + const size_t geometries_count, + enum scad_size_modifier_type type, + double modifier) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + int ierr, dim, some = 0; + int* tagout[4] = { NULL, NULL, NULL, NULL }; + size_t tagoutn[4] = { 0, 0, 0, 0}, i; + + if(!geometries || geometries_count == 0 || modifier <= 0) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + if(type == Scad_absolute_size) modifier = -modifier; + ERR(gather_tags_recursive(geometries, geometries_count, 0, tagout, tagoutn)); + for(dim = 0; dim < 4; dim++) { + for(i = 0; i < tagoutn[dim]; i += 2) { + int d = tagout[dim][i]; + int tag = tagout[dim][i+1]; + struct htable_size_modifiers* modifiers = dev->size_modifiers_by_dim + dim; + double* f_ptr = htable_size_modifiers_find(modifiers, &tag); + ASSERT(d == dim); + if(f_ptr && modifier == 1) { /* Size factor of 1 => no modifier */ + size_t c = htable_size_modifiers_erase(modifiers, &tag); + ASSERT(c == 1); + } else if(f_ptr) { + *f_ptr = modifier; + } else { + ERR(htable_size_modifiers_set(modifiers, &tag, &modifier)); + } + } + } + for(dim = 0; dim < 4; dim++) { + if(htable_size_modifiers_size_get(dev->size_modifiers_by_dim + dim) > 0) { + some = 1; + break; + } + } + + gmshModelMeshSetSizeCallback( (some ? size_callback : NULL), NULL, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + +exit: + for(dim = 0; dim < 4; dim++) { + MEM_RM(dev->allocator, tagout[dim]); + } + return res; +error: + goto exit; +} + +res_T +scad_geometries_set_mesh_algorithm +(struct scad_geometry** geometries, + const size_t geometries_count, + enum scad_mesh_algorithm algorithm) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + int ierr; + int* tagout[4] = { NULL, NULL, NULL, NULL }; + size_t tagoutn[4], i; + + if(!geometries || geometries_count == 0) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + ERR(gather_tags_recursive(geometries, geometries_count, 2, tagout, tagoutn)); + for(i = 0; i < tagoutn[2]; i += 2) { + int dim = tagout[2][i]; + int tag = tagout[2][i+1]; + gmshModelMeshSetAlgorithm(dim, tag, (int)algorithm, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + } + +exit: + for(i = 2; i < 4; i++) { + MEM_RM(dev->allocator, tagout[i]); + } + return res; +error: + goto exit; +} + +res_T +scad_geometries_set_periodic + (struct scad_geometry** source, + const size_t source_count, + struct scad_geometry** target, + const size_t target_count, + double affine[16]) +{ + res_T res = RES_OK; + struct scad_device* dev = get_device(); + struct mem_allocator* allocator = dev->allocator; + int ierr; + int* src_dimTagout[4] = { NULL, NULL, NULL, NULL }; + int* tgt_dimTagout[4] = { NULL, NULL, NULL, NULL }; + int* src_tags = NULL, *tgt_tags = NULL; + size_t src_dimTagoutn[4], tgt_dimTagoutn[4], src_tagsn = 0, tgt_tagsn = 0, i; + + if(!source || source_count == 0 || !target || target_count == 0 || !affine) { + res = RES_BAD_ARG; + goto error; + } + + ERR(check_device(FUNC_NAME)); + + ERR(gather_tags_recursive(source, source_count, 2, src_dimTagout, src_dimTagoutn)); + ERR(gather_tags_recursive(target, target_count, 2, tgt_dimTagout, tgt_dimTagoutn)); + ASSERT(src_dimTagoutn[2] % 2 == 0 && tgt_dimTagoutn[2] % 2 == 0); + src_tagsn = src_dimTagoutn[2] / 2; + tgt_tagsn = tgt_dimTagoutn[2] / 2; + if(src_tagsn == 0 || tgt_tagsn == 0) { + res = RES_BAD_ARG; + goto error; + } + + src_tags = MEM_ALLOC(allocator, src_tagsn); + tgt_tags = MEM_ALLOC(allocator, tgt_tagsn); + if(!src_tags || !tgt_tags) { + res = RES_MEM_ERR; + goto error; + } + for(i = 0; i < src_tagsn; i += 2) { + src_tags[i] = src_dimTagout[2][i+1]; + } + for(i = 0; i < tgt_tagsn; i += 2) { + tgt_tags[i] = tgt_dimTagout[2][i+1]; + } + gmshModelMeshSetPeriodic(2, tgt_tags, tgt_tagsn, src_tags, src_tagsn, + affine, 16, &ierr); + ERR(gmsh_err_to_res_T(ierr)); + +exit: + for(i = 2; i < 4; i++) { + MEM_RM(dev->allocator, src_dimTagout[i]); + MEM_RM(dev->allocator, tgt_dimTagout[i]); + } + MEM_RM(dev->allocator, src_tags); + MEM_RM(dev->allocator, tgt_tags); + return res; +error: + goto exit; +}