star-cad

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

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:
MMakefile | 11++++++++---
MREADME.md | 20++++++++++++++++++++
Mconfig.mk | 2+-
Msrc/scad.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/scad_device.c | 3++-
Msrc/scad_geometry.c | 26++++++++++++++------------
Msrc/test_export.c | 17++++++++++++-----
Msrc/test_partition.c | 3+--
Asrc/test_periodic.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; +}