关于 threejs-impl-ifc-viewer
这是使用 Three.js 实现 IFC 解析和 BIM 查看器的参考技能。它对比底层 web-ifc API 与高层 @thatopen/components 工具包,并介绍几何、属性和内存管理模式。
主要功能
- 01web-ifc 与 @thatopen/components 方案对比
- 02IFC 几何与属性提取参考
- 03BIM 查看器组件用法
- 04大型 IFC 模型内存管理
- 05弃用库与许可证注意事项
适用场景
- 01为自定义 Three.js 管线提取 IFC 几何
- 02构建含构件选择和剖切面的 BIM 查看器
- 03读取 IFC 构件属性而不渲染几何
- 04规划大型 IFC 文件的分批加载
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ impertio-studio/ three.js-claude-skill-package --skill threejs-impl-ifc-viewer安装后在 Claude Code 中输入/threejs-impl-ifc-viewer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitkeepreferences/anti-patterns.mdexamples.mdmethods.md详细介绍
threejs-impl-ifc-viewer
快速参考
库对比
| 库 | 许可证 | 级别 | 状态 | 使用场景 |
|---|---|---|---|---|
web-ifc | MIT | 底层 WASM 解析器 | 活跃(v0.77+) | 自定义流水线、完全控制 |
@thatopen/components | MIT(v3.x) | 高层 BIM 工具包 | 活跃(v3.3.2+) | 完整 BIM 查看器、快速原型开发 |
@thatopen/components-front | MIT | 浏览器专用扩展 | 活跃 | UI、高级可视化 |
web-ifc-three | — | 已弃用的桥接库 | 已停止维护 | 切勿使用 |
openbim-components(v1) | — | 已弃用的工具包 | 已停止维护 | 切勿使用 |
包安装
# Low-level approach (MIT, full control)
npm install web-ifc three
# High-level approach (MIT, batteries-included)
npm install @thatopen/components @thatopen/components-front web-ifc three
重要警告
许可证历史警告:@thatopen 软件包(v3.x)采用 MIT 许可证。不过,较早的 IFC.js 生态系统软件包(openbim-components v1、IFC.js v0.x)采用 AGPL-3.0 许可证。务必核实所安装的确切软件包版本的许可证。如果使用任何采用 AGPL-3.0 许可证的软件包,整个应用程序都必须以 AGPL-3.0 或兼容许可证开源。
切勿使用 web-ifc-three——它已弃用且无人维护。请改用 web-ifc 或 @thatopen/components。
切勿使用 openbim-components(v1)——它已弃用。请改用 @thatopen/components(v3.x)。
切勿一次性加载超过 50MB 文件中的全部 IFC 几何体——务必采用流式处理或按需加载几何体。WASM 内存有限,且无法回收,除非重新加载页面。
切勿在处理完成后忘记调用 ifcApi.CloseModel(modelID)——WASM 内存泄漏将持续存在,直到重新加载页面。
卸载 @thatopen 查看器时,务必调用 components.dispose()——否则会造成 GPU 内存、Three.js 对象和事件侦听器泄漏。
务必异步初始化 IfcAPI——在执行任何模型操作之前,必须先完成 await ifcApi.Init()。
方法 1:web-ifc(底层 WASM 解析器)
适用场景
- 需要完全控制几何体提取和渲染
- 正在构建自定义渲染流水线
- 只想使用 MIT 许可证的代码
- 需要提取 IFC 属性,但不渲染几何体
WASM 设置
import * as WebIFC from 'web-ifc';
const ifcApi = new WebIFC.IfcAPI();
ifcApi.SetWasmPath('./'); // MUST point to directory containing web-ifc.wasm
await ifcApi.Init(); // ALWAYS await before any operations
WASM 文件要求:web-ifc.wasm 文件必须从浏览器可访问的位置提供。将其从 node_modules/web-ifc/ 复制到 public/static 目录。Vite 等打包工具需要显式配置才能提供 WASM 文件。
IfcAPI 核心方法
| 方法 | 签名 | 说明 |
|---|---|---|
Init() | () => Promise<void> | 初始化 WASM 模块 |
SetWasmPath(path) | (string) => void | 设置包含 .wasm 文件的目录 |
OpenModel(data, settings?) | (Uint8Array, object?) => number | 加载 IFC 数据,返回 modelID |
CloseModel(modelID) | (number) => void | 释放模型内存 |
GetGeometry(modelID, expressID) | (number, number) => object | 获取元素的几何体 |
GetFlatMesh(modelID, expressID) | (number, number) => FlatMesh | 获取扁平化网格数据 |
GetPlacedGeometry(modelID, pg) | (number, PlacedGeometry) => MeshData | 获取带变换的已定位几何体 |
GetLine(modelID, expressID, flatten?) | (number, number, boolean?) => object | 按 expressID 读取单个 IFC 实体 |
GetAllLines(modelID) | (number) => number[] | 获取模型中的所有 express ID |
GetLineIDsWithType(modelID, type) | (number, number) => number[] | 按 IFC 类型常量获取 ID |
GetAllTypesOfModel(modelID) | (number) => TypeInfo[] | 列出模型中的所有实体类型 |
几何体提取模式
const modelID = ifcApi.OpenModel(ifcData); // ifcData: Uint8Array
const wallIDs = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCWALL);
for (const wallID of wallIDs) {
const flatMesh = ifcApi.GetFlatMesh(modelID, wallID);
for (const pg of flatMesh.geometries) {
const meshData = ifcApi.GetPlacedGeometry(modelID, pg);
// meshData.vertexData: Float32Array (interleaved position + normal, 6 floats per vertex)
// meshData.indexData: Uint32Array (triangle indices)
// pg.flatTransformation: Float64Array (4x4 column-major matrix)
// pg.color: { x: r, y: g, z: b, w: alpha } (0-1 range)
}
}
ifcApi.CloseModel(modelID); // ALWAYS release when done
将 web-ifc 几何体转换为 Three.js
import * as THREE from 'three';
function ifcMeshToThree(meshData, placedGeometry) {
const { vertexData, indexData } = meshData;
// vertexData is interleaved: [px, py, pz, nx, ny, nz, px, py, pz, nx, ny, nz, ...]
const posFloats = new Float32Array(vertexData.length / 2);
const normFloats = new Float32Array(vertexData.length / 2);
for (let i = 0; i < vertexData.length; i += 6) {
const j = i / 2;
posFloats[j] = vertexData[i];
posFloats[j + 1] = vertexData[i + 1];
posFloats[j + 2] = vertexData[i + 2];
normFloats[j] = vertexData[i + 3];
normFloats[j + 1] = vertexData[i + 4];
normFloats[j + 2] = vertexData[i + 5];
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(posFloats, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(normFloats, 3));
geometry.setIndex(new THREE.BufferAttribute(indexData, 1));
const { x: r, y: g, z: b, w: a } = placedGeometry.color;
const material = new THREE.MeshPhongMaterial({
color: new THREE.Color(r, g, b),
opacity: a,
transparent: a < 1,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
// Apply the 4x4 transform matrix (column-major)
const mat = new THREE.Matrix4();
mat.fromArray(placedGeometry.flatTransformation);
mesh.applyMatrix4(mat);
return mesh;
}
IFC 属性提取
// Read a specific entity
const wall = ifcApi.GetLine(modelID, wallExpressID, true); // flatten=true resolves references
console.log(wall.Name?.value); // "Basic Wall:Generic - 200mm"
console.log(wall.GlobalId?.value); // IFC GUID
// Get all property sets for an element
const relDefines = ifcApi.GetLineIDsWithType(modelID, WebIFC.IFCRELDEFINESBYPROPERTIES);
for (const relID of relDefines) {
const rel = ifcApi.GetLine(modelID, relID, false);
// Check if this relation references our element
// rel.RelatedObjects contains expressIDs of related elements
// rel.RelatingPropertyDefinition points to the property set
}
IFC 类型常量
| 常量 | IFC 实体 |
|---|---|
WebIFC.IFCWALL | 墙体 |
WebIFC.IFCWALLSTANDARDCASE | 标准墙体 |
WebIFC.IFCSLAB | 楼板/地板 |
WebIFC.IFCCOLUMN | 柱 |
WebIFC.IFCBEAM | 梁 |
WebIFC.IFCDOOR | 门 |
WebIFC.IFCWINDOW | 窗户 |
WebIFC.IFCROOF | 屋顶 |
WebIFC.IFCSTAIR | 楼梯 |
WebIFC.IFCSPACE | 空间/房间 |
WebIFC.IFCSITE | 场地 |
WebIFC.IFCBUILDING | 建筑 |
WebIFC.IFCBUILDINGSTOREY | 楼层 |
WebIFC.IFCPROJECT | 项目根节点 |
WebIFC.IFCRELDEFINESBYPROPERTIES | 属性集关系 |
WebIFC.IFCPROPERTYSET | 属性集 |
WebIFC.IFCPROPERTYSINGLEVALUE | 单个属性值 |
方案 2:@thatopen/components(高级 BIM 工具包)
适用场景
- 需要快速获得功能齐全的 BIM 查看器
- 希望内置片段优化,以处理大型模型
- 需要元素高亮、剖切平面或楼层平面图
- 项目可以接受 MIT 许可证(v3.x)
架构设置
import * as OBC from '@thatopen/components';
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create();
// Scene
world.scene = new OBC.SimpleScene(components);
world.scene.setup(); // Adds default lights and grid
// Camera
world.camera = new OBC.SimpleCamera(components);
world.camera.controls.setLookAt(10, 10, 10, 0, 0, 0);
// Renderer (container is an HTMLDivElement)
world.renderer = new OBC.SimpleRenderer(components, container);
// ALWAYS call init after setup
components.init();
加载 IFC 文件
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup(); // Downloads and initializes WASM
// Load from file input
const file = event.target.files[0];
const data = new Uint8Array(await file.arrayBuffer());
const model = await ifcLoader.load(data);
// model is added to the active world automatically
// model contains Three.js Group with fragment meshes
主要组件
| 组件 | 访问方式 | 用途 |
|---|---|---|
OBC.Components | new OBC.Components() | 所有子系统的中央管理器 |
OBC.Worlds | components.get(OBC.Worlds) | 多世界环境管理 |
OBC.SimpleScene | 构造函数 | 带默认设置的 Three.js 场景封装 |
OBC.SimpleCamera | 构造函数 | 内置轨道控制的相机 |
OBC.SimpleRenderer | 构造函数 | 绑定到 DOM 元素的 WebGL 渲染器 |
OBC.IfcLoader | components.get(OBC.IfcLoader) | IFC 文件加载和片段转换 |
OBC.FragmentsManager | components.get(OBC.FragmentsManager) | 高效的批量几何体管理 |
OBC.Highlighter | components.get(OBC.Highlighter) | 元素选择和高亮 |
OBC.Clipper | components.get(OBC.Clipper) | 剖切平面工具 |
OBC.Plans | components.get(OBC.Plans) | 楼层平面图生成 |
片段系统
片段系统会将 IFC 几何体转换为批量绘制调用。这对于大型 BIM 模型(10,000 个以上元素)的性能至关重要。
- 每种 IFC 类型都会批量处理为单个片段网格
- 片段尽可能共用材质,从而尽量减少状态更改
- 仍可通过 expressID 选取、高亮和隐藏单个元素
- 对于包含 1,000 个以上元素的模型,务必使用片段
清理
// ALWAYS dispose when unmounting the viewer
components.dispose();
// This releases: Three.js scene, renderer, geometries, materials, textures,
// WASM memory, event listeners, and all component state
IFC 空间树
IFC 文件遵循分层空间结构:
IFCPROJECT
└── IFCSITE
└── IFCBUILDING
├── IFCBUILDINGSTOREY (Level 0)
│ ├── IFCWALL
│ ├── IFCSLAB
│ └── IFCSPACE
└── IFCBUILDINGSTOREY (Level 1)
├── IFCWALL
├── IFCCOLUMN
└── IFCDOOR
空间包含关系由 IFCRELCONTAINEDINSPATIALSTRUCTURE 和 IFCRELAGGREGATES 关系定义。务必遍历这些关系以构建可导航的树结构——不要认为扁平的元素列表能代表建筑结构。
大型 IFC 文件的内存管理
| 文件大小 | 策略 |
|---|---|
| <10MB | 完整加载,一次性渲染所有几何体 |
| 10-50MB | 完整加载,使用片段批处理 |
| 50-200MB | 按楼层或类型流式加载几何体,并释放未使用的资源 |
| >200MB | 在服务端预处理为片段,按需加载 |
规则
- 务必监控 WASM 堆使用情况——web-ifc 会在 WASM 线性内存中分配内存,且该内存有硬性上限
- 务必释放当前不可见的几何体(屏幕外楼层、隐藏类型)
- 不要在 WASM 堆和 JavaScript 堆中同时保留重复几何体
- 务必使用
BufferGeometry(不要使用旧版Geometry) - 从场景中移除网格时,务必调用
geometry.dispose()和material.dispose()
参考链接
- references/methods.md -- web-ifc 和 @thatopen/components 的完整 API 签名
- references/examples.md -- IFC 加载和查看的可运行代码示例
- references/anti-patterns.md -- 在 Three.js 中处理 IFC 时要避免的做法