IFC Viewer SDK v1.18.0 打开查看器
中文

IFC Viewer SDK · v1.18.0

将 IFC 查看器嵌入您自己的产品

将同样的浏览器查看器嵌入您的 CDE、数字孪生或内部项目工具。直接从您的应用加载 IFC 数据,每个模型都在客户端解析。

三行代码即可首次渲染

JavaScript
const viewer = new IfcViewer("#viewer");

await viewer.add("project.ifc", ifcBytes);
// → rendered, validated, all on the client

接入

快速开始

选择你的技术栈。复制、粘贴、渲染——无需构建步骤,无需 API key。

HTML
<div id="viewer" style="height:520px"></div>
<script type="module">
  import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";

  const viewer = new IfcViewer("#viewer");

  // Load IFC bytes from your app — nothing is uploaded
  const bytes = await fetch("/models/project.ifc").then(r => r.arrayBuffer());
  await viewer.add("project.ifc", bytes);
</script>

使用我们的托管或自行托管

示例代码从我们的 CDN 导入,可在任意源上使用。若要自行托管,从你自己的域名提供 ifc-viewer.es.js —— SDK 会相对脚本自动推断应用 URL。应用部署在别处?设置 baseUrl 选项。

暂无 npm 包:按 URL 导入模块(见上),或把 ifc-viewer.es.js 内置到你的应用中。

实证

在线演示

本页面正在运行该 SDK。点击控件以获取示例 IFC 并驱动查看器——每个按钮都会显示它所调用的方法。

空闲
最近一次调用 —

架构

工作原理

你的页面里只有一个极小的封装,完整查看器运行在沙箱 iframe 中,你的字节在两者之间传递——并在它们原本所在的地方完成解析。

访客浏览器 —— 不上传任何内容你的应用SDK · 约 6 KB沙箱 iframethree.js · web-ifc · WASMIFC 字节postMessage

你的应用

一个约 6 KB 的 ES 模块,创建 iframe 并提供带类型的 JS API。3D 与解析的全部重量都不会进入你的打包产物。

postMessage 桥接

IFC 字节以 ArrayBuffer 形式转移到 iframe(零拷贝)。命令与查询是相互关联的请求/响应消息。

查看器

three.js 加上编译为 WebAssembly 的 web-ifc,在 iframe 内解析并渲染模型——就在访客的机器上。

为什么它是私密的

模型永远不会到达服务器。没有需要加固的上传链路,没有数据驻留问题,也没有按文件大小排队。

为什么它能扩展

计算在每位访客的设备上进行。分发 200 MB 的模型与 2 MB 的成本相同——它只是一个静态文件。

概念

核心概念

动手前需要了解的四件事。

挂载与就绪

new IfcViewer(target) 会立即挂载,并在查看器就绪前把命令排队。await IfcViewer.create(target)(或 whenReady())返回一个可直接使用的实例。

JavaScript
// queues commands until ready
const viewer = new IfcViewer("#viewer");

// …or await an instance that is ready to use
const viewer = await IfcViewer.create("#viewer");

字节还是 URL

add(name, bytes) 传输你已经持有的字节——无需 CORS,不上传任何内容;buffer 会被转移(变为 detached),如果之后还要用请传入副本。addFromUrl(url) 则去获取一个启用了 CORS 的公开 URL。

JavaScript
// you already have the bytes — no CORS, nothing uploaded
await viewer.add("a.ifc", bytes);

// …or load a public, CORS-enabled URL
await viewer.addFromUrl("https://host/b.ifc");

事件

用 on(event, cb) 订阅,它会返回一个取消订阅的函数。可用事件有 ready、model-progress、model-loaded、validation-completed、model-error 和 element-selected。

JavaScript
const off = viewer.on("model-loaded", (m) => {
  console.log(m.fileName, m.elementCount);
});
// later: off();

查询

按需取数:getModels、getElement、getValidation、getStats、getIssues、checkIds、checkEir、screenshot。每个都是请求/响应,并在超时后拒绝(30 秒;IDS / EIR 最长 120 秒)。

JavaScript
const models = await viewer.getModels();
const { qualityScore } = await viewer.getValidation();
const png = await viewer.screenshot();

参考

API 参考

全部方法,按组列出——含签名与返回类型。随后是构造函数选项、事件以及 <ifc-viewer> web component。

构造函数

new IfcViewer(target, options?)IfcViewer

挂载到 CSS 选择器或元素。参见下方选项。

IfcViewer.create(target, options?)Promise<IfcViewer>

创建查看器,并在其可接受命令时兑现。

加载模型

add(name, bytes)Promise<ModelLoadedEvent>

从您的应用加载 IFC 字节(ArrayBuffer/Uint8Array)。缓冲区以零拷贝方式转移。

addFromUrl(url, name?)Promise<ModelLoadedEvent>

加载一个启用 CORS 的公开 IFC URL。

removeModel(modelId)void

按 id 卸载某个模型。

clear()void

移除所有已加载的模型。

相机与选择

select(expressId | globalId, modelId?)void

选中并框选构件 —— 按 IFC expressID,或按 GlobalId(同时找到其所属模型)。

