IFC 建筑模型转 Pascal 场景图:ifc-converter 的架构、转换流程与实战指南
2026/9/12 8:41:41 网站建设 项目流程

IFC 建筑模型转 Pascal 场景图:ifc-converter 的架构、转换流程与实战指南

【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editor

导读

本文围绕apps/ifc-converter这个 Web 应用展开,讲解如何把 IFC(Industry Foundation Classes)建筑模型转换为 Pascal 场景图 JSON,并在真实@pascal-app/viewer中预览。读完本文,你将掌握:转换器前端应用与纯逻辑包的职责划分、IFC 到 Pascal 节点的一一映射规则(墙/板/门/窗/楼梯/屋顶/柱)、convertIfcToPascal的调用方式与转换选项、WASM 资源与示例模型的加载机制,以及当前已知的转换局限与可参与的改进方向。

项目定位:从 IFC 到 Pascal 场景图的桥梁

apps/ifc-converter/README.md对它的定义非常清晰:这是一个把 IFC 建筑模型转换为 Pascal 场景图 JSON、并在真实@pascal-app/viewer中预览结果的 Web 应用。用户拖入一个.ifc文件(或从内置示例中选择一个),即可检查提取出的元素,并下载 JSON 加载到 Pascal 编辑器中继续编辑。

整个方案被拆成两层,职责非常干净:

  • 纯转换逻辑包@pascal-app/ifc-converter(packages/ifc-converter):通过 web-ifc 解析 IFC,把元素映射到来自@pascal-app/core的 Pascal 节点 schema 上。无 DOM、无 React,可在任何 JS 运行时中调用。
  • Web 应用层(apps/ifc-converter):提供拖放上传区、示例选择器、元素搜索/过滤、3D 预览与 JSON 下载等 UI 能力。

这种“逻辑包 + 薄 UI”的架构意味着转换核心可以被 CLI、脚本、Agent 或其他宿主环境直接复用,而不被浏览器绑定。

转换器整体工作流

从源码看,convertIfcToPascal(packages/ifc-converter/src/index.ts)是核心入口,整个转换过程按进度回调分为如下阶段:

