commit 2fe3ce0f77d47bfa879ca9e6e8414da6a0682dc2
parent b032516cf6b787787637de02016ae648f6d05c05
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date: Mon, 3 Mar 2025 10:50:21 +0100
Merge branch 'release_0.5.1'
Diffstat:
8 files changed, 685 insertions(+), 15 deletions(-)
diff --git a/Makefile b/Makefile
@@ -128,6 +128,7 @@ lint:
TEST_SRC =\
src/test_api.c\
src/test_export.c\
+ src/test_export2.c\
src/test_lifetime.c\
src/test_partition.c
TEST_OBJ = $(TEST_SRC:.c=.o)
@@ -176,6 +177,7 @@ $(TEST_OBJ): config.mk scad-local.pc
test_api \
test_export \
+test_export2 \
test_lifetime \
test_partition \
: config.mk scad-local.pc $(LIBNAME)
diff --git a/README.md b/README.md
@@ -30,6 +30,7 @@ Edit config.mk as needed, then run:
- BugFix STL output of geometries when forcing normals
- Improve C89 compliance and fix header file to be used in c++ code
- Change build mechanics from cmake to POSIX makefiles
+- Change manpages format from asciidoc to mdoc
- Upgrade dependencies
- Improve and fix some tests
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
@@ -47,7 +47,7 @@ struct scad_geometry; /* Wrapping of dimTags gmsh description */
/* Verbosity levels */
enum scad_verbosity_levels {
- scad_verbosSCAD_ity_fatal_errors = 0,
+ scad_verbosity_fatal_errors = 0,
Scad_verbosity_errors = 1,
Scad_verbosity_warnings = 2,
Scad_verbosity_direct = 3,
@@ -59,8 +59,11 @@ enum scad_verbosity_levels {
/* Mesh algorithms */
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 {
@@ -84,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 {
@@ -113,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 } \
@@ -165,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.
@@ -230,13 +247,27 @@ scad_geometry_get_mass
double* mass);
/* Get the center of mass of the various components of the geometry.
- * Note that `center' must be allocated be the caller with enough room for (at
- * least) 3 times the count of geom (scad_geometry_ge_count) doubles */
+ * Note that `center' must be allocated by the caller with enough room for (at
+ * least) 3 times the count of geom (scad_geometry_get_count) doubles */
SCAD_API res_T
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
@@ -382,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 */
@@ -397,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
@@ -533,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];
@@ -2455,6 +2728,17 @@ scad_geometry_normal
coord = pcoord = NULL;
}
if(!out) { /* Could not find a matching surface */
+ if(str_is_empty(&geom->name)) {
+ log_warning(get_device(),
+ "Could not get normal at vertex %g %g %g "
+ "as unamed geometry %p is not close enough.\n",
+ SPLIT3(p), (void*)geom);
+ } else {
+ log_warning(get_device(),
+ "Could not get normal at vertex %g %g %g "
+ "as geometry %s is not close enough.\n",
+ SPLIT3(p), str_cget(&geom->name));
+ }
res = RES_BAD_ARG;
goto error;
}
@@ -2503,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;
+}
diff --git a/src/test_export2.c b/src/test_export2.c
@@ -0,0 +1,102 @@
+/* Copyright (C) 2022-2024 |Méso|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/math.h>
+#include <rsys/dynamic_array_double.h>
+#include <rsys/mem_allocator.h>
+
+#include <stdlib.h>
+
+/*
+ * +-------------------+
+ * | |
+ * | +-------------+ |
+ * | | | |
+ * | | +-------+ | |
+ * | | | | | |
+ * | | | | | |
+ * | | | | | |
+ * | | +-------+ | |
+ * | | | |
+ * | +-------------+ |
+ * | |
+ * +-------------------+
+ */
+
+int
+main(int argc, char* argv[])
+{
+ res_T res = RES_OK;
+ double p1[3] = {0, 0, 0};
+ double p2[3] = {2, 2, 2};
+ double p3[3] = {4, 4, 4};
+ double d1[3] = {10, 10, 10};
+ double d2[3] = {6, 6, 6};
+ double d3[3] = {2, 2, 2};
+ struct darray_double array;
+ struct scad_geometry* cube1 = NULL;
+ struct scad_geometry* cube2 = NULL;
+ struct scad_geometry* cube3 = NULL;
+ struct mem_allocator allocator;
+
+ (void)argc; (void)argv;
+
+ OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator));
+ darray_double_init(&allocator, &array);
+ OK(scad_initialize(NULL, &allocator, 3));
+
+ OK(scad_add_box("cube1", p1, d1, &cube1));
+ OK(scad_add_box("cube2", p2, d2, &cube2));
+ OK(scad_add_box("cube3", p3, d3, &cube3));
+
+ OK(scad_scene_mesh());
+
+ /* Check that all three cubes can be exported whith forced normals */
+ OK(scad_stl_export(cube1, NULL, Scad_force_normals_outward, 0));
+ OK(scad_stl_export(cube2, NULL, Scad_force_normals_outward, 0));
+ OK(scad_stl_export(cube3, "bin_cube3", Scad_force_normals_outward, 1));
+
+ /* Check that 2 cubes as a single model can be exported whith forced normals */
+ OK(scad_stl_get_data(cube1, &array));
+ OK(scad_stl_get_data(cube2, &array));
+ OK(scad_stl_data_write(&array, "2cubes.stl", Scad_force_normals_outward, 0));
+
+ /* Check that with 3 cubes as a single model, the model cannot be exported
+ * whith forced normals... */
+ OK(scad_stl_get_data(cube3, &array));
+ BAD(scad_stl_data_write(&array, "3cubes.stl", Scad_force_normals_outward, 0));
+ /* ...but can be exported anyway without forcing normals... */
+ OK(scad_stl_data_write(&array, "3cubes.stl", Scad_keep_normals_unchanged, 0));
+ /* ...and can still be exported if some triangles are duplicated */
+ OK(scad_stl_get_data(cube1, &array));
+ OK(scad_stl_data_write(&array, "3cubesd.stl", Scad_keep_normals_unchanged, 0));
+
+ OK(scad_geometry_ref_put(cube1));
+ OK(scad_geometry_ref_put(cube2));
+ OK(scad_geometry_ref_put(cube3));
+ OK(scad_finalize());
+
+ darray_double_release(&array);
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+
+ return (res == RES_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
+}