Skip to content

[Bugs] ifc-converter, editor: export wall assembly layers to IFC; unpainted PDF shapes no longer filled - #1104

Merged
Aymericr merged 2 commits into
pascalorg:mainfrom
muratdbc:fix/ifc-wall-layers-and-pdf-unpainted-paths
Oct 20, 2026
Merged

Aymericr merged 2 commits into
pascalorg:mainfrom
muratdbc:fix/ifc-wall-layers-and-pdf-unpainted-paths

Conversation

@muratdbc

@muratdbc muratdbc commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #1103. Related: #981 (IFC4 export).

IFC — wall assembly layers (packages/ifc-converter/src/export/scene-to-ifc.ts)

  • A wall with an assembly now exports one IfcMaterialLayer per layer: an IfcMaterial named after the layer's material (falling back to its role), the layer thickness, the layer id as Name, the role as Description, and the IFC4 category (LoadBearing for the core, Insulation, Finish). air layers are marked ventilated.
  • Layers run from the front face (+n), where the existing AXIS2/POSITIVE usage at -faces.a starts. A stack with face: 'exterior' whose exterior is the back face is reversed, so the exterior finish stays outward whichever way the wall was drawn.
  • The layer set takes the preset label (e.g. Exterior 2x6 — stucco).
  • Walls without an assembly, or whose layers don't sum to the body thickness, keep today's single Core layer. Usages are still shared per thickness/offset/stack.

PDF — unpainted shapes (packages/editor/src/lib/floorplan/floorplan-pdfkit-renderer.ts)

  • paintStyledGeometry built a path for geometry with neither fill nor stroke (zone outlines) and never ended it, so the next fill (the walls) painted it as well and every room printed as a dark block. The path is now ended with n.

How to test

  1. bun test packages/ifc-converter/tests/export-wall-assembly.test.ts: 3 new tests (layer stack and set name, one wall reversed so both orientations are covered, no-assembly fallback). Two of them fail on main.
  2. bun test packages/editor/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts: the new test fails on main.
  3. The full ifc-converter suite passes (95 tests). I also opened the exported IFC (two-bedroom template with the exterior-2x6-stucco and interior-2x4-drywall presets) in IfcOpenShell 0.9: 0 schema issues, 4 and 3 layers per wall, and a geometric check that the stucco is on the outside of every exterior wall, with one wall reversed as well.

Screenshots / screen recording

Not a UI change. Before the PDF fix, a floorplan sheet of the two-bedroom template printed every room as a solid #374151 block; after it, rooms are blank with walls, dimensions and marks drawn as usual.

Checklist

  • I've tested this locally with bun dev (tested headless through the packages and tests instead)
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code

https://claude.ai/code/session_0129pCwGotaTiKv5JgBv8vaC


Note

Medium Risk
IFC wall material export behavior changes for assembled walls (more entities and sharing keys); PDF fix is localized to unpainted geometry rendering.

Overview
Fixes floorplan PDF export where zone outlines with no fill/stroke left an open path, so the next wall fill painted every room as a solid block. Unpainted geometry now ends the path with PDF operator n (end path without painting), with a regression test on grouped zone + wall polygons.

IFC export now maps Pascal wall assemblies to multi-layer IfcMaterialLayerSet stacks when layer thicknesses match the wall body. Each layer gets its own material, thickness, role metadata, IFC4 category (e.g. LoadBearing for core), and ventilated IfcLogical for air gaps; layer order follows the front face and reverses when an exterior-facing assembly’s exterior is on the back. Preset labels name the layer set; walls without a valid assembly still get a single core layer. New export-wall-assembly.test.ts covers stack order, reversed walls, unknown exterior side, air layers, and the no-assembly fallback.

Reviewed by Cursor Bugbot for commit 3cdaf32. Bugbot is set up for automated code reviews on this repo. Configure here.

…illed

IFC: walls with an assembly were written as one "Core" IfcMaterialLayer of
the total thickness, so receiving tools (Revit, Archicad) never saw the
stucco / sheathing / framing / drywall stack. Each assembly layer is now an
IfcMaterialLayer with its material, thickness, role and IFC4 category
(LoadBearing for the core, Insulation, Finish), ordered from the front face
so an exterior-faced stack stays outward on both wall directions. The layer
set takes the preset label. Walls without an assembly keep the single Core
layer.

PDF: paintStyledGeometry built a path for geometry with neither fill nor
stroke (zone outlines) and never ended it, so the next fill (the walls)
painted it too and rooms printed as solid wall-coloured blocks in floorplan
and sheet exports. The unpainted path is now ended with 'n'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129pCwGotaTiKv5JgBv8vaC

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 52f7233. Configure here.

Comment thread packages/ifc-converter/src/export/scene-to-ifc.ts
Comment thread packages/ifc-converter/src/export/scene-to-ifc.ts Outdated

@Aymericr Aymericr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! The PDF fix (ending the unpainted path with n) looks right.

One thing to fix on the IFC side: the layer reversal checks resolveWallExteriorSide(wall) === -1, but core's wall-layer-bands.ts treats an unknown side as ?? -1. So on walls whose exterior side isn't known, IFC puts the stucco on the opposite face from what the 3D view shows. Could you switch it to (resolveWallExteriorSide(wall) ?? -1) === -1 and add a test for an unknown-side wall?

Minor: walls with identical layers but different preset labels end up sharing one layer set, so the second wall shows the first one's name. Worth including the label in the cache key.

if (Math.abs(sum - thickness) > 1e-4) return null
// Layers run from the front face unless the stack starts at the exterior
// and the exterior is the back face.
const startsAtBack = assembly.face === 'exterior' && resolveWallExteriorSide(wall) === -1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Core treats an unknown side as -1 here (?? -1 in wall-layer-bands.ts), so this should be (resolveWallExteriorSide(wall) ?? -1) === -1 to keep IFC matching the 3D view.

Copy link
Copy Markdown
Contributor

Hey @muratdbc, quick nudge on this one, no new push since the review yet. Two things left:

  1. The unknown-side case: startsAtBack should use (resolveWallExteriorSide(wall) ?? -1) === -1 so it matches wall-layer-bands.ts, plus a test with a wall whose exterior side is unknown.
  2. IsVentilated on the air layer is written as a boolean (bool(true)), but in IFC4 it's an IfcLogical, so it should be emitted as a logical value.

Once those are in, I'll kick off CI and take another look. Thanks!

…ed as IfcLogical

An exterior-faced assembly on a wall whose exterior side is undetermined
now starts at the back face, matching core's `?? -1` fallback in
wall-layer-bands, so IFC layers sit on the same faces as the 3D view.

IfcMaterialLayer.IsVentilated is an IfcLogical in IFC4; air layers now
emit IFCLOGICAL(.T.) instead of IFCBOOLEAN(.T.).

Co-authored-by: Murat Gocmen <8277046+muratdbc@users.noreply.github.com>
@cursor

cursor Bot commented Oct 20, 2026

Copy link
Copy Markdown

Thanks @muratdbc for this contribution! We pushed the two small review fixes on top of your commit (3cdaf32) so this can land sooner: walls with an unknown exterior side now use the ?? -1 fallback so IFC matches the 3D view, and the air layer's IsVentilated is now written as an IfcLogical. Tests for both are included.

@Aymericr
Aymericr merged commit 1d4b8f4 into pascalorg:main Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IFC export drops wall assembly layers; floorplan PDF fills rooms with the wall colour

3 participants