star-cad

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

commit e1292939bedc459bf547d8a196287b5aee11b68c
parent a9e06df040df92a36bcbf1a2063681822ea14b1f
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Thu, 11 Jul 2024 12:01:02 +0200

BugFix of normal orientation in STL output

Complete rewrite of the algorithm that selects triangles to output when
forcing normals' orientation. The new algorithm output triangles from
any enclosure in contact with enclosure #0 (the external limit of the
model). Invalid input detection is also rewritten : no triangle with
both sides, no triangle not member of output.

Diffstat:
Msrc/scad.c | 122++++++++++++++++++++++++++++++++++++++++----------------------------------------
1 file changed, 61 insertions(+), 61 deletions(-)

diff --git a/src/scad.c b/src/scad.c @@ -480,7 +480,7 @@ scad_stl_sort_orientation struct logger* logger = dev->logger; double* coord; size_t coord_n; - unsigned i, ecount, tcount_in, vcount_in, tcount, vcount; + unsigned i, ecount, tcount_in, tcount_out, vcount_in, utcount_in, vcount; struct sg3d_device* sg3d = NULL; struct sg3d_geometry* geom = NULL; struct senc3d_device* senc3d = NULL; @@ -491,8 +491,8 @@ scad_stl_sort_orientation struct sg3d_geometry_add_callbacks callbacks = SG3D_ADD_CALLBACKS_NULL__; struct darray_double new_triangles; char* tin = NULL; - char *process = NULL, *contact_0 = NULL; - unsigned ocount = 0, process_count = 0; + char *contact_0 = NULL; + unsigned ocount = 0; if(!triangles || !set_name) { res = RES_BAD_ARG; @@ -522,12 +522,12 @@ scad_stl_sort_orientation ERR(sg3d_geometry_add(geom, vcount_in, tcount_in, &callbacks, coord)); ERR(sg3d_geometry_get_unique_vertices_count(geom, &vcount)); - ERR(sg3d_geometry_get_unique_triangles_count(geom, &tcount)); - if(tcount != tcount_in) { - ASSERT(tcount_in > tcount); + ERR(sg3d_geometry_get_unique_triangles_count(geom, &utcount_in)); + if(utcount_in != tcount_in) { + ASSERT(tcount_in > utcount_in); log_warning(get_device(), "Triangles duplicates found when sorting out normals (%u / %u) in set '%s'.\n", - tcount_in - tcount, tcount_in, set_name); + tcount_in - utcount_in, tcount_in, set_name); } if(orientation == Scad_force_normals_outward) convention = SENC3D_CONVENTION_NORMAL_BACK | SENC3D_CONVENTION_NORMAL_OUTSIDE; @@ -538,7 +538,7 @@ scad_stl_sort_orientation dev->options.Geometry.OCCParallel ? SENC3D_NTHREADS_DEFAULT : 1, dev->verbose, &senc3d)); ERR(senc3d_scene_create(senc3d, convention, - tcount, sg3d_sencXd_geometry_get_indices, NULL, + utcount_in, sg3d_sencXd_geometry_get_indices, NULL, vcount, sg3d_sencXd_geometry_get_position, geom, &senc3d_scn)); ERR(senc3d_scene_get_overlapping_triangles_count(senc3d_scn, &ocount)); @@ -546,12 +546,16 @@ scad_stl_sort_orientation logger_print(logger, LOG_ERROR, "Overlapping triangles found when sorting out normals (%u / %u) " "in set '%s'.\n", - tcount_in - ocount, tcount_in, set_name); + utcount_in - ocount, utcount_in, set_name); res = RES_BAD_ARG; goto error; } ERR(senc3d_scene_get_enclosure_count(senc3d_scn, &ecount)); + /* If there is at least 1 triangle, there is at least enclosure #0 that + * represents the outside of the whole scene. + * For the model to allow forcing normals, we need a model defining an + * inside/outside frontier. These models have at least 2 enclosures. */ if(ecount < 2) { /* Must define a closed volume */ log_error(get_device(), @@ -559,37 +563,30 @@ scad_stl_sort_orientation res = RES_BAD_ARG; goto error; } else { - /* Enclosure 0 is allways present and represents the outside of the whole - * scene. Enclosures with rank 1+ are closed enclosures: normal orientation - * makes sense there. - * We will only process triangles that limit the enclosures that have - * enclosure 0 on the other side. If we processed any enclosure, possible - * internal enclosures would trigger a double processing of some triangles. - * Finally, no enclosure is allowed other than 1) enclosures in contact with - * enclosure 0, or 2) enclosures in contact with enclosures in 1). */ + /* For the model to allow forcing normals, we reject models defining + * enclosures included inside other enclosures and models with triangles + * that have their 2 sides in the output, as their normal's orientation + * could not satisfy the requirement of the 2 sides. + * To properly detect enclosures inside enclosures, we rely on the number of + * output triangles, that must be identical to the number of input + * triangles. */ unsigned e, enclosures[2]; + size_t two = 0; darray_double_init(allocator, &new_triangles); initialized = 1; - process = MEM_CALLOC(allocator, ecount, sizeof(*process)); contact_0 = MEM_CALLOC(allocator, ecount, sizeof(*contact_0)); - if(!process || !contact_0) { + if(!contact_0) { res = RES_MEM_ERR; goto error; } for(e = 0; e < ecount; e++) { ERR(senc3d_scene_get_enclosure(senc3d_scn, e, &enclosure)); ERR(senc3d_enclosure_get_header(enclosure, &header)); - if(header.primitives_count != header.unique_primitives_count) { - log_error(get_device(), - "Triangle set '%s' define an invalid closed volume " - "(both sides of %u / %u triangles are in).\n", - set_name, - header.primitives_count - header.unique_primitives_count, - header.unique_primitives_count); - res = RES_BAD_ARG; - goto error; - } + two += (header.primitives_count - header.unique_primitives_count); if(e == 0) { + /* Triangles of enclosure #0 are part of other enclosures. To have them + * with the expected normal orientation we have to get them from these + * other enclosures. */ ERR(senc3d_enclosure_ref_put(enclosure)); enclosure = NULL; continue; @@ -600,48 +597,32 @@ scad_stl_sort_orientation ERR(senc3d_enclosure_get_triangle_id(enclosure, i, &gid, &side)); ERR(senc3d_scene_get_triangle_enclosures(senc3d_scn, gid, enclosures)); if(enclosures[0] == 0 || enclosures[1] == 0) { - process_count++; - process[e] = 1; contact_0[e] = 1; break; } } ERR(senc3d_enclosure_ref_put(enclosure)); enclosure = NULL; - } - for(e = 1; e < ecount; e++) { - if(process[e]) continue; - ERR(senc3d_scene_get_enclosure(senc3d_scn, e, &enclosure)); - ERR(senc3d_enclosure_get_header(enclosure, &header)); - for(i = 0; i < header.unique_primitives_count; i++) { - enum senc3d_side side; - unsigned gid; - ERR(senc3d_enclosure_get_triangle_id(enclosure, i, &gid, &side)); - ERR(senc3d_scene_get_triangle_enclosures(senc3d_scn, gid, enclosures)); - if(contact_0[enclosures[0]] || contact_0[enclosures[1]]) { - process_count++; - process[e] = 1; - break; - } + if(two > 0) { + log_error(get_device(), + "Triangle set '%s' define an invalid closed volume " + "(%u / %u triangles with both sides in).\n", + set_name, (unsigned)two, utcount_in); + res = RES_BAD_ARG; + goto error; } - ERR(senc3d_enclosure_ref_put(enclosure)); - enclosure = NULL; - } - if(process_count != ecount -1) { - log_error(get_device(), - "Triangle set '%s' defines a topology that doesn't allow forcing normals.\n", - set_name); - res = RES_BAD_ARG; - goto error; } - /* Just a guess */ - ERR(darray_double_reserve(&new_triangles, 9 * tcount_in)); - tin = MEM_CALLOC(allocator, 1, 9 * tcount_in); + ERR(darray_double_reserve(&new_triangles, 9 * utcount_in)); + tin = MEM_CALLOC(allocator, 1, 9 * utcount_in); if(!tin) { res= RES_MEM_ERR; goto error; } for(e = 1; e < ecount; e++) { + if(!contact_0[e]) { + /* Output only enclosures in contact with enclosure #0 */ + continue; + } ERR(senc3d_scene_get_enclosure(senc3d_scn, e, &enclosure)); ERR(senc3d_enclosure_get_header(enclosure, &header)); for(i = 0; i < header.unique_primitives_count; i++) { @@ -649,8 +630,18 @@ scad_stl_sort_orientation enum senc3d_side side; /* Ensure that input triangles are included only once */ ERR(senc3d_enclosure_get_triangle_id(enclosure, i, &idx, &side)); - if(tin[idx]) continue; /* Allready in output */ - tin[idx] = 1; + if(tin[idx]) { + /* Allready in output */ + char s = (side == SENC3D_FRONT) ? 1 : 2;; + 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", + set_name); + res = RES_BAD_ARG; + goto error; + } + tin[idx] = (side == SENC3D_FRONT) ? 1 : 2;; /* Get the vertices as they are ordered in the enclosure's mesh */ ERR(senc3d_enclosure_get_triangle(enclosure, i, vrt)); /* Rewrite vertices according to enclosure's mesh */ @@ -665,12 +656,21 @@ scad_stl_sort_orientation ERR(senc3d_enclosure_ref_put(enclosure)); enclosure = NULL; } + tcount_out = (unsigned)(darray_double_size_get(&new_triangles)/9); + ASSERT(utcount_in >= tcount_out); + if(utcount_in != tcount_out) { + log_error(get_device(), + "Triangle set '%s' defines a topology that doesn't allow forcing normals" + " (%u /%u triangles not part of the output).\n", + set_name, utcount_in - tcount_out, utcount_in); + res = RES_BAD_ARG; + goto error; + } ERR(darray_double_copy_and_release(triangles, &new_triangles)); initialized = 0; } exit: - MEM_RM(allocator, process); MEM_RM(allocator, contact_0); MEM_RM(allocator, tin); if(initialized) darray_double_release(&new_triangles);