# Crosstech Impl Ifc To Threejs

> >

- **Type:** Skill
- **Install:** `agentstack add skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-impl-ifc-to-threejs`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Impertio-Studio](https://agentstack.voostack.com/s/impertio-studio)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Impertio-Studio](https://github.com/Impertio-Studio)
- **Source:** https://github.com/Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package/tree/main/skills/source/crosstech-impl/crosstech-impl-ifc-to-threejs

## Install

```sh
agentstack add skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-impl-ifc-to-threejs
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# crosstech-impl-ifc-to-threejs

## Quick Reference

### Technology Boundary

| Aspect | Side A: IFC / web-ifc | Side B: Three.js |
|--------|----------------------|------------------|
| Data model | IFC entities with properties, relationships, geometry | Scene graph: Object3D, Mesh, BufferGeometry |
| Coordinate system | Z-up (Z vertical, X/Y ground plane) | Y-up (Y vertical, X/Z ground plane) |
| Geometry format | Parametric (extruded solids, BREP, CSG) | Tessellated triangles (BufferGeometry) |
| Properties | IfcPropertySet, IfcElementQuantity via relationships | NOT stored in scene graph — userData only |
| Materials | IfcSurfaceStyle, IfcMaterialLayerSetUsage | MeshPhongMaterial, MeshStandardMaterial |
| Identifiers | expressID (integer per entity) | Object3D.userData or Fragment index |
| Directionality | **IFC → Three.js (one-way rendering)** | No write-back to IFC |

### The Bridge: Two Approaches

| Approach | Package | When to Use |
|----------|---------|-------------|
| **@thatopen/components** (recommended) | `@thatopen/components` + `@thatopen/components-front` | Production BIM viewers, rapid development |
| **Custom web-ifc + Three.js** | `web-ifc` + `three` | Full rendering control, existing Three.js apps |

### Critical Warnings

**NEVER** assume IFC properties exist in the Three.js scene graph. Properties are stored in the IFC model and MUST be queried via web-ifc or @thatopen/components APIs separately.

**NEVER** manually rotate the model by -90° around X when using web-ifc. web-ifc ALREADY transforms geometry from Z-up to Y-up internally. Applying a second rotation produces upside-down models.

**NEVER** use `LoadAllGeometry` for production models. It loads ALL geometry into memory at once. ALWAYS use `StreamAllMeshes` for incremental processing.

**NEVER** use a Three.js version different from the one pinned by @thatopen/components. Version mismatches cause silent rendering failures. ALWAYS verify with `npm ls three`.

**NEVER** forget to call `ifcApi.CloseModel(modelID)` when done. WASM memory is NOT garbage collected by JavaScript.

**NEVER** set the WASM path AFTER calling `ifcApi.Init()`. The path MUST be configured BEFORE initialization.

---

## Technology Boundary Detail

### Side A: IFC / web-ifc

IFC files contain parametric geometry, semantic properties, spatial hierarchy, and material definitions. web-ifc (v0.0.77) parses IFC via WebAssembly and provides:

- **Model loading**: `IfcAPI.OpenModel(data, settings)` returns a modelID
- **Geometry extraction**: `StreamAllMeshes` yields `FlatMesh` objects with vertex/index arrays
- **Property access**: `ifcApi.properties.getPropertySets(modelID, expressID)` or manual `GetLine`
- **Spatial structure**: `ifcApi.properties.getSpatialStructure(modelID)` returns the full hierarchy
- **Schema detection**: `ifcApi.GetModelSchema(modelID)` returns "IFC2X3", "IFC4", or "IFC4X3"

Vertex data format from web-ifc: 6 floats per vertex (x, y, z, nx, ny, nz — position + normal interleaved).

### Side B: Three.js Scene

Three.js (r160+) renders triangulated meshes via WebGL/WebGPU:

- **BufferGeometry**: position attribute (Float32Array, 3 components), normal attribute (Float32Array, 3 components), index (Uint32Array)
- **Materials**: MeshPhongMaterial or MeshStandardMaterial with color, opacity, transparency
- **Scene graph**: Object3D hierarchy for spatial organization
- **Raycasting**: Raycaster intersects meshes for element selection
- **Camera**: PerspectiveCamera with OrbitControls for BIM navigation

### The Bridge: Conversion Pipeline

```
IFC File (.ifc)
    │
    ▼
web-ifc (WASM) ─── parses IFC, tessellates geometry, transforms Z-up → Y-up
    │
    ├── Option A: @thatopen/components
    │   IFC → Fragments (Flatbuffers) → Three.js Scene
    │   Automatic: highlighting, hiding, clipping, properties
    │
    └── Option B: Custom pipeline
        StreamAllMeshes → BufferGeometry + Material → Three.js Scene
        Manual: raycasting, property panels, spatial hierarchy
```

---

## Axis Convention Handling

### Rule: web-ifc Handles the Transform

web-ifc converts IFC Z-up coordinates to Three.js Y-up coordinates internally during geometry extraction. The output of `GetVertexArray` is ALREADY in Y-up format.

**When you MUST apply a manual transform**:
- Receiving coordinates from IfcOpenShell (server-side, Z-up output)
- Reading raw IFC placement matrices without web-ifc processing
- Importing point clouds or external meshes that use Z-up

```typescript
// ONLY for Z-up data NOT processed by web-ifc
const rotationMatrix = new THREE.Matrix4().makeRotationX(-Math.PI / 2);
geometry.applyMatrix4(rotationMatrix);
```

### COORDINATE_TO_ORIGIN Setting

web-ifc defaults `COORDINATE_TO_ORIGIN: true`, which centers geometry at the world origin. This removes real-world coordinates (easting/northing). If you need georeferenced positioning:

```typescript
const modelID = ifcApi.OpenModel(data, {
  COORDINATE_TO_ORIGIN: false, // Keep original IFC coordinates
});
```

---

## Essential Patterns

### Pattern 1: @thatopen/components BIM Viewer (Recommended)

```typescript
import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";
import * as THREE from "three";

const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create();

const container = document.getElementById("viewer")!;
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.SimpleCamera(components);
world.scene = new OBC.SimpleScene(components);

// IFC loader with WASM configuration
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup({
  autoSetWasm: false,
  wasm: { path: "https://unpkg.com/web-ifc@0.0.77/", absolute: true },
});

// Fragment manager — handles model lifecycle
const fragments = components.get(OBC.FragmentsManager);
fragments.list.onItemSet.add(({ value: model }) => {
  model.useCamera(world.camera.three);
  world.scene.three.add(model.object);
  fragments.core.update(true);
});

// Load IFC
const response = await fetch("/models/building.ifc");
const data = new Uint8Array(await response.arrayBuffer());
await ifcLoader.load(data, false, "building");

// Interaction: highlighting + hiding
const highlighter = components.get(OBCF.Highlighter);
highlighter.multiple = "ctrlKey";
highlighter.zoomToSelection = true;

const hider = components.get(OBC.Hider);
```

### Pattern 2: Custom web-ifc + Three.js Pipeline

```typescript
import * as THREE from "three";
import { IfcAPI } from "web-ifc";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

// Initialize web-ifc
const ifcApi = new IfcAPI();
ifcApi.SetWasmPath("/static/wasm/"); // BEFORE Init()
await ifcApi.Init();

// Load model
const ifcData = new Uint8Array(await fetch("model.ifc").then(r => r.arrayBuffer()));
const modelID = ifcApi.OpenModel(ifcData, { COORDINATE_TO_ORIGIN: true });

// expressID → mesh mapping for selection
const expressIdToMesh = new Map();

// Stream geometry and convert to Three.js
ifcApi.StreamAllMeshes(modelID, (flatMesh) => {
  const placedGeometries = flatMesh.geometries;
  for (let i = 0; i  {
  const mouse = new THREE.Vector2(
    (event.clientX / window.innerWidth) * 2 - 1,
    -(event.clientY / window.innerHeight) * 2 + 1
  );
  raycaster.setFromCamera(mouse, camera);
  const hits = raycaster.intersectObjects(scene.children, true);

  if (hits.length > 0) {
    const expressID = hits[0].object.userData.expressID;
    if (expressID === undefined) return;

    // Get entity info
    const entity = ifcApi.GetLine(modelID, expressID, true);
    console.log("Selected:", entity.Name?.value, "Type:", entity.constructor.name);

    // Get property sets
    const psets = await ifcApi.properties.getPropertySets(modelID, expressID, true);
    displayPropertyPanel(entity, psets);
  }
});
```

### Pattern 4: Scene Graph Mirroring IFC Spatial Structure

```typescript
async function buildSpatialHierarchy(
  ifcApi: IfcAPI, modelID: number, scene: THREE.Scene
): Promise> {
  const structure = await ifcApi.properties.getSpatialStructure(modelID);
  const groups = new Map();

  function traverse(node: any, parent: THREE.Object3D) {
    const group = new THREE.Group();
    group.name = node.Name?.value ?? `Element_${node.expressID}`;
    group.userData.expressID = node.expressID;
    parent.add(group);
    groups.set(node.expressID, group);

    if (node.children) {
      for (const child of node.children) {
        traverse(child, group);
      }
    }
  }

  traverse(structure, scene);
  return groups;
}
```

---

## Fragments Format

@thatopen/components does NOT render IFC geometry directly. The pipeline is:

1. **IFC file** → web-ifc parses and tessellates
2. **Fragments conversion** → Flatbuffers-based binary format optimized for GPU
3. **Three.js rendering** → Fragments are rendered as instanced meshes

**Key implications**:
- First load is slow (parse + tessellate + convert)
- Exported `.frag` files load near-instantly on subsequent visits
- Server-side pre-conversion eliminates client-side processing cost

```typescript
// Export model to .frag for caching
const fragsBuffer = await model.getBuffer(false);
const file = new File([fragsBuffer], "model.frag");
// Upload to server or store in IndexedDB
```

---

## Common Operations

### Property Panel for Selected Element

```typescript
async function getElementProperties(
  ifcApi: IfcAPI, modelID: number, expressID: number
): Promise>> {
  const result: Record> = {};

  const psets = await ifcApi.properties.getPropertySets(modelID, expressID, true);
  for (const pset of psets) {
    if (!pset.HasProperties) continue;
    const setName = pset.Name?.value ?? "Unnamed";
    result[setName] = {};
    for (const prop of pset.HasProperties) {
      if (prop.NominalValue) {
        result[setName][prop.Name.value] = prop.NominalValue.value;
      }
    }
  }
  return result;
}
```

### Category-Based Visibility (Hider Pattern)

```typescript
// @thatopen/components approach
const wallIds = model.getItemsOfCategories(["IFCWALL"]);
const wallMap: OBC.ModelIdMap = {};
wallMap[model.modelId] = wallIds;
await hider.isolate(wallMap); // Show ONLY walls

// Custom approach
function hideByType(ifcApi: IfcAPI, modelID: number, typeCode: number,
                    meshMap: Map, visible: boolean) {
  const ids = ifcApi.GetLineIDsWithType(modelID, typeCode);
  for (let i = 0; i  {
  material.polygonOffset = true;
  material.polygonOffsetUnits = 1;
  material.polygonOffsetFactor = Math.random();
});

// Custom pipeline — apply to every material
const material = new THREE.MeshPhongMaterial({ /* ... */ });
material.polygonOffset = true;
material.polygonOffsetUnits = 1;
material.polygonOffsetFactor = Math.random();
```

### Memory Management

```typescript
// ALWAYS set memory limits for production
const modelID = ifcApi.OpenModel(data, {
  MEMORY_LIMIT: 1073741824,  // 1GB — conservative for browsers
  TAPE_SIZE: 33554432,       // 32MB read buffer
});

// ALWAYS close models when done
ifcApi.CloseModel(modelID);

// For @thatopen/components — dispose fragments
fragments.dispose();
```

---

## Decision Tree

```
START: You need IFC in a Three.js scene
│
├── Q1: Building a new BIM viewer from scratch?
│   ├── YES → Use @thatopen/components (Pattern 1)
│   │   Provides: loading, highlighting, hiding, clipping, properties
│   └── NO → Integrating into existing Three.js app?
│       ├── YES → Use custom web-ifc pipeline (Pattern 2)
│       └── NO → Just need static 3D preview?
│           └── Use @thatopen/components with minimal config
│
├── Q2: Do you need IFC property access?
│   ├── YES → MUST maintain web-ifc model reference alongside Three.js scene
│   │   Properties are NOT in the scene graph — query via ifcApi or components API
│   └── NO → Geometry-only conversion is sufficient
│
├── Q3: Model size > 100MB?
│   ├── YES → Pre-convert to Fragments on server, serve .frag files
│   │   Use StreamAllMeshes (NEVER LoadAllGeometry)
│   │   Set MEMORY_LIMIT in LoaderSettings
│   └── NO → Direct browser loading is acceptable
│
├── Q4: Coordinates from IfcOpenShell (server-side)?
│   ├── YES → Apply rotation: Matrix4.makeRotationX(-Math.PI / 2)
│   └── NO (from web-ifc) → No rotation needed, already Y-up
│
└── Q5: Need georeferenced coordinates?
    ├── YES → Set COORDINATE_TO_ORIGIN: false
    └── NO → Keep default (true) for centered viewing
```

---

## Data Loss Summary

| IFC Data | Three.js Representation | Status |
|----------|------------------------|--------|
| Geometry (parametric) | Tessellated BufferGeometry | **LOST** — triangulation is irreversible |
| Surface colors | MeshPhongMaterial / MeshStandardMaterial | Simplified |
| Material layers | Single material per mesh | **LOST** — layer structure not preserved |
| Properties (Psets) | NOT in scene graph | Available via API only |
| Quantities | NOT in scene graph | Available via API only |
| Spatial hierarchy | Optional Group hierarchy | Partial — requires manual setup |
| Relationships | NOT represented | **LOST** unless queried via API |
| GlobalId | userData or Fragment index | Available via API only |
| Type definitions | NOT in scene graph | Available via API only |

---

## Reference Links

- [references/methods.md](references/methods.md) — API signatures for web-ifc geometry, @thatopen/components, Three.js conversion
- [references/examples.md](references/examples.md) — Complete working examples for both approaches
- [references/anti-patterns.md](references/anti-patterns.md) — Common mistakes in IFC→Three.js integration

### Official Sources

- https://github.com/ThatOpen/engine_web-ifc — web-ifc source and API (v0.0.77)
- https://github.com/ThatOpen/engine_components — @thatopen/components (v3.3.2)
- https://docs.thatopen.com/ — ThatOpen documentation
- https://threejs.org/docs/ — Three.js documentation
- https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ — IFC4 schema

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Impertio-Studio](https://github.com/Impertio-Studio)
- **Source:** [Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package](https://github.com/Impertio-Studio/Cross-Tech-AEC-Claude-Skill-Package)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-impertio-studio-cross-tech-aec-claude-skill-package-crosstech-impl-ifc-to-threejs
- Seller: https://agentstack.voostack.com/s/impertio-studio
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
