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.
This version has some functional differences:
- Structured JSON error message instead of free text (on STDOUT not STDERR)
- New --prioritise-local flag to control which side wins in merge conflicts (not used by Bonsai yet)
- IfcLocalPlacement conflicts now auto-resolve instead of failing the merge (partial solution to #6885)
- Float values are normalised when comparing entities (workaround for #7696)
Include bonsai_git_branch in get_debug_info(). For dev environments
using the GitPython-based update_commit_data() path, the branch is
read from repo.active_branch.name. For built extensions, a 7777777
placeholder is replaced at build time via the Makefile, matching the
existing pattern for bonsai_commit_hash and bonsai_commit_date.
Generated with the assistance of an AI coding tool.
Move all business logic into bonsai core and tool. Performance fixes to
minimise file IO, various minor bug fixes and tests.
Generated with the assistance of an AI coding tool.