Skip to content

feat(ifc): preserve unsupported IFC geometry - #553

Open
yorhodes wants to merge 2 commits into
pascalorg:mainfrom
yorhodes:codex/ifc-type-support
Open

feat(ifc): preserve unsupported IFC geometry#553
yorhodes wants to merge 2 commits into
pascalorg:mainfrom
yorhodes:codex/ifc-type-support

Conversation

@yorhodes

@yorhodes yorhodes commented Jul 27, 2026

Copy link
Copy Markdown

Summary

  • add a format-neutral imported-mesh built-in node with registry-driven 3D and floorplan geometry
  • preserve unsupported or non-parametric IFC elements as serialized triangle geometry with source display colors
  • import IFC spaces as room zones and keep storey-relative elevations consistent across native and fallback nodes
  • correct wall centerlines, unhosted opening placement, roof/landing/skylight fallback handling, and overly aggressive wall merging
  • map door families from IfcDoor.OperationType and glazing from Pset_DoorCommon.GlazingAreaFraction
  • allow browser and Node callers to configure the WebIFC WASM path

Architecture

  • source-format parsing remains isolated in @pascal-app/ifc-converter
  • the reusable mesh schema stays in core, while Three.js and floorplan builders live in packages/nodes/src/imported-mesh
  • imported meshes are import-only and hidden from the empty-object palette
  • no viewer/editor kind-specific dispatch or project-name/material-name heuristics are introduced

Verification

  • npm run build --workspace=@pascal-app/core
  • npm run build --workspace=@pascal-app/ifc-converter
  • focused TypeScript check for the imported-mesh node
  • bun test packages/core/src
  • bun test packages/nodes/src — 877 passed, 1 skipped
  • bun test packages/ifc-converter/tests — 9 passed
  • end-to-end conversion of 01-duplex.ifc — 245 nodes, including 100 exact-geometry fallbacks

Note

Medium Risk
Large changes to IFC→scene-graph mapping and spatial math can shift imported model layout and wall/opening behavior; mitigated by new unit tests for cleanup and door semantics.

Overview
Adds a first-class imported-mesh scene node (indexed triangle primitives with IFC colors) and wires it through core schema, editor events, and a hidden @pascal-app/nodes definition so import-only geometry is selectable and renderable without palette clutter.

The IFC converter stops dropping unsupported or failed-native elements: beams, furnishings, railings, and similar types—and walls/slabs/doors/windows that could not be parameterized—are emitted as imported-mesh via GetFlatMesh, with axis handling that avoids double swapYZ on mesh vertices. IfcSpace maps to zone (room) nodes; doors pick families from OperationType and glazing from Pset_DoorCommon.GlazingAreaFraction. Placement and stacking improve through inferred storeys for building-aggregated roofs, level heights from IFC elevations, level-local coordinates, stricter wall centerlines, and skipping roof/landing slabs and unhosted wall-hosted openings (skylights fall through to mesh). Post-import wall simplification is tightened so parallel offset walls and different materials are not merged. wasmPath is configurable for Node callers.

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

@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 5 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 3449f30. Configure here.

if (parentNodeId && nodes[parentNodeId]) {
;(nodes[parentNodeId] as { children?: string[] }).children?.push(nodeId)
}
importedMeshCount++

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stair flights duplicate stair geometry

Medium Severity

When converting IFCSTAIR elements, their child IFCSTAIRFLIGHT express IDs are not registered as processed. This causes the fallback mesh pass to create duplicate imported-mesh nodes for stair flights, leading to double-rendered stair geometry.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3449f30. Configure here.

for (let i = 0; i + 2 < primitive.positions.length; i += 3) {
const point: [number, number] = [primitive.positions[i]!, primitive.positions[i + 2]!]
unique.set(`${point[0].toFixed(5)}:${point[1].toFixed(5)}`, point)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Space hull wrong plan axes

Medium Severity

When an IFCSPACE has no swept profile, meshFootprint builds the zone polygon from GetFlatMesh vertex positions[0] and positions[2]. With default swapYZ, that extraction maps the second plan axis from IFC world Z, while native walls and profile-based zones use scene X/Y from worldToScene. Room zones from the mesh hull can sit rotated or offset from walls.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3449f30. Configure here.

deletable: true,
presettable: false,
},
geometry: buildImportedMeshGeometry,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Moving imported mesh double-transforms

Medium Severity

IFC import bakes level-local world coordinates into primitives while leaving position at [0,0,0], but the node definition marks imported-mesh as movable. ParametricNodeRenderer applies node.position on the outer group, so dragging an imported mesh shifts it twice relative to its baked vertices.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3449f30. Configure here.

const materialA = (a.wall.metadata as { material?: unknown } | undefined)?.material
const materialB = (b.wall.metadata as { material?: unknown } | undefined)?.material
if (typeof materialA !== 'string' || typeof materialB !== 'string') return true
return materialA === materialB

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing material allows wall merge

Medium Severity

wallMaterialCompatible treats a missing metadata.material on either wall as compatible and allows merge. Collinear exterior and interior fragments can combine when only one side received an IFC material association, undoing the intent of the new material guard.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3449f30. Configure here.

upper.pop()
const hull = [...lower, ...upper]
return hull.length >= 3 ? hull : null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Space footprint uses convex hull

Medium Severity

When an IFCSPACE has no swept profile, meshFootprint builds a convex hull from mesh vertices and that polygon becomes the room ZoneNode. Concave or L-shaped spaces are inflated to their hull, so room zones and anything derived from the boundary can be wrong while the commit still counts the space as successfully imported.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3449f30. Configure here.

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.

1 participant