# ifc

IFC model loader — geometry, spatial structure, properties.

```typescript
import { IfcModel, createIfcWorker } from '@yaoc1996/js-tools/ifc'
```

---

## `class IfcModel`

A Three.js `Mesh` that holds a loaded IFC model's geometry, materials, spatial structure, and property data. The constructor is private — use `IfcModel.load()` to create instances.

### `IfcModel.load(params, onProgress?) → Promise<IfcModel>`

Loads an IFC file in a background worker and constructs the model.

#### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `params` | `LoadModelDataParams` | — | Load configuration (see Types) |
| `onProgress` | `(progress: number) => void` | `undefined` | Progress callback receiving values in `[0, 1]` |

#### Returns

`Promise<IfcModel>` — Fully constructed model with geometry and materials. The model's `position` is set to the centroid offset so geometry is centered at the origin.

#### Example

```typescript
import { IfcModel } from '@yaoc1996/js-tools/ifc'

const model = await IfcModel.load({
    dataBuffer: arrayBuffer,
    withSpatialStructure: true,
    withProperties: true,
    withEdges: true,
})

scene.add(model)
if (model.edgeModel) scene.add(model.edgeModel)
```

#### Notes

- Supports IFC2X3, IFC4, and IFC4X3 schemas. Spatial structure and property data are extracted via `web-ifc`'s cross-schema `properties.*` helpers.
- The worker is always terminated, even on error, and the IFC model is always closed (`try/finally` around `OpenModel`/`CloseModel`).
- The `web-ifc.wasm` binary is bundled as a sibling asset of the worker. The optional `wasmPath` override is only needed when you ship the wasm yourself or want to host it at a custom URL.
- `IfcSpatialNode.elements` is an IFC2X3-only legacy field; cross-schema parsing flattens IfcRelAggregates and IfcRelContainedInSpatialStructure into a single `children` tree. The `globalId` (IFC GUID) is included on every node when available.

### `getFeature(id) → IfcSpatialNode | undefined`

Returns the `IfcSpatialNode` for the given express ID, or `undefined` if no spatial structure was loaded.

### `getFeatureProperties(id) → { propertySet: IfcPropertySet; properties: IfcProperty[] }[] | undefined`

Returns all property sets (and their properties) associated with the given express ID.

### `getFeatureWithChildren(id) → IfcSpatialNode[]`

Returns the node for the given ID together with all of its descendant nodes (children and elements, recursively). Returns an empty array if the node is not found.

### `setSpatialStructure(spatialStructure) → void`

Populates the internal feature map from a spatial structure tree.

### `setPropertyData(propertySets, properties, propertySetToPropertiesMap, propertySetToObjectsMap) → void`

Builds the internal property index from raw property data returned by the worker.

### `setEdgeModel(edgeModel) → void`

Attaches a `LineSegments` edge model to the `IfcModel`. The edge model is a separate Three.js object — add it to the scene independently.

### `dispose() → void`

Disposes all GPU resources: geometry buffers, materials, and the edge model (if present).

---

## `createIfcWorker()`

Spawns the IFC processing worker and returns a [Comlink](https://github.com/GoogleChromeLabs/comlink)-wrapped handle. Used internally by `IfcModel.load()`.

```typescript
import { createIfcWorker } from '@yaoc1996/js-tools/ifc'
import { transfer } from 'comlink'

const handle = createIfcWorker()
try {
    const result = await handle.api.loadModelData(
        transfer(params, [params.dataBuffer])
    )
} finally {
    handle.terminate()
}
```

Returns `{ api: Remote<IfcWorkerApi>, terminate(): void }`. Use `transfer(...)` from `comlink` to transfer the `dataBuffer` ownership.

---

## Types

```typescript
type GeometryData = {
    expressID: number
    positionBuffer: ArrayBuffer
    normalBuffer: ArrayBuffer
    indexBuffer: ArrayBuffer
    edgeIndexBuffer?: ArrayBuffer
}

type IfcRelationshipMap = { [key: string]: number[] }

type IfcProperty = {
    expressID: number
    name: string
    value: unknown
}

type IfcPropertySet = {
    expressID: number
    name?: string
}

type IfcSpatialNode = {
    expressID: number
    type: number
    typeName: string
    name?: string
    globalId?: string                  // IFC GUID
    children?: IfcSpatialNode[]
    elements?: IfcSpatialNode[]        // IFC2X3-only legacy, undefined on IFC4 / IFC4X3
}

type LoadModelDataParams = {
    name?: string
    wasmPath?: string                  // optional override; library bundles its own wasm
    withEdges?: boolean
    withSpatialStructure?: boolean
    withProperties?: boolean
    edgeMatchingPrecision?: number
    dataBuffer: ArrayBuffer
}

type LoadModelDataResult = {
    offset: readonly [number, number, number]
    dataByColor: { [key: string]: GeometryData[] }
    spatialStructure?: IfcSpatialNode
    propertySetToObjectsMap?: IfcRelationshipMap
    propertySetToPropertiesMap?: IfcRelationshipMap
    propertySets?: IfcPropertySet[]
    properties?: IfcProperty[]
}

type IfcWorkerApi = {
    loadModelData(
        params: LoadModelDataParams,
        onProgress?: (progress: number) => void
    ): Promise<LoadModelDataResult>
}
```