setView(view)void

飞到场景的命名视图 —— iso、front、back、left、right、top、bottom —— 并让整个场景入画。无返回值(1.14 至 1.17 期间查看器会忽略它)。

frame({ view?, fill?, azimuth?, elevation?, animate? })Promise<{ scope }>

用于展示的取景:模型占画面的 `fill`(0.2–0.98,默认 0.85),按包围盒角点而非包围球拟合。azimuth / elevation 可从任意角度观察。

frame(elementId, modelId?, options?)Promise<{ scope: 'element' }>

框选单个构件(expressID):未指定视图时保持当前角度。构件不在模型中时拒绝。

fit() · reset()void

从当前角度带边距地框选活动模型 / 重置相机。

setCamera(position, direction)void

将相机置于某位置并朝某方向观察。

getCamera()Promise<CameraState | null>

相机的位置与朝向——保存下来,再用 lookAt 恢复。

lookAt(position, target, animate?)Promise<void>

将相机飞到某个位置并看向目标(场景米,Y 轴向上)。

setWalkMode(enabled, { speed? })Promise<WalkState>

第一人称漫游模式(WASD 移动,拖动环顾,Esc 退出)。触发 walk-changed。

getWalkState()Promise<WalkState>

漫游模式是否开启,以及速度。

可见性

isolate(ifcType?)void

隔离一个类别(如 "IfcWall");不传参则清除。

showAll()void

恢复全部可见性(清除隐藏与隔离)。

hideElements(ids, modelId?) · showElements(ids, modelId?)void

按 expressID 隐藏 / 显示一组元素。

setModelVisible(modelId, visible)Promise<void>

在联合场景中显示或隐藏某个模型,而不卸载它。

setModelOpacity(opacity, modelId?)Promise<void>

使模型半透明(0.05–1)——例如机电周围的建筑。

isolateModel(modelId | null)Promise<void>

仅显示此模型;传 null 恢复全部显示。

外观与展示

setBackground(background)Promise<BackgroundState>

更改场景背景:预设('white'、'paper'、'blueprint'、'sky'、'studio')、'#rrggbb'、'#上,#下' 或 { top, bottom }。不会保存为访客自己的偏好。

getBackground()Promise<BackgroundState>

当前的场景背景。

setAccent(color)Promise<void>

