From efa26ad6f49750215425d40c4fbf33c9e3ed1706 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sun, 5 Apr 2026 18:30:36 -0500 Subject: [PATCH] See #3742: Handle unilateral shared-edge detection for implicit boundaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some adjacent same-material elements don't explicitly draw their shared boundary in both SVG projection groups — one element's edge is implicit (absorbed into its parent path or omitted because the other element "owns" it). In these cases the bilateral lines_match check finds zero hits even though the elements are correctly identified as adjacent and coplanar. Add a unilateral bbox-edge fallback: after the bilateral pass finds nothing, compute the SVG bounding box of each element's parsed segments and check whether any segment from one element lies on a face of the other element's bbox with meaningful overlap. Segments that pass are on the shared interface and are removed even without a mirror segment in the opposite group. The overlap check (> TOL) prevents false positives from segments that merely touch a bbox corner without sharing real extent. Generated with the assistance of an AI coding tool. --- .../bonsai/bim/module/drawing/operator.py | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 5ecd4fd445..d383cf5b87 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -1668,7 +1668,7 @@ class CreateDrawing(bpy.types.Operator): # Quick AABB guard corners_a = [obj_a.matrix_world @ Vector(c) for c in obj_a.bound_box] corners_b = [obj_b.matrix_world @ Vector(c) for c in obj_b.bound_box] - _verbose = {"1eW6OACmXD1hWJLDeB6erW", "2x0$XNlFH6FxlvS7eYJttx"} == {guid_a, guid_b} + _verbose = {"3S7QeSPVn3EB3T9TkDUy1e", "0YyL6RtCL8axn6AG9ItFhs"} == {guid_a, guid_b} for axis in range(3): min_a = min(c[axis] for c in corners_a) max_a = max(c[axis] for c in corners_a) @@ -1789,6 +1789,39 @@ class CreateDrawing(bpy.types.Operator): pass return None + def segs_bbox(segs): + """Compute AABB (min_x, max_x, min_y, max_y) from a parsed segment list.""" + if not segs: + return None + xs = [c for _, (p0, p1) in segs for c in (p0[0], p1[0])] + ys = [c for _, (p0, p1) in segs for c in (p0[1], p1[1])] + return (min(xs), max(xs), min(ys), max(ys)) + + def seg_on_boundary_of(seg, bbox): + """True if seg is axis-aligned, lies on a bbox face, and meaningfully overlaps it. + + Used for unilateral matching: when one element's shared edge is not + explicitly drawn (it's implicit or on the interior), the other element's + segments that lie on that missing edge can still be detected and removed. + """ + (x0, y0), (x1, y1) = seg + min_x, max_x, min_y, max_y = bbox + is_v = abs(x0 - x1) < TOL + is_h = abs(y0 - y1) < TOL + if is_v: + x_mid = (x0 + x1) / 2 + if abs(x_mid - min_x) < TOL or abs(x_mid - max_x) < TOL: + y_lo, y_hi = min(y0, y1), max(y0, y1) + overlap = min(y_hi, max_y) - max(y_lo, min_y) + return overlap > TOL + if is_h: + y_mid = (y0 + y1) / 2 + if abs(y_mid - min_y) < TOL or abs(y_mid - max_y) < TOL: + x_lo, x_hi = min(x0, x1), max(x0, x1) + overlap = min(x_hi, max_x) - max(x_lo, min_x) + return overlap > TOL + return False + def lines_match(a, b): """Return True if segments a and b represent the same physical edge. @@ -1883,7 +1916,7 @@ class CreateDrawing(bpy.types.Operator): for j, (grp_j, mat_j, style_j, segs_j, guid_j) in enumerate(group_data): if j <= i: continue - _target = {"1eW6OACmXD1hWJLDeB6erW", "2x0$XNlFH6FxlvS7eYJttx"} == {guid_i, guid_j} + _target = {"0QpDRRuif0R80d4CYPEQ$L", "0YyL6RtCL8axn6AG9ItFhs"} == {guid_i, guid_j} if mat_j != mat_i: if _target: print(f"[TARGET PAIR] SKIPPED: different mat {mat_i!r} vs {mat_j!r}") @@ -1904,6 +1937,26 @@ class CreateDrawing(bpy.types.Operator): to_remove.add(id(path_i)) to_remove.add(id(path_j)) matched += 1 + # Unilateral fallback: when one element's shared edge is implicit + # (not explicitly drawn as a path segment), segments from the other + # element that lie on the boundary of that element's SVG bbox are + # still on the shared face and should be removed. + if matched == 0 and segs_i and segs_j: + bbox_i = segs_bbox(segs_i) + bbox_j = segs_bbox(segs_j) + if bbox_i and bbox_j: + for path_j, line_j in segs_j: + if seg_on_boundary_of(line_j, bbox_i): + if _target: + print(f"[TARGET PAIR] UNILATERAL j-on-i: {line_j}") + to_remove.add(id(path_j)) + matched += 1 + for path_i, line_i in segs_i: + if seg_on_boundary_of(line_i, bbox_j): + if _target: + print(f"[TARGET PAIR] UNILATERAL i-on-j: {line_i}") + to_remove.add(id(path_i)) + matched += 1 if _target: print(f"[TARGET PAIR] adj=True, matched={matched} segment pair(s)") if matched == 0: