commit 63e24b5c7dd1b4dcd181559e418c09ac6026e1a9
parent 5b83d886a283a6cd78be11cb62c03df871873870
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date: Thu, 5 Jun 2025 18:35:24 +0200
Merge branch 'release_0.5.2'
Diffstat:
9 files changed, 219 insertions(+), 28 deletions(-)
diff --git a/Makefile b/Makefile
@@ -130,7 +130,8 @@ TEST_SRC =\
src/test_export.c\
src/test_export2.c\
src/test_lifetime.c\
- src/test_partition.c
+ src/test_partition.c\
+ src/test_periodic.c
TEST_OBJ = $(TEST_SRC:.c=.o)
TEST_DEP = $(TEST_SRC:.c=.d)
@@ -151,12 +152,14 @@ test: build_tests
clean_test:
$(SHELL) make.sh clean_test $(TEST_SRC)
- rm -f bin_cube2.stl
+ rm -f bin_fused.stl
rm -f bin_cube.stl
rm -f bin_rectangle.stl
- rm -f cube2.stl
rm -f cube.stl
+ rm -f cube_quasi.stl
rm -f different_names_0.stl
+ rm -f fused.stl
+ rm -f fused_quasi.stl
rm -f not-a-volume.stl.stl
rm -f part_0.9_1o.stl
rm -f part_0.9_2o.stl
@@ -165,6 +168,7 @@ clean_test:
rm -f part_1_1.stl
rm -f part_1_2.stl
rm -f rectangle.stl
+ rm -f rectangle_quasi.stl
rm -f "sphere 1_0.stl"
rm -f "sphere 1.stl"
@@ -180,5 +184,6 @@ test_export \
test_export2 \
test_lifetime \
test_partition \
+test_periodic \
: config.mk scad-local.pc $(LIBNAME)
$(CC) $(CFLAGS_EXE) -o $@ src/$@.o $(LDFLAGS_EXE) $(SCAD_LIBS) $(RSYS_LIBS) -lm
diff --git a/README.md b/README.md
@@ -25,6 +25,26 @@ Edit config.mk as needed, then run:
## Release notes
+### Version 0.5.2
+
+- Fix a file name in debug STL output file.
+- Add debug code that output new STL files on some errors.
+- Add tests.
+
+### Version 0.5.1
+
+- Add `scad_get_bounding_box' API call.
+- Add `scad_geometries_set_periodic' API call.
+- Add `scad_geometries_set_mesh_size_modifier' API call.
+- Add `scad_geometries_set_mesh_algorithm' API call.
+- Add `scad_geometry_get_closest_point' API call.
+- Add the `quasi structured' meshing algorithm.
+- Add the `automatic' meshing algorithm.
+- Add the `initial mesh only' meshing algorithm.
+- Add a geometry visibility management feature.
+- Add `' API call.
+- Add `' API call.
+
### Version 0.5
- BugFix STL output of geometries when forcing normals
diff --git a/config.mk b/config.mk
@@ -1,4 +1,4 @@
-VERSION = 0.5.1
+VERSION = 0.5.2
PREFIX = /usr/local
LIB_TYPE = SHARED
diff --git a/src/scad.c b/src/scad.c
@@ -77,7 +77,8 @@ write_ascii_stl
d3_sub(edge1, vtx[1], vtx[0]);
d3_sub(edge2, vtx[2], vtx[0]);
d3_cross(tmp, edge1, edge2);
- if(d3_eq(tmp, zero)) continue;
+ if(d3_eq(tmp, zero))
+ continue;
d3_normalize(n, tmp);
OKP(fprintf(stl_file, " facet normal %g %g %g\n", SPLIT3(n)));
@@ -153,7 +154,8 @@ write_binary_stl
f3_sub(edge1, trg.vrtx[1], trg.vrtx[0]);
f3_sub(edge2, trg.vrtx[2], trg.vrtx[0]);
f3_cross(tmp, edge1, edge2);
- if(f3_eq(tmp, zero)) continue;
+ if(f3_eq(tmp, zero))
+ continue;
f3_normalize(trg.n, tmp);
trg.attrib = 0;
if(1 != fwrite(&trg, 50, 1, stl_file)) {
@@ -493,6 +495,9 @@ scad_stl_sort_orientation
char* tin = NULL;
char *contact_0 = NULL;
unsigned ocount = 0;
+ struct darray_double dblsided;
+ struct str dbl_name;
+ int dblsided_initialized = 0;
if(!triangles || !set_name) {
res = RES_BAD_ARG;
@@ -600,10 +605,52 @@ scad_stl_sort_orientation
enclosure = NULL;
}
if(two > 0) {
+ size_t idx;
+ res_T r;
log_error(get_device(),
- "Triangle set '%s' define an invalid closed volume "
+ "Triangle set '%s' defines an invalid closed volume "
"(%u / %u triangles with both sides in).\n",
set_name, (unsigned)two, utcount_in);
+ /* Output the two-sides-in triangles */
+ darray_double_init(allocator, &dblsided);
+ str_init(allocator, &dbl_name);
+ dblsided_initialized = 1;
+ ERR(darray_double_reserve(&dblsided, 9 * two));
+ for(e = 0; e < ecount; e++) {
+ ERR(senc3d_scene_get_enclosure(senc3d_scn, e, &enclosure));
+ ERR(senc3d_enclosure_get_header(enclosure, &header));
+ for(i = header.unique_primitives_count; i < header.primitives_count; i++) {
+ enum senc3d_side side;
+ unsigned gid, j, k;
+ unsigned trg[3];
+ double v[3];
+ ERR(senc3d_enclosure_get_triangle_id(enclosure, i, &gid, &side));
+ ERR(senc3d_enclosure_get_triangle(enclosure, gid, trg));
+ ASSERT(side == (SENC3D_FRONT | SENC3D_BACK));
+ for(j = 0; j < 3; j++) {
+ ERR(senc3d_enclosure_get_vertex(enclosure, trg[j], v));
+ for(k = 0; k < 3; k++) {
+ ERR(darray_double_push_back(&dblsided, v+k));
+ }
+ }
+ }
+ ERR(senc3d_enclosure_ref_put(enclosure));
+ enclosure = NULL;
+ }
+ idx = strlen(set_name);
+ ASSERT(idx > 3 && 0 == strcmp(set_name + idx - 4, ".stl"));
+ str_set(&dbl_name, set_name);
+ str_insert(&dbl_name, idx - 4, "_double_sided_triangles");
+ r = scad_stl_data_write(&dblsided, str_cget(&dbl_name),
+ Scad_keep_normals_unchanged, 0);
+ if(r == RES_OK) {
+ log_error(get_device(),
+ "Saved double sided triangles to file '%s'.\n",
+ str_cget(&dbl_name));
+ } else {
+ log_error(get_device(),
+ "Could not save double sided triangles to a file.\n");
+ }
res = RES_BAD_ARG;
goto error;
}
@@ -628,7 +675,8 @@ scad_stl_sort_orientation
if(tin[idx]) {
/* Allready in output */
char s = (side == SENC3D_FRONT) ? 1 : 2;;
- if(s == tin[idx]) continue; /* Same side => OK (should not happen?) */
+ if(s == tin[idx])
+ continue; /* Same side => OK (should not happen?) */
log_error(get_device(),
"Triangle set '%s' defines a topology that doesn't allow forcing normals"
" (found triangle(s) with 2 sides in).\n",
@@ -668,6 +716,10 @@ scad_stl_sort_orientation
exit:
MEM_RM(allocator, contact_0);
MEM_RM(allocator, tin);
+ if(dblsided_initialized) {
+ darray_double_release(&dblsided);
+ str_release(&dbl_name);
+ }
if(initialized) darray_double_release(&new_triangles);
if(sg3d) SG3D(device_ref_put(sg3d));
if(geom) SG3D(geometry_ref_put(geom));
diff --git a/src/scad_device.c b/src/scad_device.c
@@ -513,7 +513,8 @@ device_register_ref_to_tags
for(i = 0; i < count; i += 2) {
int d = dimTags[i];
int t = dimTags[i+1];
- if(d == dim && t == tag) continue;
+ 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;
diff --git a/src/scad_geometry.c b/src/scad_geometry.c
@@ -255,6 +255,7 @@ static res_T
store_tags
(int* dimTags,
size_t count,
+ const int down_to_dim,
struct htable_tags t[4])
{
res_T res = RES_OK;
@@ -265,6 +266,7 @@ store_tags
int dim = dimTags[i];
int tag = dimTags[i+1];
ASSERT(dim >= 0 && dim <= 3);
+ if(down_to_dim > dim) continue;
ERR(htable_tags_set(t+dim, &tag, &one));
}
@@ -304,7 +306,8 @@ gather_tags_recursive
res = RES_BAD_ARG;
goto error;
}
- ERR(store_tags(geometries[i]->gmsh_dimTags, geometries[i]->gmsh_dimTags_n, t));
+ ERR(store_tags(geometries[i]->gmsh_dimTags, geometries[i]->gmsh_dimTags_n,
+ down_to_dim, t));
}
/* Recursively build result by dimension and list constituents,
@@ -312,7 +315,8 @@ gather_tags_recursive
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;
+ if(sz[dim] == 0)
+ continue;
dimTags[dim] = MEM_ALLOC(allocator, sz[dim] * sizeof(*dimTags));
if(!dimTags[dim]) {
res = RES_MEM_ERR;
@@ -330,7 +334,7 @@ gather_tags_recursive
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));
+ ERR(store_tags(sub, subn, down_to_dim, t));
gmshFree(sub); sub = NULL;
}
ASSERT(sz[dim] == c);
@@ -1695,10 +1699,6 @@ error:
int dim = INT_MAX;
if(!mixed_dim_err_msg(name, "common boundary", geometries, geometries_count, &dim))
mixed_dim_err_msg(name, "common boundary", tools, tools_count, &dim);
- if(geom) {
- SCAD(geometry_ref_put(geom));
- geom = NULL;
- }
}
if(geom) {
SCAD(geometry_ref_put(geom));
@@ -2177,11 +2177,12 @@ scad_geometries_partition
}
for(k = 0; k < geometries_count; k++) {
struct scad_geometry* g = geometries[k];
- if(!overlap[k]) continue;
+ if(!overlap[k])
+ continue;
tmp_err = RES_BAD_OP;
if(dump_overlapping_err) {
if(str_is_empty(&g->name)) {
- str_printf(&tmp, "unamed_partition_error_%lu_%lu.stl",
+ str_printf(&tmp, "unamed_partition_error_%lu_%lu",
err_cpt, (long unsigned)item_cpt++);
tmp_err =
scad_stl_export(g, str_cget(&tmp), Scad_keep_normals_unchanged, 0);
@@ -2193,7 +2194,7 @@ scad_geometries_partition
log_error(get_device(), "Could not dump geoemtry.\n");
}
} else {
- str_printf(&tmp, "%s_partition_error_%lu_%lu.stl",
+ str_printf(&tmp, "%s_partition_error_%lu_%lu",
str_cget(&g->name), err_cpt, (long unsigned)item_cpt++);
tmp_err =
scad_stl_export(g, str_cget(&tmp), Scad_keep_normals_unchanged, 0);
@@ -2354,7 +2355,8 @@ scad_geometries_swap
struct scad_geometry *g1 = pool1[i], *g2 = pool2[i];
size_t c1 = g1->gmsh_dimTags_n, c2 = g2->gmsh_dimTags_n;
int *dt1 = g1->gmsh_dimTags, *dt2 = g2->gmsh_dimTags;
- if(pool1[i] == pool2[i]) continue;
+ if(pool1[i] == pool2[i])
+ continue;
/* Swap content according to flags. Don't swap refcount! */
if(flags & Scad_swap_name) {
if(dev->log) {
@@ -2848,7 +2850,7 @@ error:
res_T
scad_geometries_set_mesh_algorithm
-(struct scad_geometry** geometries,
+ (struct scad_geometry** geometries,
const size_t geometries_count,
enum scad_mesh_algorithm algorithm)
{
diff --git a/src/test_export.c b/src/test_export.c
@@ -32,7 +32,7 @@ main(int argc, char* argv[])
double d1[3] = {1, 1, 1};
struct scad_geometry* rectangle = NULL;
struct scad_geometry* cube = NULL;
- struct scad_geometry* cube2 = NULL;
+ struct scad_geometry* fused = NULL;
struct scad_geometry* geoms[2];
struct mem_allocator allocator;
@@ -43,10 +43,10 @@ main(int argc, char* argv[])
OK(scad_add_rectangle("rectangle", p1, d1, &rectangle));
OK(scad_add_box("cube", p1, d1, &cube));
- OK(scad_add_box(NULL, p2, d1, geoms+1));
+ OK(scad_add_cylinder(NULL, p2, d1, 0.5, 2*PI, geoms+1));
geoms[0] = cube;
- OK(scad_fuse_geometries("cube2", geoms, 1, geoms+1, 1, &cube2));
+ OK(scad_fuse_geometries("fused", geoms, 1, geoms+1, 1, &fused));
OK(scad_scene_mesh());
@@ -59,8 +59,15 @@ main(int argc, char* argv[])
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, Scad_force_normals_outward, 0));
- OK(scad_stl_export(cube2, "bin_cube2", Scad_force_normals_outward, 1));
+ OK(scad_stl_export(fused, NULL, Scad_force_normals_outward, 0));
+ OK(scad_stl_export(fused, "bin_fused", Scad_force_normals_outward, 1));
+
+ /* New meshing algorithm */
+ OK(scad_geometries_set_mesh_algorithm(geoms, 2, Scad_Quasi_Structured));
+ OK(scad_scene_mesh());
+ OK(scad_stl_export(rectangle, "rectangle_quasi", Scad_keep_normals_unchanged, 0));
+ OK(scad_stl_export(cube, "cube_quasi", Scad_force_normals_outward, 0));
+ OK(scad_stl_export(fused, "fused_quasi", Scad_force_normals_outward, 0));
OK(scad_finalize());
diff --git a/src/test_partition.c b/src/test_partition.c
@@ -103,7 +103,6 @@ int
main(int argc, char* argv[])
{
struct mem_allocator allocator;
- res_T res = RES_OK;
(void)argc; (void)argv;
@@ -125,5 +124,5 @@ main(int argc, char* argv[])
mem_shutdown_proxy_allocator(&allocator);
CHK(mem_allocated_size() == 0);
- return (res == RES_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
+ return EXIT_SUCCESS;
}
diff --git a/src/test_periodic.c b/src/test_periodic.c
@@ -0,0 +1,105 @@
+/* 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 "test_common.h"
+
+#include <rsys/rsys.h>
+#include <rsys/math.h>
+#include <rsys/double3.h>
+#include <rsys/mem_allocator.h>
+
+#define XR 1.5
+#define L 1
+
+int
+main(int argc, char* argv[])
+{
+ struct mem_allocator allocator;
+ struct scad_geometry *cyl1 = NULL, *cyl2 = NULL, *cyl = NULL;
+ struct scad_geometry *bound = NULL, **faces = NULL;
+ struct scad_geometry * internal = NULL, *external = NULL, *lat[2] = { NULL, NULL};
+ double p1[3] = { 0,0,0 }, p2[3], d2[3] = { L,0,0};
+ double r1 = 1, r2 = r1 * XR, len;
+ double cyl_affine[16] = { 1, 0, 0, 0, 0, XR, 0, 0, 0, 0, XR, 0, 0, 0, 0, 1 };
+ size_t i, facesn;
+
+ (void)argc; (void)argv;
+
+ OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator));
+
+ scad_initialize(NULL, &allocator, 2);
+ r2 = r1 * XR;
+ scad_add_cylinder(NULL, p1, d2, r1, 2*PI, &cyl1);
+ scad_add_cylinder(NULL, p1, d2, r2, 2*PI, &cyl2);
+ scad_cut_geometries("cylinder", &cyl2, 1, &cyl1, 1, &cyl);
+ scad_geometry_ref_put(cyl1);
+ scad_geometry_ref_put(cyl2);
+ scad_geometry_boundary(NULL, &cyl, 1, &bound);
+ scad_geometry_explode(bound, NULL, &faces, &facesn);
+ scad_geometry_ref_put(bound);
+ ASSERT(facesn == 4);
+ d3_add(p2, p1, d2);
+ len = d3_len(d2);
+ for(i = 0; i < facesn; i++) {
+ struct scad_geometry* f = faces[i];
+ double center[3], m;
+ scad_geometry_get_centerofmass(f, center);
+ if(fabs(center[0] - p1[0]) < FLT_EPSILON) {
+ ASSERT(lat[0] == NULL);
+ lat[0] = f;
+ scad_geometry_rename(f, "left_side");
+ continue;
+ }
+ if(fabs(center[0] - p2[0]) < FLT_EPSILON) {
+ ASSERT(lat[1] == NULL);
+ lat[1] = f;
+ scad_geometry_rename(f, "right_side");
+ continue;
+ }
+ scad_geometry_get_mass(f, &m);
+ if(fabs(m - len*2*PI*r1) < FLT_EPSILON) {
+ ASSERT(internal == NULL);
+ internal = f;
+ scad_geometry_rename(f, "internal");
+ continue;
+ }
+ if(fabs(m - len*2*PI*r2) < FLT_EPSILON) {
+ ASSERT(external == NULL);
+ external = f;
+ scad_geometry_rename(f, "external");
+ continue;
+ }
+ }
+ ASSERT(lat[0] && lat[1] && internal && external);
+ scad_geometries_set_periodic(&internal, 1, &external, 1, cyl_affine);
+ scad_geometries_set_mesh_algorithm(lat, 1, Scad_Initial_Mesh_Only);
+ for(i = 0; i < facesn; i++) {
+ scad_geometry_ref_put(faces[i]);
+ }
+ MEM_RM(&allocator, faces);
+ scad_scene_mesh();
+ scad_stl_export(cyl, "periodic", Scad_force_normals_outward, 1);
+ scad_geometry_ref_put(cyl);
+ scad_finalize();
+#undef XR
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+
+ return EXIT_SUCCESS;
+}