mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user