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.
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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>
- Add Anthropic prompt caching (cache_control on system prompt and
tools) to reduce repeated token costs by ~90%
- Truncate large tool results in conversation history (2000 char cap)
to prevent context bloat from ifc_tree/ifc_select responses
- Add sliding window (40 messages) on conversation history, trimming
at user message boundaries to avoid breaking tool-call sequences
- Default "New IFC" button to IFC4X3 schema instead of IFC4
- Constrain ifc_new schema parameter with enum to prevent invalid
schema strings like "IFC4X3ADD2"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parse ifcmerge JSON output and display a per-conflict breakdown in the
panel when merge fails. Ctrl+click on the Merge button previews
conflicts without committing. Add SelectConflictEntity operator to
select and frame the conflicting object in the 3D viewport.
Generated with the assistance of an AI coding tool.
A degenerate edge (zero-length segment) caused an early `return`
of a tuple instead of continuing the loop, resulting in a
TypeError when snap.py iterated the result and tried to assign
`point["group"]` on a float.
Generated with the assistance of an AI coding tool.
Add a provider selector (OpenAI / Anthropic) to the ifcchat web UI,
allowing users to use their Anthropic API key with Claude models
instead of only OpenAI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a remote branch tip is checked out (resulting in detached HEAD),
the new-branch name field is now pre-filled with the local equivalent
of the remote branch name (generating a unique suffix if that name is
already taken), so the commit button is immediately usable.
See #7580
Generated with the assistance of an AI coding tool.