From f4dba8bd38d2cbbf4079fcaac6e605acd76b7588 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sat, 11 Jul 2026 08:12:54 +0300 Subject: [PATCH] Bonsai: pick the true sweep edge in detect_extrusion_edge (#5898) When re-deriving an IfcExtrudedAreaSolid (e.g. converting slabs to IfcArbitraryProfileDefWithVoids), detect_extrusion_edge returned the first mesh edge that leaves the profile plane by more than 1e-6. On slanted or stepped meshes a nearly in-plane boundary edge can clear that threshold first, so the sweep direction ended up lying in the profile plane and the extrusion collapsed to a zero-thickness sliver (12 of 69 slabs in the report went from ~2500 volume to ~0). Scan all candidate edges and return the one whose direction is most aligned with the profile-face normal, i.e. the genuine through-thickness sweep edge, keeping the same [in_profile_vertex, off_plane_vertex] return orientation. Benefits all auto-detect callers. Verified in headless Blender 5.1 / Bonsai 0.8.6 against the reported sample: after the fix all 69 slabs match their Brep ground truth to 5 to 6 significant figures (worst diff 0.01%), the previously degenerate 12 are restored, and the 57 already-correct slabs are volume-identical. Co-Authored-By: Claude Opus 4.8 --- .../bonsai/bim/module/geometry/helper.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/helper.py b/src/bonsai/bonsai/bim/module/geometry/helper.py index 3b14003435..ffb901cb42 100644 --- a/src/bonsai/bonsai/bim/module/geometry/helper.py +++ b/src/bonsai/bonsai/bim/module/geometry/helper.py @@ -316,10 +316,18 @@ class Helper: return {"outer_curve": outer_loop, "inner_curves": inner_loops} # An extrusion edge is an edge that shares a single vertex with a profile - # face and is not on the plane of the face. + # face and is not on the plane of the face. Multiple such edges may exist + # (e.g. a slanted/stepped mesh has boundary edges that leave the profile + # plane only marginally); the genuine sweep edge is the one whose direction + # is most aligned with the profile normal. Picking the first candidate in + # arbitrary bmesh order could select a nearly in-plane edge, yielding a + # degenerate (zero-thickness) extrusion. So we pick the best-aligned edge. def detect_extrusion_edge(self, bm: bmesh.types.BMesh, profile_face: bmesh.types.BMFace) -> Union[list[int], None]: bm.edges.ensure_lookup_table() face_verts_set = set(profile_face.verts) + normal = profile_face.normal + best_edge = None + best_alignment = 0.0 for edge in bm.edges: unshared_verts = set(edge.verts) - face_verts_set if len(unshared_verts) == 1: @@ -327,14 +335,24 @@ class Helper: if ( abs( mathutils.geometry.distance_point_to_plane( - unshared_vert.co, profile_face.verts[0].co, profile_face.normal + unshared_vert.co, profile_face.verts[0].co, normal ) ) > 1e-6 ): - if unshared_vert == edge.verts[1]: - return [edge.verts[0].index, edge.verts[1].index] - return [edge.verts[1].index, edge.verts[0].index] + direction = edge.verts[1].co - edge.verts[0].co + length = direction.length + if length < 1e-9: + continue + # 1.0 == parallel to the profile normal (a true sweep edge). + alignment = abs(direction.dot(normal)) / length + if alignment > best_alignment: + best_alignment = alignment + if unshared_vert == edge.verts[1]: + best_edge = [edge.verts[0].index, edge.verts[1].index] + else: + best_edge = [edge.verts[1].index, edge.verts[0].index] + return best_edge def create_extruded_area_solid( self, mesh: bpy.types.Mesh, extrusion_indices: list[int], profile_def: dict[str, Any]