进度阶段说明
0%初始化创建WebIFC.IfcAPI实例并调用Init()加载 WASM
10%打开模型OpenModel(ifcData)载入 Uint8Array 形式的 IFC 字节
20%空间关系分析读取IFCRELAGGREGATESIFCRELCONTAINEDINSPATIALSTRUCTURE,构建父子关系映射;探测长度单位换算因子;计算场景原点偏移
30%SiteIFCSITEsite节点
40%BuildingIFCBUILDINGbuilding节点
50%LevelIFCBUILDINGSTOREYlevel节点(含标高解析)
60%WallIFCWALL/IFCWALLSTANDARDCASEwall节点(含轴线、厚度、高度提取)
Door / Window通过 void/fill 关系链挂载到墙;缺失关系时按最近墙投影回退
SlabIFCSLABslab节点(轮廓多边形 + 标高)
Stair / RoofIFCSTAIR/IFCROOF→ 占位节点(包围盒/扁平多边形进 metadata)
ColumnIFCCOLUMN/IFCCOLUMNSTANDARDCASEcolumn节点(含截面形状识别)
Beam / Item计数后跳过(见下文局限)
后处理解析levelId、属性集、材质、类型名(IFCRELDEFINESBYPROPERTIESIFCRELASSOCIATESMATERIALIFCRELDEFINESBYTYPE
94%简化simplifyConvertedSceneGraph合并墙体碎片、去重洞口
95%收尾CloseModel、构建PascalSceneGraph返回

最终返回的结构为:

interface PascalSceneGraph { nodes: Record<AnyNodeId, AnyNode> rootNodeIds: AnyNodeId[] collections?: Record<string, unknown> }

nodes是 id 到节点的扁平映射,rootNodeIds记录顶层节点(site),层级关系通过每个节点的parentIdchildren表达。

单位换算与坐标变换:转换的第一步

IFC 文件可能使用毫米、英寸、英尺甚至自定义换算单位,而 Pascal 场景统一以米为单位。getLengthUnitFactor(packages/ifc-converter/src/index.ts)负责从IFCPROJECT.UnitsInContext中解析LENGTHUNIT

  • 米制前缀(MILLI=1e-3、CENTI=1e-2、KILO=1e3 等)直接换算;
  • 英制(FOOT/FEET=0.3048、INCH=0.0254)写死换算;
  • ConversionBasedUnit则读取ConversionFactor.ValueComponent作为因子;
  • 解析失败时返回 1,视为单位即米。

坐标变换方面,转换器实现了一套 4×4 矩阵栈(identity/multiply/transformPoint3/buildAxis2Placement3DMatrix),resolveWorldTransform会沿着PlacementRelTo链从根到叶逐级乘算,把每个元素的局部ObjectPlacement解析为世界矩阵。此外,为了把地理参考模型居中到原点附近,转换器以第一个IFCSITE的放置原点作为originOffset,所有世界坐标经worldToScene减去该偏移并乘以unitFactor后写入场景。

各元素类型的映射规则

Site / Building / Level

  • IFCSITEsite节点,直接作为rootNodeIds根。转换器目前不读取 IFC 场地几何,而是注入 PascalSiteNode要求的默认 30×30 属性线多边形(源码中标注了TODO(ifc-fix): derive from IfcSite.SiteAddress or building footprints.)。
  • IFCBUILDINGbuilding节点,父节点为空间层级中的 site。
  • IFCBUILDINGSTOREYlevel节点。标高优先从放置链解析(resolveWorldTransform后取 Z),失败时回退到storey.Elevation * unitFactor,并写入 metadata 的elevation字段。

Wall(墙)

墙是转换的核心难点,处理流程为:

  1. Representation中寻找RepresentationIdentifier === 'Axis'的表示,读取IFCPOLYLINE/IFCINDEXEDPOLYCURVE/IFCGEOMETRICSET得到轴线点列,从而确定start/end
  2. Body表示中读取IfcExtrudedAreaSolidDepth(按选项解释为高度或厚度)、剖面XDim/YDim(矩形剖面)或Radius(圆形剖面),并支持沿BooleanClippingResult.FirstOperand链解包和MappedRepresentation展开;
  3. 若高度/厚度仍缺失(常见于普通IFCWALL携带 Brep/映射几何的情况),调用measureWallLocalExtentsGetFlatMesh读取网格顶点,并把顶点投影到墙体自身轴线上测量沿轴/垂直/竖直三个方向的跨度——源码注释特别强调,投影到真实轴线是旋转不变的,而世界空间 AABB 会把旋转墙的长与厚混为一谈;
  4. 最后仍未解析出数值时,回退到@pascal-app/core的默认值DEFAULT_WALL_HEIGHT = 2.5DEFAULT_WALL_THICKNESS = 0.1(见 packages/core/src/systems/wall/wall-footprint.ts)——这正是 README 所述“walls that default to a fixed height”的实现位置;
  5. 无法确定end的墙会被直接跳过。

Door / Window(门与窗)

门与窗的处理体现了 IFC 关系链的完整利用:

  • 首选路径(void/fill 关系链):通过IFCRELVOIDSELEMENT(墙→洞口)与IFCRELFILLSELEMENT(洞口→填充元素)建立wall → opening → fill的映射。填充元素的OverallWidth/OverallHeight提供宽高;洞口在世界空间的位置投影到墙轴线,得到position(沿墙距离),并进行钳制避免 CSG 开洞溢出墙体外;窗还会从洞口放置 Z 与墙基 Z 的差计算sillHeight(暂存于 metadata)。
  • 回退路径(最近墙投影):部分导出器(如巴黎示例)只写 void 不写 fill,因此转换器把所有未关联的门窗按世界位置投影到最近的墙段(HOST_WALL_MAX_DIST = 1.0米内),并优先选择能容纳洞口宽度的墙,避免把小门吸附到墙角碎墙上导致 CSG 溢出。既无关系链又找不到宿主墙的元素则挂回其空间容器。

每个门窗通过expressIdToNodeId去重,确保同一填充元素只生成一个节点;tests/openings.test.ts(packages/ifc-converter/tests/openings.test.ts)专门用04-ifc-open-house.ifc断言 6 个填充元素在“关系重复出现”的恶意输入下仍恰好各生成一次,且父墙的children无重复。

Slab / Stair / Roof / Column

  • IFCSLABslab节点:从Body剖面轮廓点(或XDim/YDim矩形)生成多边形,去掉首尾重合点,标高取放置 Z;厚度暂存 metadata(PascalSlabNode尚无thickness字段)。
  • IFCSTAIRstair节点:优先取自身 Body 包围盒;失败则遍历楼梯段子元素,利用NumberOfRisers/RiserHeight/TreadLength估算总高与总进深;均失败时保留原点占位。包围盒信息存放在 metadata(PascalStairNode是参数化楼梯,转换器尚未映射)。
  • IFCROOFroof节点:提取扁平多边形与高度进 metadata(PascalRoofNode由 roof-segments 组成,目前仅作占位)。
  • IFCCOLUMNcolumn节点:通过剖面字段检测round/rectangular形状,剥除装饰性默认样式(style: 'plain'baseStyle: 'none'capitalStyle: 'none'),用 IFC 剖面的宽/深/半径或宽深比决定crossSection

Beam / Item:当前被跳过的类型

IFCBEAM(及IFCBEAMSTANDARDCASE)因为 Pascal 还没有beam节点类型而被跳过,仅统计数量并在控制台告警。IFCFURNISHINGELEMENTIFCBUILDINGELEMENTPROXYIFCRAILINGIFCCOVERINGIFCCURTAINWALLIFCPLATEIFCMEMBERIFCFOOTING等家具/构件类实体同理——Pascal 的ItemNode需要一个包含 id/src/dimensions 等信息的目录资产(catalog asset),转换器无法从裸 IFC 几何合成,因此也仅计数跳过。

后处理:属性集、材质与类型名

转换完成后,转换器把 IFC 的语义信息尽力保留进节点的metadata(类型定义为ConverterMetadata):

  • 属性集:遍历IFCRELDEFINESBYPROPERTIES,把每个属性集(Pset_*)下的属性(IFCPROPERTYSINGLEVALUENominalValue)与工程量(LengthValue/AreaValue/VolumeValue/WeightValue/CountValue)写入metadata.properties[psetName]
  • 材质:遍历IFCRELASSOCIATESMATERIAL,支持IfcMaterialLayerSet(层名 +LayerThickness×unitFactor 换算为米)、IfcMaterialLayerSetUsage、单材质三种情况,写入metadata.materialmetadata.materialLayers
  • 类型名:遍历IFCRELDEFINESBYTYPE,把类型对象的Name写入metadata.typeName
  • 楼层归属:沿父子链上溯为每个元素解析所属 storey,写入metadata.levelId

UI 层的元素搜索(apps/ifc-converter/components/IfcConverter.tsx)正是靠这些 metadata 实现的——按名称、类型、IFC 类型、材质、GlobalId、属性键值多维度匹配,点击搜索结果还会在 3D 视图中选中对应节点并弹出属性面板。

场景简化:清理转换产物

IFC 中的墙经常被切成大量共线碎片,导致转换结果碎片化。simplifyConvertedSceneGraph(packages/ifc-converter/src/cleanup.ts)按顺序执行四步:

  1. 剔除微小墙:长度小于MIN_WALL_LENGTH = 0.08米且无子元素的墙删除;
  2. 合并共线墙碎片:按“父节点 + 角度桶(1° 粒度)+ 中心线偏移 + 高度 + 材质签名”分组判并查集合并。材质签名会序列化materialmaterialLayers,因此不同材质的墙绝不合并(tests/cleanup.test.ts中多组用例专门验证了这一点);合并时把门/窗重投影(rehostOpeningToWall)到保留墙上,并把被合并墙的expressID记入metadata.ifcSimplification
  3. 同步洞口子级:确保每个门/窗在宿主墙的children中恰好出现一次;
  4. 去重洞口:对同一墙上的门/窗按类型、家族、位置(0.05 米容差)、宽高生成签名,删除重复项。

合并可跨“门洞大小”的间隙(默认maxWallJoinGap = 1.25米,可通过IfcConversionSimplificationOptions调整)。每次简化都会返回一份IfcConversionSimplificationStats统计,包括输入/输出墙体数、合并组数、删除的微墙与重复洞口数。

调用方式与转换选项

convertIfcToPascal的完整签名如下(见 packages/ifc-converter/src/index.ts):

export interface ConversionOptions { swapYZ?: boolean // 默认 true:Y-up 坐标(Pascal 约定),把 IFC 的 Z-up 交换到 Y extrusionDepthIsHeight?: boolean // 默认 true:拉伸深度解释为高度(而非厚度) swapProfileDimensions?: boolean // 默认 false:是否交换剖面的 XDim/YDim 解释 simplify?: boolean | IfcConversionSimplificationOptions // 默认启用场景简化 label?: string } export async function convertIfcToPascal( ifcData: Uint8Array, onProgress?: (message: string, percent: number) => void, options?: ConversionOptions, ): Promise<PascalSceneGraph>

仓库预置了两个变体预设:

  • VARIANT_PRESETS.AswapYZ: trueextrusionDepthIsHeight: true,标签 “Default (Y-up, depth=height)”——默认形态;
  • VARIANT_PRESETS.BswapYZ: false,标签 “Z-Up (no axis swap)”——保留 IFC 原始 Z-up 坐标。

需要说明的是,IfcExtrudedAreaSolid的深度语义在 IFC 中本意是拉伸厚度,而 Pascal 墙以“高度”为第一属性,因此默认把深度当高度使用;swapProfileDimensions则解决某些导出器中剖面 XDim/YDim 与墙长/厚约定相反的情况。进度回调接收(message, percent)二元组,UI 层用它渲染进度条。

在纯 Node 环境中调用时,注意 web-ifc 需要 WASM,测试(如 packages/ifc-converter/tests/openings.test.ts)通过拦截SetWasmPath指向node_modules中 web-ifc 包的目录来完成初始化。

Web 应用层:上传、预览与下载

依赖与资源加载

apps/ifc-converter/package.json声明了转换应用,核心依赖为@pascal-app/ifc-converter@pascal-app/core(scene registry 与节点 schema)与@pascal-app/viewer(3D 渲染)。web-ifc 的 WASM 二进制(web-ifc.wasmweb-ifc-mt.wasmweb-ifc-node.wasm)由 apps/ifc-converter/scripts/copy-web-ifc-wasm.mjs 在postinstall/predev/prebuild时自动复制到public/(按大小幂等,跳过已存在文件),因为 web-ifc 默认从应用根路径加载/web-ifc*.wasm

开发与运行

bun dev # 在 apps/ifc-converter 目录下运行 # 或在仓库根目录:turbo run dev

由于查看器使用 three 的 WebGPU 渲染器与基于 registry 的场景 store,两者都不适合 SSR,因此PascalViewer通过 Next.js 的dynamic(..., { ssr: false })懒加载(见 apps/ifc-converter/components/IfcConverter.tsx)。

示例文件与环境变量

小体积示例 IFC 已提交到apps/ifc-converter/public/test-ifc-files/(如01-duplex.ifc04-ifc-open-house.ifc05-paris-ground-floor.ifc10-sample-house.ifc),大体积示例(几十 MB 级,如 Schependomlaan、RAC Sample Project、Sample Castle 等)则托管在公开的 Supabase Storage bucket,运行时按需拉取,避免仓库臃肿。示例清单与说明见 apps/ifc-converter/lib/test-files.ts。

环境变量NEXT_PUBLIC_IFC_EXAMPLES_BASE_URL可覆盖远程示例的存储桶地址;置为空字符串会隐藏全部远程示例(避免未配置环境时出现 404 卡片)。应用还支持?file=<name>查询参数直达某个示例并自动滚动到转换区,且会把选择同步回 URL(history.replaceState)。

交互与导出

转换完成后,用户可:

  • 通过左侧 3D 预览(Orbit 左键旋转 / 右键平移 / 滚轮缩放)检查结果,点击元素弹出属性面板(类型、IFC 类型、GlobalId、ExpressID、所在楼层、几何数值、材质层与属性集);
  • 用类型过滤(wall/slab/door/window/stair/roof/column)与楼层过滤按需显隐,用搜索框跨名称/类型/材质/属性检索;
  • 下载 Pascal JSON(${原文件名去掉.ifc}_pascal.json)、下载原始 IFC,或复制 JSON 到剪贴板,随后把 JSON 加载进 Pascal 编辑器继续设计。

预览组件 apps/ifc-converter/components/PascalSceneViewer.tsx 直接把场景推入useScenestore,并用真实@pascal-app/viewerViewer+CameraControls渲染;AutoFit会延迟两帧等待墙体斜接、楼板构建等逐帧几何系统稳定后再计算包围盒取景,LevelFocus则在切换楼层时沿 Y 轴平移相机目标。

已知局限与改进方向(README 明确列出)

README 以“Early alpha”诚实标注了当前边界,这些局限也都能在源码中找到对应位置:

  1. 普通IFCWALL(Brep/映射几何)回退到固定高度:精确的逐墙高度需要基于几何 AABB 提取。源码中measureWallLocalExtents+wallHeightThicknessFromExtents已部分弥补——只要墙自身网格可读,就会用旋转不变的轴向投影恢复真实高/厚,只有完全失败才落到DEFAULT_WALL_HEIGHT(2.5 米)/DEFAULT_WALL_THICKNESS(0.1 米);
  2. 家具等物品被跳过:Pascal 的ItemNode需要目录资产(catalog asset),转换器无法从裸 IFC 几何合成,因此IFCFURNISHINGELEMENT等实体仅计数并告警;
  3. 梁被跳过:Pascal 尚无beam节点类型(源码中保留了恢复映射的注释说明,轴点列→start/end、剖面 XDim/YDim→宽/深、拉伸深度→轴线长度的映射思路);
  4. 门/窗与墙的匹配:当 IFC 省略 fill 关系时按 1 米范围内的最近墙投影匹配,匹配并非完美;
  5. 楼梯/屋顶为占位:目前只在 metadata 中保留包围盒(楼梯)或扁平多边形 + 高度(屋顶),尚未映射到 Pascal 的参数化StairNode/ 分段式RoofNode

正因如此,README 明确欢迎贡献:遇到转换效果差的文件,提供“示例 IFC + 问题说明”最有帮助;改进几何提取、补充更多元素类型、处理边界情况的 PR 正是这个项目最需要的。

结语

apps/ifc-converter是一个将“标准解析能力”与“领域场景模型”结合的完整示例:web-ifc 负责 IFC 的复杂解析,@pascal-app/core提供 Pascal 节点 schema 与 registry,@pascal-app/viewer提供渲染,转换器本体则专注于单位换算、坐标变换、关系链解析与几何提取这一层纯逻辑。无论你想把 IFC 导入 Pascal 编辑器继续设计,还是想在自有管线中复用@pascal-app/ifc-converter的转换能力,本文梳理的映射规则、选项语义与局限边界都可以作为直接参考。

【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editor

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询