| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…erial elements in Bonsai SVG drawings Adds `remove_coplanar_boundary_lines()` to operator.py (Bonsai uses this path, not draw.py's main()). After `merge_linework_and_add_metadata()` assigns material CSS classes, this post-processes the SVG to delete projection line segments that appear in two or more adjacent, coplanar elements with the same material and presentation style. Key design decisions: - Material identity: compared via sorted IFC material ID tuples from `get_materials()`, not CSS class names — avoids false matches between unrelated `material-null` elements. - Presentation style identity: compared via IFC IfcPresentationStyle IDs from `StyledByItem` on geometry representation items — handles elements with no material but distinct visual styles. - Physical adjacency: confirmed by a 3D shared-vertex test (tol=0.01 m) after a quick AABB guard, rejecting elements whose 2D projections overlap but sit at different depths. - Coplanarity: determined by the dominant (largest-area) face normal of each Blender mesh object — area-weighted averages are unreliable for slabs whose equal top/bottom faces cancel out. Folded walls sharing an edge but meeting at an angle are correctly rejected (normal dot ≪ 1.0). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SummaryFixes #3742. When a plan or RCP drawing is generated, adjacent elements that are coplanar and share the same material produce a visible line between them in the SVG output. This line exists because each IFC element is projected independently into its own <g class="projection"> group — the shared edge appears in both groups and is rendered twice. This PR removes those duplicate segments after linework generation so the surfaces read as a single continuous plane. BackgroundBonsai's drawing pipeline does not use draw.py's main() function — it has its own equivalent path in CreateDrawing.execute() inside operator.py. The draw.py implementation in the referenced PR (#7894) therefore has no effect on Bonsai drawings. This fix is applied at the correct location in operator.py. What was addedA new method remove_coplanar_boundary_lines(root) is called on the SVG root immediately after merge_linework_and_add_metadata() (which is where IFC GUIDs and material CSS classes are stamped onto the <g> elements). It runs for both HALFEDGE and OPENCASCADE cut modes. The method works in three stages: 1. Group collection 2. Visual identity check (material + style)
3. Coplanarity and adjacency check (are_coplanar_and_adjacent)
Results are cached per GUID pair to avoid redundant 3D queries. 4. Segment matching Cases correctly handled
|
Sorry, something went wrong.
…rations Extends the SVG projection line deduplication introduced earlier with several new cases: **Containment adjacency (inner element inside outer)** - The original shared-vertex check failed when an inner element's corners lie on edges of the outer element rather than at corner vertices. - Added vertex-to-edge proximity check using point-to-segment distance. - When one AABB is fully contained within another, skip the dominant-face normal check: a short/flat inner element's largest polygon is its top/bottom face, giving a spurious perpendicular normal. **Anti-parallel normals (mirrored wall faces)** - Two adjacent wall sections can have opposite face normals (+Y vs -Y) while still being on the same plane. The original `abs(dot)` check correctly identifies them as parallel, but projecting onto each object's own normal produced mirrored plane-position values that never matched. - Fixed by projecting both objects' vertices onto the same reference normal (n_a) when computing face-plane positions. **Parallel offset walls (false positive rejection)** - Two walls with identical orientations but on different parallel planes (e.g., offset walls meeting at a corner) were incorrectly accepted by the normal-direction check alone. - Added face-plane position check: after confirming normals are parallel, verify at least one face-plane position of A (projected onto the shared normal) matches one of B within tolerance. **L-shaped wall split segments (collinear overlap)** - An L-shaped wall's boundary at a shared interface can be split into multiple sub-segments (one per notch side), while the adjacent simple wall has a single full-length segment covering the same edge. - Exact endpoint matching always failed in this case. - Extended `lines_match` with a collinear-overlap check: axis-aligned segments on the same line with overlapping extents (beyond a trivial point touch) are now treated as the same physical edge. Fix #3742: Improve coplanar boundary removal for complex wall configurations Extends the SVG projection line deduplication introduced earlier with several new cases: **Containment adjacency (inner element inside outer)** - The original shared-vertex check failed when an inner element's corners lie on edges of the outer element rather than at corner vertices. - Added vertex-to-edge proximity check using point-to-segment distance. - When one AABB is fully contained within another, skip the dominant-face normal check: a short/flat inner element's largest polygon is its top/bottom face, giving a spurious perpendicular normal. **Anti-parallel normals (mirrored wall faces)** - Two adjacent wall sections can have opposite face normals (+Y vs -Y) while still being on the same plane. The original `abs(dot)` check correctly identifies them as parallel, but projecting onto each object's own normal produced mirrored plane-position values that never matched. - Fixed by projecting both objects' vertices onto the same reference normal (n_a) when computing face-plane positions. **Parallel offset walls (false positive rejection)** - Two walls with identical orientations but on different parallel planes (e.g., offset walls meeting at a corner) were incorrectly accepted by the normal-direction check alone. - Added face-plane position check: after confirming normals are parallel, verify at least one face-plane position of A (projected onto the shared normal) matches one of B within tolerance. **L-shaped wall split segments (collinear overlap)** - An L-shaped wall's boundary at a shared interface can be split into multiple sub-segments (one per notch side), while the adjacent simple wall has a single full-length segment covering the same edge. - Exact endpoint matching always failed in this case. - Extended `lines_match` with a collinear-overlap check: axis-aligned segments on the same line with overlapping extents (beyond a trivial point touch) are now treated as the same physical edge. Generated with the assistance of an AI coding tool.
BackgroundBonsai generates SVG drawings by projecting 3D IFC elements into 2D. When two The core function is remove_coplanar_boundary_lines inside the Changes Made This Session1. Vertex-to-Edge Proximity Check (Containment Case)Problem: The original adjacency check used a vertex-to-vertex shared-point Fix: After the vertex-to-vertex check fails, fall back to a point_to_seg_dist_sq(p, a, b):
projects p onto segment a–b, clamps to [0,1], returns squared distance
This correctly handles T-junctions, embedded elements, and other cases where 2. AABB Containment Bypass for the Normal CheckProblem: After proximity passes, the code checks coplanarity using the Fix: Before the normal check, detect whether one element's AABB is fully def aabb_contains(outer, inner):
for axis in range(3):
if min(inner[axis]) < min(outer[axis]) - tol: return False
if max(inner[axis]) > max(outer[axis]) + tol: return False
return True
if aabb_contains(corners_a, corners_b) or aabb_contains(corners_b, corners_a):
return True # skip normal checkThis is safe because two elements at different depth layers (which the normal 3. Face-Plane Position Check (Parallel Offset Walls)Problem: Two walls with identical orientation (same face normal direction) Fix: After confirming the normals are parallel (dot > 0.999), project all plane_pos_a = {round(v.dot(n_a), 5) for v in verts_a}
plane_pos_b = {round(v.dot(n_a), 5) for v in verts_b} # both use n_a
same_plane = any(abs(pa - pb) < tol for pa in plane_pos_a for pb in plane_pos_b)Note that both sets are projected onto n_a (not each onto its own Example rejected: Wall A at Y ∈ [−0.25, +0.05], Wall B at Y ∈ [−0.41, Example accepted: Wall A and Wall B both at Y ∈ [−0.41, −0.10], normals 4. Collinear-Overlap Segment Matching (L-Shaped Walls)Problem: The segment matching function lines_match required exact Example:
No exact match is possible, but all three segments represent the same physical Fix: Added a collinear-overlap check as a second case in lines_match.
# Vertical case
if abs((x0a + x1a)/2 - (x0b + x1b)/2) < TOL:
return max(min_ya, min_yb) < min(max_ya, max_yb) - TOLThe - TOL threshold prevents false matches between segments that merely share Summary of Guards in are_coplanar_and_adjacentThe full decision pipeline is now: 1. Look up Blender objects by GUID (cache miss → query IFC + tool.Ifc)
2. If either object is missing or non-mesh → assume adjacent (True)
3. AABB guard: if bounding boxes are separated on any world axis → False
4. Proximity: vertex-to-vertex within tol?
No → vertex-to-edge within tol?
No → False
5. AABB containment: if one bbox fully contains the other → True (skip 6–7)
6. Dominant-normal check: largest-polygon normal for each object
dot product < 0.999 → False (elements meet at an angle)
7. Face-plane check: project all vertices onto n_a
no position from A within tol of any position from B → False
8. → True (adjacent and coplanar)
And lines_match now handles both:
Known Remaining Debug OutputThe code still contains [TARGET PAIR], [MERGED], and [COPLANAR DEBUG] |
Sorry, something went wrong.
|
updated test files: https://hub.openingdesign.com/OSArch/Community_Troubleshooting/commit/30ced2a2bcb6b0dc104b162d7519385c7ff10406
|
Sorry, something went wrong.
…aries 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.
What the problem wasWhen two adjacent walls/slabs share a boundary, Bonsai generates an SVG where both elements have a <path> drawn along that shared edge — one from each element. The algorithm removes these duplicate lines by finding segments that appear in both elements' path lists and deleting them. The problem: sometimes only one element actually draws the shared boundary as an explicit path segment. The other element's path simply doesn't include it — the shared edge is implicit in the polygon closure or owned entirely by the neighboring element. So the search for a matching pair comes up empty, and neither segment gets removed. In the failing case (0QpDRRuif0R80d4CYPEQ$L vs 0YyL6RtCL8axn6AG9ItFhs):
What the fix doesAfter the bilateral search finds nothing, a fallback check kicks in. It computes a simple bounding box from each element's drawn segments, then asks:
Element j's two vertical segments at x≈89.73 land exactly on element i's right bbox edge, and their y-ranges sit well within element i's y-extent — so they pass the test and get removed, even though element i had no matching segment to pair them against. The overlap > TOL guard prevents false positives from segments that merely graze a bbox corner without sharing real length. test file:
|
Sorry, something went wrong.
Handle additional adjacency cases in the plan/section SVG projection line deduplication introduced for #3742: - Use IfcMaterialLayerSetUsage layer sequence instead of a flat material set for material comparison. Prefix/suffix matching allows elements whose assemblies share the same layers at the interface (one having extra finish layers) to be correctly merged. - Add a unilateral bbox-edge fallback for pairs where one element's shared boundary is implicit (not drawn as an explicit SVG path segment). Segments from the other element that lie on the boundary of the first element's SVG bounding box are removed even without a bilateral match. Generated with the assistance of an AI coding tool.
When an L-shaped element wraps around a smaller coplanar element, the shared face is interior to the larger element's bounding box. The previous seg_on_boundary_of check required the shared face to lie on the larger element's own bbox edge (outer abutment only), so it missed these cases entirely. The new logic checks whether the segment lies on the *smaller* element's own bbox edge and whether the other element extends past that edge on the adjacent side — correctly handling both simple side-by-side abutment and notch/wrap configurations. Generated with the assistance of an AI coding tool.
When two coplanar same-material elements share only part of an edge (e.g. an L-shaped element abutting a rectangle along a notch), the previous bilateral match removed whole segments and left gaps or over-removed lines. The new approach groups all segments per element by their axis-aligned line, merges them into interval unions, computes the shared intersection, removes all original segments on that line, and re-adds the non-shared remainders as new path elements. This correctly handles any number of sub-segments on the same line. Generated with the assistance of an AI coding tool.
Sorry, something went wrong.
Two changes to mat_keys_match: 1. Boundary-ends fallback: when exact/reversed/prefix/suffix checks all fail, allow a join if any end layer (first or last) of each assembly shares a material ID. This handles adjacent elements with different inner layers but the same face material at the shared interface (e.g. two-layer roofs that share only their visible surface material). 2. Camera-directional face check: for IfcMaterialLayerSetUsage elements with AXIS3 (vertical) layer direction (slabs, roofs), compute the actual camera-facing layer — top layer for plan views (camera looks down), bottom layer for RCPs (camera looks up) — using DirectionSense to resolve which end is up. When both elements provide a face layer ID, only those are compared, preventing incorrect joins in RCPs where elements expose different bottom materials even though they share a top material. Walls (AXIS1/AXIS2) are unaffected and continue to use the generic ends-intersection fallback. Generated with the assistance of an AI coding tool.
Sorry, something went wrong.
The same-plane check in are_coplanar_and_adjacent tested whether any vertex of element A shared a depth value (projected onto n_a) with any vertex of element B. This falsely passed for elements stacked end-to-end along the normal axis, because they touch at a single interface point that appears in both vertex sets. Replace the point-match with a range-overlap test: project each element's full vertex set onto n_a to get a 1-D depth interval, then require the two intervals to overlap by more than tol. Side- by-side coplanar elements overlap across their full shared face thickness; end-to-end stacked elements only touch at a zero-width boundary, so their overlap is 0 and the join is correctly rejected. Generated with the assistance of an AI coding tool.
Sorry, something went wrong.
The generic boundary-ends check (any end layer of a matches any end layer of b) was too permissive for AXIS2 walls in plan view. Because the full cross-section is visible in section, two walls with different assemblies that merely share an interior or exterior layer were incorrectly joined. For AXIS3 slabs/roofs the camera-face layer check already handles cases where only the visible face layer matters. The boundary-ends fallback is no longer needed and has been removed. AXIS1/AXIS2 walls now only join via exact, reversed, prefix, or suffix layer sequence matches. Generated with the assistance of an AI coding tool.
Replace the camera_looks_up flag (plan vs RCP) and dominant-face- normal heuristic with a unified local-axis projection for all LayerSetDirections: AXIS3 (slabs/roofs) → object local Z (col[2]) AXIS2 (walls) → object local Y (col[1]) AXIS1 (rare walls) → object local X (col[0]) The camera look vector is projected onto the object's world-space stacking axis; a negative dot product means the camera is on the positive (exterior) side of the layer sequence, a positive dot means it is on the negative (interior) side. This correctly handles sloped AXIS3 slabs viewed by two plan-type cameras from opposite sides — the tilted local Z produces different dot products for each camera direction, where the old camera_looks_up flag returned the same result for both. Generated with the assistance of an AI coding tool.
The same-plane check projected vertices onto the dominant face normal (n_a) to determine if two elements span the same depth range. This correctly rejected slabs stacked along their normal axis but broke for elements whose largest face is perpendicular to the camera (e.g. X-normal boxes in plan view) — projection onto X produces zero overlap for legitimate side-by-side pairs. Replace n_a projection with projection onto _cam_look. Elements at the same camera depth produce overlapping ranges and are joined; elements separated in camera depth produce disjoint ranges and are correctly rejected regardless of the orientation of their faces. Generated with the assistance of an AI coding tool.
Introduces a "Join Coplanar Surfaces" boolean property (default off) nested under the "Generate Material Layers" option in BIM_PT_camera. When enabled, remove_coplanar_boundary_lines runs after linework generation to suppress shared boundary lines between adjacent coplanar elements of the same material. Generated with the assistance of an AI coding tool.
Adds a "Join Coplanar Surfaces" bool property (default off) nested under "Generate Material Layers" in BIM_PT_camera. When enabled, remove_coplanar_boundary_lines runs after linework generation to suppress shared boundary lines between adjacent coplanar elements of the same material. Fixes a false-positive where back-to-back walls at adjacent face planes (e.g. two thin walls touching at Y=1.185) were incorrectly joined. The bilateral segment matching now only removes a shared line segment when it covers the full running-direction extent of at least one element's union — offset partial overlaps are skipped. A companion flag suppresses the unilateral bbox fallback when bilateral already found explicit segments on that line, preventing the fallback from removing the same boundary the bilateral pass correctly rejected. Generated with the assistance of an AI coding tool.
…airs Extends the "Join Coplanar Surfaces" drawing feature with three fixes: - Offset walls on adjacent parallel planes (e.g. back-to-back walls at a corner) were incorrectly joining. The ivs_equal guard now correctly blocks removal when the shared SVG segment covers neither element's full running-direction extent. - The unilateral bbox fallback was firing after the bilateral pass already rejected an offset-wall pair (matched=0 but keys were found), removing the boundary line the bilateral correctly skipped. A new _bilateral_had_explicit_keys flag suppresses the fallback in this case. - Wall panels on the exact same surface (identical material-layer plane positions) were incorrectly blocked by the ivs_equal guard when their SVG segments were offset due to panel arrangement. are_coplanar_and_adjacent now returns "same_surface" vs True to distinguish same-surface pairs (ivs_equal bypassed) from adjacent-surface pairs (ivs_equal enforced). Also handles AABB-contained element pairs (inner element physically inside outer in 3D) by returning a "contained_a"/"contained_b" sentinel so the bilateral loop can skip the ivs_equal full-extent requirement for the partial overlap at the shared face. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I also created an architectural document for a dense knowledge-transfer reference for AI agents or developers who need to continue work on the "Join Coplanar Surfaces" feature for Bonsai's SVG drawing generation. The following highlighted tests still fail, and I probably will not address them in the near future, since a lot of AI time was burned trying to solve and it continued to run into dead ends. |
Sorry, something went wrong.
|
@theoryshaw @Moult @sboddy I ran a verification pass on this PR against current v0.8.0 so it does not stay stuck. Summary of what I found. It merges cleanly against v0.8.0 at 55a2430, no conflicts. The lint-formatting failure is 31 cosmetic lines that one black . fixes. The compile-and-test failure is pre-existing on main (every ci.yml run on v0.8.0 in April 2026 was red) and unrelated, since this PR touches no C++. It composes with sboddy's merged #8608. They sit at different layers: #8608 classifies edges within a single element in the C++ serializer, this PR removes duplicate segments between elements in Python after merge. #8608 keeps the d="Mx,y Lx,y" format and the per-product group structure that this PR's parser relies on, so nothing here is superseded. Both default off, so the combination is untested. It genuinely works, on the cases it reaches. Using your own Highland_Haven_old.ifc in headless Blender 5.1.2, with the toggle on it removed real seams on 6 of 6 drawings I tried, including the exact 0QpDRRuif0R80d4CYPEQ$L / 0YyL6RtCL8axn6AG9ItFhs pair you called out. With the toggle off the output is byte-identical to baseline across all 76 projection groups and 3674 paths, so the opt-in gating is clean. Three things I think need addressing before merge:
One more thing worth a maintainer's eye: the fill_mode == "SVGFILL" changes are not behind the toggle, and num_passes going from 0 to 1 activates a code path that could never execute before. That is the draw.py-style approach aothms suggested in #3742 and may be the more promising direction, but it currently ships ungated. I could not A/B it because SVGFILL already crashes on baseline on this model (A linearring requires at least 4 coordinates), which looks like a separate pre-existing bug. On cost, the new pass added 7.5s to a 76-element plan and 5.5s to NORTH SECTION. Fixing item 1 should bring that down, since empty material keys currently force the expensive geometry test on every unmaterialised element. Happy to help with any of the above if useful. Test artifacts and the exact before/after segment lists are reproducible from your Highland_Haven_old.ifc. This comment was written with AI assistance. |
Sorry, something went wrong.
Sorry, something went wrong.
|
https://github.com/sboddy/IfcOpenShell/tree/fix-3742-coplanar-bonsai-projection_2_sjb If you want to take a look. I'll see if I (or more accurately, Claude) can't fix the outstanding issues. |
Sorry, something went wrong.
|
awesome, will take a look, this weekend. |
Sorry, something went wrong.
|
Wow! This thing is a severe PITA. Multiple rounds of trying to fix this thing. I got it to fix some of the above problems, but damn that wall is evil... evil I tell you! |
Sorry, something went wrong.
|
The more I've fought with this the more I think (1) is the correct interpretation for both PLAN and NORTH SECTION. (2) Is now correct. If you look real close the top lines look to have different thicknesses. However if you zoom right in on the actual model you will in fact see a difference between the two sides. So I'm calling this as right. (2) This is the best I've got so far. These lines are really tricky little buggers! I don't want to "steal your thunder" by posting my own PR, but there's quite a lot of useful background in my drafted PR description that might be worth preserving, and feeding to the next rounds of trying to beat this into submission. I can either create a draft PR, or post the draft .md directly here as a very long comment. Your choice. |
Sorry, something went wrong.
|
@sboddy this is genuinely appreciated, thank you for grinding through this and for folding in the three things we flagged (the empty material wildcard, the AXIS3 restriction on the camera-face fallback, and the toggle persistence). Please go ahead and open the draft PR, not just a comment. Reasons: it turns your write up into a referenceable, versioned artifact instead of one very long comment buried in an already long thread, CI can actually run against your branch so we get a real signal instead of screenshots, and it gives theoryshaw a clean diff to review against his original when he gets to it this weekend, rather than a wall of text to parse. We would rather build on a draft PR than a comment, and we will actively use it, it slots straight into the linework work we're doing on #8633. On your test rig calls: tracing your circled cases against the labeled grid, (1) being correct in both PLAN and NORTH SECTION lines up with how the code is actually meant to work. The style key check (IfcPresentationStyle IDs from StyledByItem) was written into the original PR specifically to give material less elements an identity to match on, so two undecorated elements sharing the same style merging is intended behavior, not scope creep. So no, you're not accidentally merging styles, that's the design working as intended for the no material case. (2) in PLAN, keeping the line where there is a real but subtle difference, is the right default for a drawing tool. Erring toward a spurious line beats erring toward erasing a real boundary, so that call looks right to us too. (2) in NORTH SECTION, the complicated wall with voids and infills, matches what theoryshaw already flagged back in April as one of the two tests he could not crack. That is the genuine frontier here, not a regression you introduced, and it is fine if that one stays open for another round. One thing worth knowing before you push: we checked and your branch and #8633 do not touch any of the same code paths (#8633 only changes the grouping criteria in merge_linework_and_add_metadata, your work is entirely in remove_coplanar_boundary_lines/mat_keys_match), so there is no conflict either direction and they can land in any order. Once the draft PR is up we are happy to run the same before and after regression pass on your branch that we ran on theoryshaw's, using Highland_Haven_old.ifc plus your synthetic test rig, so there is a reproducible verification alongside the screenshots. |
Sorry, something went wrong.
|
@sboddy I independently re-verified your branch (fix-3742-coplanar-bonsai-projection_2_sjb, currently at 4bc3e808d9) against Highland_Haven_old.ifc in headless Blender. All three things we flagged are fixed exactly as described. mat_keys_match((), x) now returns False (operator.py line 2130), the camera-face fallback now returns None for anything but AXIS3 (operator.py lines 2084 to 2085), and both toggles are now wired to EPset_Drawing the same way as has_linework/linework_mode (prop.py lines 500 to 511, tool/drawing.py lines 1012 to 1013 and 1049 to 1052). Live, on the real model: with the toggle off, output is path-count identical to v0.8.0 baseline on every drawing I tried, so the opt-in gating stays clean. With it on, the door and sanitary terminal outlines that were wrongly dissolved on theoryshaw's original branch are now correctly kept (confirmed the exact pair theoryshaw called out in April too, 0QpDRRuif0R80d4CYPEQ$L and 0YyL6RtCL8axn6AG9ItFhs, now resolves). On ELEVATION - WEST, the different-siding wall pair that was wrongly merged before is now correctly kept apart. I did not find any case where a boundary that should stay visible got newly removed. I was not able to fetch your synthetic test rig, hub.openingdesign.com blocks both git clone and raw file access for me, so I could not independently replay the numbered grid cases from your screenshots. From the screenshots themselves, the COMPLICATED WALL WITH VOIDS AND INFILLS case in NORTH SECTION does look like the same genuinely hard frontier theoryshaw flagged in April, not a regression, so it seems reasonable for that to stay open for another round. This is solid work, thank you for grinding through it. Given theoryshaw's own suggestion, this seems ready for a draft PR so CI can run and the diff is easy to review against the original. |
Sorry, something went wrong.
|
I'm making progress, but it is a torturous journey filled with lurking traps. I'm pushing fixes to my branch, and currently having yet another go at removing those lingering lines. I've not (so far) addressed the no-material, same style item. I'm still not sure if the intent is to merge styles or not. I've been ignoring that for now in favour of solving the real knotty problem wall. When I reach the point that I feel I can't prompt Claude to further resolve it (getting real close now) I'll create the draft PR with all the gory details. |
Sorry, something went wrong.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
…erial elements in Bonsai SVG drawings
Adds remove_coplanar_boundary_lines() to operator.py (Bonsai uses this path, not draw.py's main()). After merge_linework_and_add_metadata() assigns material CSS classes, this post-processes the SVG to delete projection line segments that appear in two or more adjacent, coplanar elements with the same material and presentation style.
Key design decisions: