merge_linework_and_add_metadata created its merged groups with
etree.Element("g"), which puts them outside the SVG namespace.
move_elements_to_top then looks for them with ".//svg:g[...]", so
EPset_Drawing.BringToFront never matched the merged cut and material
layer groups - i.e. exactly the groups holding the filled, closed
polygons that do the occluding. Listed elements failed to hide what was
behind them.
This is invisible in the written file: lxml serialises an unnamespaced
<g> inside an SVG-default-namespace parent as plain <g class="...">
without xmlns="", so on re-parse it is SVG-namespaced. The mismatch only
exists in memory, during the session where it matters.
Creating the group and path in the SVG namespace leaves the serialised
output byte-identical and makes them visible to the later XPath queries.
Fixes IfcOpenShell#9317
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes #9317
EPset_Drawing.BringToFront only reordered some of an element's SVG groups. The ones it missed were the merged cut and material-layer groups — i.e. exactly the groups holding the filled, closed polygons that do the occluding — so a listed element failed to hide anything behind it.
merge_linework_and_add_metadata created those groups with etree.Element("g"), which puts them outside the SVG namespace. move_elements_to_top looks for them with .//svg:g[contains(@class, '...')], which cannot match an unnamespaced element.
The reason this went unnoticed is that it is invisible in the written file. lxml serialises an unnamespaced <g> inside an SVG-default-namespace parent as plain <g class="..."> — no xmlns="" — so on re-parse the element is SVG-namespaced. The mismatch exists only in memory, during the one session where it matters:
Creating the group and path in the SVG namespace leaves the serialised output byte-identical and makes them visible to the later XPath queries.
Before / after
Test case: a section with EPset_Drawing.BringToFront = "IfcSlab", where the slab passes through a wall.
Group order before — only the serializer's projection group (correctly namespaced) reaches the end; the two merged IfcSlab cut groups stay underneath the wall:
After, all IfcSlab groups are moved to the end and the slab correctly occludes the wall. (The remaining sliver of wall on the far side is correct — the slab genuinely does not extend that far.)
Not addressed here
There are two more unnamespaced etree.Element("path") calls in the same file (the SHAPELY surface fills and the IfcSpace pass). Nothing queries them in-memory afterwards, so they are latent rather than broken; left alone to keep this change minimal.
🤖 Generated with Claude Code