运行时更改界面强调色(#rrggbb)。

setClientMode(enabled)Promise<void>

开启或关闭客户视图——面向相关方的展示视图,等同于 ui: 'client'。

setRenderQuality(quality)Promise<void>

'quality' 启用更重的渲染用于主视觉图;日常使用 'standard'。

日照分析

setSolar(opts?)Promise<SolarState>

在场地当地的日期和时间生成真实阴影:{ date: '06-21', time: '18:00' }。场地取自 IFC 地理参照;模型没有时请传入 location——绝不会静默使用默认城市。

getSolar()Promise<SolarState>

分析的日期、时间和时区,以及场地位置。

地图模式

setSiteContext(opts?)Promise<SiteContextState>

将模型连同地形和 OpenStreetMap 周边放到地图上。调用即表示你的页面代表其访客同意加载瓦片;请展示署名。

getSiteContext()Promise<SiteContextState>

地图模式状态、放置位置以及必须展示的署名。

场景

openScene(url | SceneDocument)Promise<void>

打开另一个场景:.scene.json 的 URL,或一个场景文档(它压缩后放在框架地址里传递,因此必须能放进链接)。查看器随之重新加载;等待中的调用会被拒绝,ready 会再次触发。

exportScene({ title?, description?, camera? })Promise<SceneExport>

将屏幕上的场景导出为场景文档——按 URL 引用的模型、图层、孪生、地图、相机——并附上需要注明的数据源和承载它的链接。

packScene(doc)Promise<string | null>

自行将场景文档打包成 #scene= 链接(过大无法放入链接时返回 null)。

数据图层与孪生

getLayerPresets()Promise<LayerPreset[]>

公共实时数据源目录,以查看器语言命名,并说明每个源在浏览器中需要什么(密钥、代理)以及在场地附近是否有数据。

addLayer({ preset } | { url, live? } | { geojson })Promise<DataLayerInfo>

添加图层:目录中的数据源({ preset })、某个 URL 上的 GeoJSON 或实时数据流({ url, live }),或你已有的 GeoJSON({ geojson })。仅限本次会话:不会保存到访问者的图层中。

getLayers()Promise<DataLayerInfo[]>

所有数据图层:状态、要素数量、范围、刷新和署名。

setLayerVisible(id, visible)Promise<DataLayerInfo>

显示或隐藏图层。

frameLayer(id)Promise<void>

将相机移到图层的要素上。

removeLayer(id)Promise<void>

移除图层。

getTwin()Promise<TwinState>

当前的运营孪生:设备数据源(状态、最近读数),以及每个绑定命中的规则、颜色、标签值和是否在告警。

剖切

addSection({ axis?, offset?, level?, flip? })Promise<SectionsState & { id }>

添加剖切面。{ level: '一层' } 是该楼层的平面图;{ axis: 'x', offset: 4.5 } 是 4.5 米处的剖面。

updateSection(id, { offset?, enabled?, flipped? })Promise<SectionsState>

移动、启用/停用或翻转剖切面。

removeSection(id?)Promise<SectionsState>

移除一个剖切面;不传 id 时移除全部剖切。

setSectionBox('model' | 'selection' | false)Promise<SectionsState>

围绕模型或选中构件的剖切盒;false 将其移除。

getSections()Promise<SectionsState>

所有剖切面、剖切盒,以及可用于按楼层剖切的楼层。

导览

startTour(template?, { autoplay?, title? })Promise<TourState>

启动内置导览:'social'、'client-walkthrough' 或 'technical-review'(逐一查看校验问题)。由查看器自带的导览栏播放。

playTour({ title?, steps }, { startAt?, autoplay? })Promise<TourState>

播放你编写的导览:相机停靠点,可带说明、高亮与隔离的类别。autoplay:true 或每站毫秒数。

nextTourStep() · prevTourStep() · goToTourStep(i)Promise<TourState>

在停靠点之间切换。

setTourAutoplay(autoplay)Promise<TourState>

为正在播放的导览开启或关闭自动前进。

stopTour()Promise<TourState>

停止导览并交还相机。

getTour()Promise<TourState>

导览及其停靠点,格式与 playTour() 相同——可保存后再次播放。

演示导演

getPresentationRecipes()Promise<PresentationRecipe[]>

内置导演配方(会议演示、短视频、LinkedIn 预告、发布风格、编辑风格…)。

createPresentation(recipe?, options?)Promise<PresentationState>

按配方把模型变成剪辑好的视频:镜头依据 IFC 规划,带字幕和音乐。会用结果打开 Clip Studio。通过 presentation-progress 跟踪进度。

exportPresentation({ resolution?, music? })Promise<PresentationVideo>

在访客浏览器中编码演示并返回 MP4 字节——可上传、附加或在 <video> 中播放。

closePresentation()Promise<void>

关闭 Clip Studio。

封面工作室

getCoverOptions()Promise<CoverCatalog>

封面工作室提供的配方、模板、格式和配色。

createCover({ recipe?, template?, format?, palette?, text? })Promise<CoverState>

生成封面:先运行配方(会拍摄所需视图),再设置模板、格式、配色和文字。会打开封面工作室,仍可编辑。

getCover()Promise<CoverState | null>

当前的封面;封面工作室关闭时为 null。

exportCover({ type?, slide? })Promise<CoverFile>

导出:png / jpeg(单页),pdf / pptx(整份文档),zip(全部页面)。返回字节。

closeCover()Promise<void>

关闭封面工作室。

文章套件

activate()void

立即启动延迟加载的查看器(海报按钮的作用)。之前的调用会排队执行。

bindSteps(steps, { rootMargin? })() => void

滚动叙事:每个段落经过屏幕中部时,取景、隔离、移动太阳或运行你的代码。返回解绑函数。

setTurntable(enabled | degPerSec)Promise<{ active, speed }>

空闲时缓慢旋转(度/秒;false 停止)。首次触碰即停止,减少动态效果时不运行。

toggleFullscreen()Promise<void>

将插图放大到全屏,或恢复。

setPaused(paused)Promise<{ paused }>

暂停或恢复渲染。pauseOffscreen 会自动处理。

场景分组

getGroups()Promise<SceneGroupsState>

场景中的所有分组——从 IFC 推断(项目、场地、位置)以及用户创建的分组。

createGroup(name, modelIds?)Promise<string>

创建分组,可同时放入模型。返回其 id。

renameGroup(id, name) · deleteGroup(id)Promise<void>

重命名或删除用户分组。自动分组不可编辑。

assignToGroup(itemId, groupId | null | 'loose')Promise<void>

将模型或点云移入分组;null 交还给自动分组,'loose' 表示不属于任何分组。

setGroupVisible(id, visible) · isolateGroup(id | null)Promise<void>

显示或隐藏整个分组,或只显示一个分组。

frameGroup(id)Promise<void>

将相机对准某个分组,包括隐藏的成员。

测量

setMeasureTool(tool)Promise<void>

为访客启用测量工具(会打开测量面板),或传 'none'。结果通过 measurements-changed 返回。

getMeasurements()Promise<MeasurementsState>

屏幕上的所有测量。数值始终为国际单位——米、平方米、度——与界面显示无关。

clearMeasurements(id?)Promise<MeasurementsState>

移除一个测量,或全部。

查询

getModels()Promise<ModelSummary[]>

列出已加载的模型。返回 Promise。

findElements({ ifcClass?, globalId?, name?, modelId?, limit? })Promise<ElementSummary[]>

在已加载的模型中按 IFC 类、GlobalId 和/或名称查找构件。每个结果都包含 getElement()、select() 和 frame() 所需的信息。

getElement(expressId | globalId, modelId?)Promise<IfcElementData | null>

按 expressID 或 GlobalId。构件的 IFC 数据:带 GlobalId 的属性、其属性集和工程量,以及其类型的数据 —— typeName、typeProperties —— 含单位。effectivePropertySets 合并两者,构件自身的值优先。

validate(modelId?, { force? })Promise<ValidationRunResult>

立即校验一个模型(默认活动模型),并以其 Health Score 和问题计数解析。失败时附带原因拒绝。

getValidation()Promise<ValidationSummary | null>

屏幕上的校验结果;在任何一次运行产生结果之前为 null。见“校验生命周期”。

getValidationStatus()Promise<ValidationStatus>

校验当前所处状态:idle、running(含进度)、done 或 error。

getStats()Promise<StatsResult>

按类别统计每个模型的元素数量——用于仪表盘图表。返回 Promise。

getIssues({ severity?, limit?, modelId? })Promise<IssuesResult>

用于表格的校验问题(按严重程度 / 数量过滤)。返回 Promise。

screenshot()Promise<string>

将当前三维视图捕获为 PNG data URL。返回 Promise。

compare({ base, head, baseLabel?, headLabel? })Promise<{ changes }>

按 URL 比较两次交付(按 GlobalId),并在结果上打开比较工作区。

面板

openPanel(panel | null) · closePanel()void

打开某个工具面板,或传入 null 关闭当前打开的面板。若面板不可用——被外壳隐藏,或没有可作用的已加载内容——则不执行任何操作,也不报错。

getPanels()Promise<PanelsResult>

当前打开的面板,以及此刻可供选择的面板。未出现在 `available` 中的工具无法打开,而不只是被禁用。

setPanels(panels)void

在运行时把工具栏限制为这些面板。与 URL 参数 `panels=` 使用同一套名称,并优先于它。只会收窄查看器提供的内容,绝不新增;空数组表示不显示工具栏。

点云

addPointCloud(fileName, bytes)Promise<string>

从字节加载扫描——LAS、LAZ、COPC、PLY、PCD 或文本。缓冲区是转移而非复制,返回点云 id。

addPointCloudFromUrl(url, fileName?)Promise<string>

加载由查看器自行获取的扫描。该 URL 必须允许 CORS。

listPointClouds()Promise<PointCloudInfo[]>

所有已加载的扫描。`pointCount` 是常驻内存的数量,`declaredCount` 是文件所含的数量——当预算截断解析时两者不同。

removePointCloud(cloudId) · clearPointClouds()Promise<void>

卸载一个扫描并释放其 GPU 缓冲区,或全部卸载。

setPointCloudVisible(cloudId, visible)Promise<void>

显示或隐藏一个扫描,而不卸载它。

fitPointCloud(cloudId?)Promise<void>

把相机对准某个扫描,或第一个加载的扫描。

setPointCloudDisplay(display, renderBudget?)Promise<void>

点大小、不透明度、着色模式、密度、置信度阈值——所有扫描共用。每一项都是着色器 uniform,因此在两千万点上也是即时生效。

inspectPointCloud(enabled?)Promise<void>

启用点击读取。启用后点击一个点会发出 `pointcloud-picked`,同时带上场景坐标和文件中的坐标。

setPointCloudPlacement(placement, cloudId?)Promise<void>

手动微调扫描:位置、偏航、调平、缩放。可传部分字段且会被钳制;叠加在推导出的对齐之上,而不是替换它。

setPointCloudUpAxis(axis, cloudId?)Promise<void>

修正扫描以哪个轴为上。PLY、PCD 和文本都不声明方向,由查看器推断——请先检查 `upAxisSource`。

Imported 3D models

addMesh(files)Promise<string>

从字节导入模型。需要一个列表:.gltf 需要它的 .bin 和贴图,.obj 需要它的 .mtl——只传入口文件会得到灰色几何体。

addMeshFromUrl(urls)Promise<string>

导入由查看器获取的模型。请传入它需要的全部 URL;它们会并行获取,且都必须允许 CORS。

listMeshes()Promise<MeshInfo[]>

所有已导入的模型,含三角面和贴图数量。在信任旁边的数值之前,请先读取 `unitSource` 和 `upAxisSource`。

removeMesh(meshId?) · clearMeshes()Promise<void>

移除一个导入并释放其几何体、材质和贴图,或全部移除。

setMeshVisible(visible, meshId?)Promise<void>

显示或隐藏一个导入,而不卸载它。

fitMesh(meshId?)Promise<void>

把相机对准某个导入,或全部导入。

setMeshPlacement(placement, meshId?)Promise<void>

手动放置导入:位置、偏航、调平、缩放。可传部分字段且会被钳制。导入初始居中于 IFC 之上、落在其地面。

setMeshUpAxis(axis, meshId?)Promise<void>

修正来源的垂直轴。仅对 OBJ 有意义——glTF 规范强制 Y 向上,因此它报告 `upAxisSource: 'declared'`。

setMeshUnit(unitScale, meshId?)Promise<void>

修正来源单位:1 米、0.01 厘米、0.001 毫米、0.3048 英尺。这些格式都不记录单位,查看器根据模型尺寸推断。

IDS 与 EIR 合规

checkIds(idsXml)Promise<IdsResult>

对照 buildingSMART IDS(.ids XML)检查模型。在 worker 中运行。按规格返回通过/未通过。

checkEir(profile)Promise<IdsResult>

按 EIR / BIM 校验配置检查模型——可传对象、其 JSON(含简写形式),或内置配置的 id,如 'builtin-en14351-1'。编译为 IDS,在同一引擎上运行:返回与 checkIds 相同的 IdsResult。

语言

setLanguage(lang)void

在运行时切换界面语言。

getLanguages()string[]

列出支持的语言代码(标签见 IfcViewer.LANGUAGES)。

生命周期与事件

whenReady()Promise<void>

当查看器可接受命令时兑现。

isReadyboolean

Getter —— 查看器发出就绪信号后为 true。

on(event, cb)() => void

订阅事件;返回取消订阅的函数。

off(event, cb)void

移除先前添加的事件监听器。

dispose()void

销毁查看器并移除 iframe。

并发的 add() / addFromUrl() 调用在内部串行执行,每个 promise 都与自己的那次加载关联 —— 由应用发起的加载(URL 参数、iframe 内上传)绝不会兑现你的 add() promise。

构造函数选项

选项类型默认值说明
ui'minimal' | 'full' | 'kiosk' | 'client' | 'article' | 'embed''minimal'界面预设。'embed':用于自行提供文件的宿主应用 —— 3D、选中即显示属性、带“校验”和“测量”的紧凑工具栏;无打开、分享、账户或语言。
toolbarbooleanpreset显示工具栏(默认取决于预设)。false 仅保留 3D 视图和面板。
tools('validate' | 'measure')[]['validate', 'measure']ui: 'embed' 紧凑工具栏上的工具 —— 'validate'、'measure'。[] 表示无。
autoFramebooleantrue每次加载完成后,以 iso 视角带边距框选整个场景。false 则由你控制相机。
validatebooleantrue加载时执行校验(驱动 Health Score)。
panelbooleanfalse自动打开校验面板。
panelsPanelName[]—从第一帧起就把工具栏限制为这些面板——`['scene','map']`,或用 `[]` 表示不显示工具栏。名称与 setPanels 及 URL 参数 `panels=` 相同。优于加载后再调用 setPanels:那样会先显示完整工具栏再移除工具。
langstringauto强制界面语言(如 en、es、ja)。
accent#rrggbbbrand为查看器着色,与你的仪表盘一致。
modelstring—就绪后自动加载这个公开的 IFC URL。
backgroundBackgroundSpec—从第一帧起的场景背景——预设、'#rrggbb' 或 '#上,#下'。
mapboolean | ('terrain' | 'buildings' | 'showcase')[]—加载后将模型放到地图上:true,或图层 ['terrain','buildings','showcase']。
solar · moon'MM-DDTHH:MM' · boolean—在场地当地时间打开日照分析:'06-21T18:00'。
scansstring[]—与模型一同加载的点云 URL。
layersstring—要打开的数据图层配置:从“数据图层”面板导出的 JSON 的 URL(实时数据源、样式、警报)。自 1.16 起。
scenestring—要打开的场景:场景文档的 URL(模型、数据图层、实时设备、地图、相机)。自 1.17 起。
view · fillCameraView · number—所有模型加载完成后,从该视图紧凑取景。ui: 'article' 默认 'iso'。
wheel'always' | 'ctrl''always''ctrl':滚轮滚动你的页面,只有按住 Ctrl/⌘ 才缩放,就像嵌入的地图。ui: 'article' 默认如此。
lazyboolean | 'visible'false需要时才加载:true 显示带按钮的海报,'visible' 在插图接近屏幕时启动。
poster · posterTitle · posterText · launchLabelstring—查看器就绪前显示的海报图片、标题、文字和按钮文字。
aspectRatiostring ('16/10')—按比例设置尺寸('16/10'):海报与查看器共用同一个框,页面不会跳动。
turntableboolean | numberfalse加载后空闲缓慢旋转(true = 6°/秒,或指定度/秒)。
pauseOffscreenbooleanui === 'article'离开屏幕时停止渲染。ui: 'article' 时默认 true。
fullscreenButtonbooleanfalse角落里的小放大按钮。
height · widthnumber | string'100%'iframe 尺寸(数字 → px)。
baseUrlstringauto应用的基础 URL。由脚本 URL 自动推断。
loadTimeoutnumber120000N 毫秒后拒绝 add()/addFromUrl()(0 表示禁用)。
className · titlestring—额外的 class / iframe 的 title。
on* callbacksfunction—便捷回调 —— 与 .on(...) 等价。

事件

事件Payload说明
ready{ languages }查看器已挂载并就绪。
model-progress{ percent, phase }加载进度(下载 → 解析 → 渲染)。
model-loaded{ modelId, fileName, elementCount, fromCache }某个模型加载完成。
validation-started{ modelId }某个模型开始校验 —— 加载之后(validate: true)或通过 validate()。
validation-completed{ modelId, qualityScore, errors, warnings, info, total }校验完成:Health Score 与计数,以及它们所属的模型(联合场景的汇总为 null)。
validation-failed{ modelId, message }校验无法运行或未完成 —— 附带原因。
model-error{ message, url?, name? }一次加载失败。
element-selected{ expressId, modelId, ifcType, name, globalId }用户选中了一个构件 —— 附带其 GlobalId,便于与你自己的数据对应。
pointcloud-picked{ cloudId, position, sourcePosition, classification, intensity, distance }从扫描数据中读取了一个点,使用文件自身坐标而非场景坐标。
map-feature-picked{ id, name?, label?, featureKind, heightM?, heightEstimated }点击了 OpenStreetMap 周边中的一栋建筑。仅为背景,不属于模型。
walk-changed{ active, speed }漫游模式被开启或关闭——由访客或宿主触发。
measurements-changed{ tool, units, items }添加、移除或重命名了测量。携带完整列表。
tour-started{ title, total, template }导览开始播放。
tour-step{ index, total, caption }导览切换到另一个停靠点。
tour-ended{ completed }导览停止;到达最后一站时 completed 为 true。
presentation-progress{ stage, label?, progress }导演正在生成或导出演示。
layer-feature-picked{ layerId, layer, featureId, geometry, lonLat, properties }点击了数据图层中的要素:其图层、属性以及一个可标记的点。
alert{ kind, from, id, name, rule, count, sample }数据图层或孪生的某条规则开始或停止告警。

静态成员

IfcViewer.LANGUAGES({ code, label }[],母语名称)与 IfcViewer.SUPPORTED_LANGUAGES(语言码)—— 可在查看器就绪前构建语言选择器。

Web component

零 JavaScript —— 把标签放进任意页面或仪表盘,设置属性即可。

属性: modeluilangaccentvalidatepanelbase-urltoolbartoolsauto-frame

事件会以名为 ifcviewer:<type> 的 DOM CustomEvent 重新派发(detail = payload)。底层的 IfcViewer 位于元素的 .viewer 上;getStats / getIssues / screenshot 会转发过去。

JavaScript
const el = document.querySelector("ifc-viewer");
el.addEventListener("ifcviewer:validation-completed", (e) => {
  updateScore(e.detail.qualityScore);
});
const stats = await el.getStats();

指南

范例

可直接复制粘贴的模式,对应团队真正会做的事。

构件检查器

在 3D 中点击,把 IFC 属性集读进你自己的面板。

JavaScript
viewer.on("element-selected", async (e) => {
  const data = await viewer.getElement(e.expressId, e.modelId);
  renderPanel(data.name, data.globalId, data.propertySets);
});

Health Score 组件

在你的仪表盘中呈现模型的质量分与问题数量。

JavaScript
viewer.on("validation-completed", (v) => {
  setScore(v.qualityScore);                 // 0–100, or null
  setCounts(v.errors, v.warnings, v.info);
});

IDS 合规检查

对照 buildingSMART IDS 校验,并按规格列出通过 / 未通过。

JavaScript
const ids = await fetch("/specs/project.ids").then(r => r.text());
const res = await viewer.checkIds(ids);     // runs in a worker

res.specs.forEach((spec) => {
  row(spec.name, spec.status, spec.passedCount + "/" + spec.applicableCount);
  spec.failures.forEach((f) => onClick(() => viewer.select(f.expressId, f.modelId)));
});

EIR / BIM 校验

运行可编辑的 EIR 配置(ISO 19650 风格)—— 无需 .ids 文件。失败项会带上构件的 GlobalId。

JavaScript
// An editable EIR profile — object, JSON, or the compact shorthand below.
const profile = {
  name: "Door QA",
  rules: [
    { type: "requiredProperty", entity: "IfcDoor", property: "FireRating", severity: "error" },
    { type: "allowedValues", entity: "IfcDoor", property: "FireRating",
      values: ["EI30", "EI60", "EI90"], severity: "warning" },
  ],
};
const res = await viewer.checkEir(profile);   // same IdsResult as checkIds
res.specs.forEach((spec) => {
  spec.failures.forEach((f) => row(spec.name, f.globalId, f.reasons.join(" · ")));
});

用于博客文章的场景

白色背景、模型位于其场地,以及夏至傍晚的阴影——预先声明,无需点击。

JavaScript
// Declared up front: the iframe boots with all of it, no clicks, no flash.
const viewer = await IfcViewer.create("#viewer", {
  model: "https://example.com/house.ifc",
  ui: "client",
  background: "white",
  map: ["terrain", "buildings"],   // your page declares tile consent
  solar: "06-21T19:30",            // summer solstice, site-local
});

// …or change the story as the reader scrolls
await viewer.setSolar({ date: "12-21", time: "09:30" });
const { attributions } = await viewer.getSiteContext(); // show these

平面图与测量

按楼层名称剖出平面图,让读者测量,再以国际单位读取结果。

JavaScript
const { levels } = await viewer.getSections();
await viewer.addSection({ level: levels[0].name });   // floor plan, 1.2 m up
viewer.setView("top");

await viewer.setMeasureTool("distance");
viewer.on("measurements-changed", ({ items }) => {
  // value is SI — metres, m², degrees — whatever the viewer displays
  render(items.map((m) => m.kind + ": " + m.value?.toFixed(2)));
});

文章中的引导式导览

用 getCamera() 定义一次停靠点,然后让导览在正文旁自动播放。

JavaScript
// 1. Once, while writing the post: frame each view and copy the camera.
console.log(JSON.stringify(await viewer.getCamera()));

// 2. In the page: play the stops, self-running, next to your text.
await viewer.playTour({
  title: "Casa Poblenou",
  steps: [
    { position: { x: 52, y: 29, z: 24 }, target: { x: 18, y: 7, z: -11 }, caption: "The site" },
    { position: { x: 18, y: 40, z: 11 }, target: { x: 18, y: 0, z: 10 },
      caption: "Structure", isolate: ["IfcColumn", "IfcSlab"] },
  ],
}, { autoplay: 6000 });
viewer.on("tour-step", ({ index, caption }) => highlightParagraph(index));

从你的页面生成模型视频

用配方生成演示并取回 MP4——无需录屏,无需上传。

JavaScript
viewer.on("presentation-progress", ({ stage, progress }) => bar.value = progress ?? 0);
await viewer.createPresentation("linkedin-teaser", { title: "Casa Poblenou", targetSec: 20 });

const { bytes, mimeType } = await viewer.exportPresentation({ resolution: 1080 });
video.src = URL.createObjectURL(new Blob([bytes], { type: mimeType }));

为你的文章配一张封面

用你的标题生成模型的 Pinterest 图钉或 LinkedIn 卡片,并取回 PNG。

JavaScript
await viewer.createCover({
  recipe: "pinterest",
  text: { title: "Casa Poblenou", location: "Barcelona", website: "https://example.com" },
});
const { bytes, mimeType } = await viewer.exportCover({ type: "png" });
img.src = URL.createObjectURL(new Blob([bytes], { type: mimeType }));

文章中的模型

ui: 'article' 只有画布,模型填满画面,滚轮让读者继续阅读。用 frame() 选择角度。

JavaScript
const viewer = new IfcViewer("#figure", {
  ui: "article",            // canvas only, tight iso framing, wheel scrolls the page
  model: "https://example.com/pavilion.ifc",
  lazy: true,               // nothing loads until the reader asks
  poster: "/img/pavilion.jpg",
  posterTitle: "The pavilion, live",
  aspectRatio: "16/10",     // no layout shift from poster to viewer
  background: "auto",       // paper on a light page, studio on a dark one
  turntable: true,          // a slow turn until the reader takes over
  fullscreenButton: true,
  validate: false,
});

随文字推进的故事

固定查看器,让每个段落决定镜头:隔离墙体、绕行一周、引入傍晚的阳光。

JavaScript
// <div id="figure" style="position:sticky;top:1rem"></div>
// <p id="s1">…</p> <p id="s2">…</p> <p id="s3">…</p>
const viewer = new IfcViewer("#figure", { ui: "article", aspectRatio: "16/10", model: url });
viewer.bindSteps([
  { el: "#s1", frame: { view: "iso" } },
  { el: "#s2", isolate: "IfcWall", frame: { azimuth: 225, elevation: 20 } },
  { el: "#s3", isolate: null, solar: { active: true, date: "06-21", time: "19:30" } },
]);

在你的页面中嵌入实时城市孪生

打开一个已发布的场景,响应它的告警和点击,在旁边添加你自己的实时数据源,并分享结果链接。

JavaScript
const viewer = new IfcViewer("#twin", {
  ui: "client",
  scene: "https://www.ifcvieweronline.eu/scenes/barcelona-placa-catalunya.scene.json",
});
viewer.on("alert", (a) => { if (a.kind === "start") notify(a.name + ": " + a.rule) });
viewer.on("layer-feature-picked", (f) => showCard(f.layer, f.properties));

// Your own live source next to the scene's (GeoJSON with CORS, every 60 s)
await viewer.addLayer({ url: "https://example.org/sensors.geojson", live: 60 });
const { link, sources } = await viewer.exportScene({ title: "Our twin" });

你应用中的产品页

固定版本,交出文件,展示厂商写在类型里的数据:传热系数、抗风压等级、制造商。

JavaScript
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";

const viewer = await IfcViewer.create("#product-3d", {
  ui: "embed",               // 3D + properties, Validate and Measure only
  panels: ["properties"],
  lang: "es",
});
const bytes = await fetch(product.ifcUrl).then(r => r.arrayBuffer());
await viewer.add("V-70-PR.ifc", bytes);                      // framed for you

// The product, by class (or by its GlobalId) — no expressIDs to know
const [win] = await viewer.findElements({ ifcClass: "IfcWindow" });

// What the manufacturer put in the type (IfcWindowType → HasPropertySets)
const el = await viewer.getElement(win.globalId);
const pset = (name) => el.effectivePropertySets.find(s => s.name === name);
const uw = pset("Pset_WindowCommon")?.properties.find(p => p.name === "ThermalTransmittance");
console.log(el.typeName, el.globalId, uw?.value, uw?.unit);   // "Ventana V-70 practicable" … 1.2 "W/(m²·K)"

const { qualityScore } = await viewer.validate();             // Health Score, on demand

// Declared performance per EN 14351-1: wind, water, acoustics, Uw, g / τv, DoP
const en = await viewer.checkEir("builtin-en14351-1");
const missing = en.specs.filter(s => s.status === "fail").map(s => s.name);

适配你的品牌

用一个强调色让查看器与你的产品保持一致。

JavaScript
new IfcViewer("#viewer", { accent: "#3fa866" });

// …or on the web component:
// <ifc-viewer accent="#3fa866" model="…"></ifc-viewer>

语言选择器

用查看器自带的语言构建一个选择器。

JavaScript
// build a <select> from the bundled languages
IfcViewer.LANGUAGES.forEach(({ code, label }) => addOption(code, label));

// switch at runtime
viewer.setLanguage("ja");

Health Score

校验生命周期

validate: true 会在每个模型加载完成后校验;validate() 按需运行并以结果解析。getValidation() 在各个时刻返回什么:

时刻getValidation() 返回
尚未校验null —— getValidationStatus() 显示 idle。
首次运行进行中null —— getValidationStatus() 显示 running 及进度;validation-started 已触发。
运行完成后{ qualityScore, errors, warnings, info, total, status: 'done', modelId }。多个模型时为汇总(modelId: null)。
新一轮运行进行中上一次的数字,status: 'running'。
运行失败后上一次的数字,status: 'error' 并附 error —— 若之前没有则为 null。已触发 validation-failed,validate() 已拒绝。

版本

固定版本

自 1.18.0 起,每个版本还会发布到一个永不改变的路径。生产环境请固定版本;可变路径始终指向最新版本。

HTML
<script type="module">
  import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";
</script>
路径缓存用途
/sdk/ifc-viewer.es.js每次加载都会重新验证试用 —— 总是最新版本。
/sdk/1.18.0/ifc-viewer.es.js不可变,缓存一年生产 —— 运行的正是你测试过的代码。

固定版本的 SDK 会打开哪个查看器?

iframe 是同源的在线查看器,而不是冻结的副本:固定版本的 SDK 保持自己的 API 与行为,而其中的查看器持续改进。查看器会响应任何已发布 SDK 发送的每条命令 —— 构建时会检查 —— 所以旧的固定版本依然可用。

同时固定内容

SDK 旁的 versions.json 列出了每个版本的 Subresource Integrity 哈希:

HTML
<script type="module" src="https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js"
        integrity="sha384-WsT4d0DS1oqva4rLWCGBDrx50uZVA3B95IDWbfxaFt8lnjs6NgOrjKNcWQL57/0z" crossorigin="anonymous"></script>

更新日志

1.18.0 —— 宿主应用中的目录对象。getElement() 返回 GlobalId 与类型数据(typeName、带单位的 typeProperties、effectivePropertySets;IFC4 类型与 IFC2x3 样式)。新增 validate()、getValidationStatus()、validation-started / validation-failed,validate: true 现在会在每次加载后真正校验。加载后自动框选,fit() 与 frame() 为小物体留出边距,frame(elementId),setView() 重新可以移动相机。ui: 'embed' 支持 toolbar 与 tools。固定版本位于 /sdk/1.18.0/。新增 findElements();getElement() / select() / element-selected 支持 GlobalId;门窗的框与扇属性集,以及类型的材料;在 ui: 'embed' 中属性面板会让选中的物体保持可见。 checkEir('builtin-en14351-1') 检查符合 EN 14351-1 的窗和外门的声明性能——抗风压、水密性、隔声、传热系数、g 与 τv、制造商与产品型号代码——基于标准 IFC 属性集;EIR 规则新增 where(仅检查具有某属性值的构件)和 optional。

帮助

疑难排查

少数几个容易踩到的坑。

什么都没渲染出来

依次检查:查看器是否到达 ready、模型是否加载成功(model-loaded 而非 model-error)、容器高度是否非零、字节来源是否为有效的 IFC。model-error 的消息会指出原因。

加载模型时出现 CORS 错误

CORS 只影响 addFromUrl(url)。如果字节在你手里,自行获取并调用 add(name, bytes) —— 因为数据是直接交付的,不涉及 CORS。

iframe 是空白的或拒绝加载

应用必须可被嵌入 —— 主机上不能有 X-Frame-Options: DENY,也不能有限制性的 frame-ancestors CSP。我们托管的应用允许嵌入。

调用 add() 后我的 ArrayBuffer 变空了

add() 会转移该 buffer 以实现零拷贝交付,这会让它在你的代码中变为 detached。若之后仍需这些字节,请传入副本(bytes.slice(0))。

加载或查询超时

add()/addFromUrl() 在 loadTimeout 后拒绝(默认 120 秒;0 表示禁用)。查询在 30 秒后拒绝,checkIds 在 120 秒后拒绝。慢设备上的大模型可能需要更大的 loadTimeout。

支持哪些浏览器?

任何支持 WebAssembly 与 ES 模块的现代常青浏览器 —— Chrome、Edge、Firefox、Safari。不支持 Internet Explorer。

在我本地的开发服务器上返回 404

SPA 开发服务器可能会用 HTML 回退拦截 /sdk/。请在真实构建产物或静态托管上测试,或直接导入 /sdk/ifc-viewer.es.js 模块。