IFC Viewer SDK · v1.18.0
IFC ビューアを自社プロダクトに組み込む
同じブラウザビューアを CDE、デジタルツイン、社内のプロジェクトツールに組み込みます。各モデルはクライアント側で解析され、IFC データを自社アプリから直接読み込みます。
3 行で最初の描画まで
const viewer = new IfcViewer("#viewer");
await viewer.add("project.ifc", ifcBytes);
// → rendered, validated, all on the client- 100% クライアントサイド
- アップロード用バックエンド不要
- ランタイム依存ゼロ
- 約 6 KB のラッパー
- TypeScript 型定義
- 10 言語
- IDS で検証済みのエンジン
セットアップ
クイックスタート
使っているスタックを選んでください。コピーして貼り付ければ描画されます — ビルド手順も API キーも不要です。
<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>JavaScript は不要 — タグを任意のページやダッシュボードに置き、属性を設定するだけです。
<script type="module" src="https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js"></script>
<ifc-viewer model="https://your-cde.com/model.ifc" ui="minimal" accent="#3fa866"
style="display:block;height:520px"></ifc-viewer>effect でマウントし、アンマウント時に破棄します。React 18 StrictMode の二重実行にも安全です。
import { useEffect, useRef } from "react";
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";
export function ModelViewer({ url }) {
const host = useRef(null);
useEffect(() => {
const viewer = new IfcViewer(host.current, { model: url });
return () => viewer.dispose(); // StrictMode-safe teardown
}, [url]);
return <div ref={host} style={{ height: 520 }} />;
}onMounted でマウントし、onUnmounted で破棄します。
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";
const host = ref(null);
let viewer;
onMounted(() => { viewer = new IfcViewer(host.value, { model: "https://host/m.ifc" }); });
onUnmounted(() => viewer && viewer.dispose());
</script>
<template>
<div ref="host" style="height: 520px" />
</template>ホスティング版とセルフホスト
各スニペットは当方の CDN から読み込みます。どのオリジンからでも動作します。セルフホストする場合は ifc-viewer.es.js を自社ドメインから配信してください — SDK はスクリプトからの相対位置でアプリの URL を自動的に解決します。アプリを別の場所に置いている場合は baseUrl オプションを指定してください。
npm パッケージはまだありません。URL でモジュールを読み込む(上記)か、ifc-viewer.es.js を自分のアプリに同梱してください。
実証
ライブデモ
このページは SDK を実行しています。コントロールをクリックするとサンプル IFC を取得してビューアを操作します — 各ボタンは呼び出すメソッドを表示します。
⚠ サンプルのホストがレート制限をかけている可能性があります。しばらくしてからもう一度お試しください。
アーキテクチャ
仕組み
ページ側にはごく小さなラッパー、サンドボックス化された iframe には完全なビューア、その間をあなたのバイト列が渡り — データはもともとある場所で解析されます。
あなたのアプリ
iframe と型付き JS API を生成する約 6 KB の ES モジュール。3D や解析の重量はいっさいあなたのバンドルに入りません。
postMessage ブリッジ
IFC のバイト列は ArrayBuffer として iframe へ転送されます(ゼロコピー)。コマンドとクエリは対応付けられたリクエスト/レスポンスのメッセージです。
ビューア
three.js と WebAssembly にコンパイルされた web-ifc が、iframe の中でモデルを解析・描画します — 閲覧者のマシン上で。
なぜプライベートなのか
モデルがサーバーに届くことはありません。守るべきアップロード経路も、データ所在地の論点も、ファイルサイズ待ちの列もありません。
なぜスケールするのか
計算は閲覧者それぞれの端末で走ります。200 MB のモデルを配信するコストは 2 MB のものと同じ — ただの静的ファイルだからです。
コンセプト
基本コンセプト
作り始める前に押さえておきたい 4 点。
マウントと準備完了
new IfcViewer(target) は即座にマウントし、ビューアの準備ができるまでコマンドをキューに入れます。await IfcViewer.create(target)(または whenReady())ならすぐに使えるインスタンスが得られます。
// 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 は不要で、何もアップロードされません。バッファは転送される(detached になる)ため、後でも必要ならコピーを渡してください。addFromUrl(url) は CORS 対応の公開 URL を取得します。
// 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 が利用できます。
const off = viewer.on("model-loaded", (m) => {
console.log(m.fileName, m.elementCount);
});
// later: off();クエリ
必要なときにデータを取得します:getModels、getElement、getValidation、getStats、getIssues、checkIds、checkEir、screenshot。いずれもリクエスト/レスポンス方式で、タイムアウト後に reject します(30 秒。IDS / EIR は最大 120 秒)。
const models = await viewer.getModels();
const { qualityScore } = await viewer.getValidation();
const png = await viewer.screenshot();リファレンス
API リファレンス
すべてのメソッドをグループ別に — シグネチャと戻り値の型つきで。続いてコンストラクタのオプション、イベント、<ifc-viewer> Web component を掲載します。
コンストラクタ
new IfcViewer(target, options?)IfcViewerCSS セレクタまたは要素にマウント。下のオプションを参照。
IfcViewer.create(target, options?)Promise<IfcViewer>ビューアを生成し、コマンドを受け付けられる状態になったら解決します。
モデルの読み込み
add(name, bytes)Promise<ModelLoadedEvent>自社アプリから IFC バイト(ArrayBuffer/Uint8Array)を読み込みます。バッファはゼロコピーで転送されます。
addFromUrl(url, name?)Promise<ModelLoadedEvent>CORS 対応の公開 IFC URL を読み込みます。
removeModel(modelId)voidid でモデルをアンロードします。
clear()void読み込み済みのモデルをすべて削除します。
カメラと選択
select(expressId | globalId, modelId?)void要素を選択して収めます — IFC の expressID、または GlobalId で(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' }>1 つの要素(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?)voidexpressID で要素の集合を非表示 / 表示します。
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>実行時に UI のアクセントカラーを変更(#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: '1階' } はその階の平面図、{ axis: 'x', offset: 4.5 } は 4.5 m の断面。
updateSection(id, { offset?, enabled?, flipped? })Promise<SectionsState>断面の移動、有効/無効、反転。
removeSection(id?)Promise<SectionsState>断面を 1 つ削除。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 または停留点ごとの ms。
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(1 ページ)、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>グループ全体の表示/非表示、または 1 つだけ表示。
frameGroup(id)Promise<void>グループにカメラを合わせます(非表示のメンバーも含む)。
計測
setMeasureTool(tool)Promise<void>訪問者向けに計測ツールを有効化(計測パネルを開きます)、または 'none'。結果は measurements-changed で届きます。
getMeasurements()Promise<MeasurementsState>画面上のすべての計測。値は常に SI 単位——m、m²、度——表示とは無関係です。
clearMeasurements(id?)Promise<MeasurementsState>計測を 1 つ、またはすべて削除。
クエリ
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>現在の 3D ビューを PNG data URL として取得。Promise を返します。
compare({ base, head, baseLabel?, headLabel? })Promise<{ changes }>URL で 2 つの納品を(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>スキャンを 1 つ解放して GPU バッファーを返すか、すべて解放します。
setPointCloudVisible(cloudId, visible)Promise<void>スキャンを解放せずに表示・非表示を切り替えます。
fitPointCloud(cloudId?)Promise<void>カメラをスキャンに、または最初に読み込んだものに合わせます。
setPointCloudDisplay(display, renderBudget?)Promise<void>点サイズ、不透明度、配色、密度、信頼度しきい値——すべてのスキャンで共通です。いずれもシェーダーの uniform なので、2000 万点でも即座に反映されます。
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>取り込みを 1 つ削除してジオメトリー・マテリアル・テクスチャーを解放するか、すべて削除します。
setMeshVisible(visible, meshId?)Promise<void>取り込みを解放せずに表示・非表示を切り替えます。
fitMesh(meshId?)Promise<void>カメラを 1 つの取り込みに、またはすべてに合わせます。
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)と照合します。ワーカー内で実行され、仕様ごとの合否を返します。
checkEir(profile)Promise<IdsResult>EIR / BIM 検証プロファイルでモデルをチェックします。オブジェクト、その JSON(省略形も可)、または 'builtin-en14351-1' のような組み込みプロファイルの id を渡せます。IDS にコンパイルされ同じエンジンで実行されるため、checkIds と同じ IdsResult を返します。
言語
setLanguage(lang)void実行時に UI 言語を変更します。
getLanguages()string[]対応言語コードを列挙(ラベルは IfcViewer.LANGUAGES)。
ライフサイクルとイベント
whenReady()Promise<void>ビューアがコマンドを受け付けられる状態になったら解決します。
isReadybooleanゲッター — ビューアが準備完了を通知した時点で 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' | UI プリセット。'embed':ファイルを自ら渡すホストアプリ向け — 3D、選択時のプロパティ、検証と計測だけのコンパクトなツールバー。開く・共有・アカウント・言語はなし。 |
toolbar | boolean | preset | ツールバーを表示(既定はプリセットに従う)。false で 3D ビューとパネルのみ。 |
tools | ('validate' | 'measure')[] | ['validate', 'measure'] | ui: 'embed' のコンパクトなツールバーのツール — 'validate'、'measure'。[] でなし。 |
autoFrame | boolean | true | 読み込みが落ち着くたびに、シーン全体を iso から余白付きで収めます。false ならカメラはあなた次第。 |
validate | boolean | true | 読み込み時に検証を実行します(ヘルススコアの元になります)。 |
panel | boolean | false | 検証パネルを自動的に開きます。 |
panels | PanelName[] | — | 最初のフレームからツールレールをこれらのパネルに限定します — `['scene','map']`、レールなしは `[]`。setPanels および URL パラメータ `panels=` と同じ名前です。読み込み後に setPanels を呼ぶより望ましく、後者は完全なレールを表示してからツールを取り除きます。 |
lang | string | auto | UI の言語を固定します(例:en、es、ja)。 |
accent | #rrggbb | brand | ダッシュボードに合わせてビューアを色づけします。 |
model | string | — | 準備完了後にこの公開 IFC URL を自動で読み込みます。 |
background | BackgroundSpec | — | 最初のフレームからのシーン背景——プリセット、'#rrggbb'、'#上,#下'。 |
map | boolean | ('terrain' | 'buildings' | 'showcase')[] | — | 読み込み後にモデルを地図上へ:true、またはレイヤー ['terrain','buildings','showcase']。 |
solar · moon | 'MM-DDTHH:MM' · boolean | — | 敷地の現地時刻で日照スタディを開く:'06-21T18:00'。 |
scans | string[] | — | モデルと一緒に読み込む点群の URL。 |
layers | string | — | 開くデータレイヤー設定:データレイヤーパネルから書き出した JSON の URL(ライブソース、スタイル、アラート)。1.16 以降。 |
scene | string | — | 開くシーン:シーン文書の URL(モデル、データレイヤー、ライブデバイス、地図、カメラ)。1.17 以降。 |
view · fill | CameraView · number | — | すべてのモデルの読み込み後、このビューからぴったり収めます。ui: 'article' は 'iso' を含みます。 |
wheel | 'always' | 'ctrl' | 'always' | 'ctrl':ホイールはページをスクロールし、Ctrl/⌘ を押したときだけズーム。埋め込み地図と同じ動き。ui: 'article' で有効。 |
lazy | boolean | 'visible' | false | 必要になるまで読み込まない:true はボタン付きポスター、'visible' は図が画面に近づくと起動。 |
poster · posterTitle · posterText · launchLabel | string | — | ビューアの準備ができるまで表示するポスター画像・タイトル・テキスト・ボタン名。 |
aspectRatio | string ('16/10') | — | 比率でサイズ指定('16/10'):ポスターとビューアが同じ枠を共有し、ページが動きません。 |
turntable | boolean | number | false | 読み込み後のゆっくりした回転(true = 6°/秒、または度/秒)。 |
pauseOffscreen | boolean | ui === 'article' | 画面外では描画を停止。ui: 'article' では既定で true。 |
fullscreenButton | boolean | false | 隅に小さな拡大ボタン。 |
height · width | number | string | '100%' | iframe のサイズ(数値 → px)。 |
baseUrl | string | auto | アプリのベース URL。スクリプトの URL から自動的に導出されます。 |
loadTimeout | number | 120000 | N ミリ秒後に add()/addFromUrl() を reject します(0 で無効)。 |
className · title | string | — | 追加の class / iframe の title。 |
on* callbacks | function | — | 簡易コールバック — .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。 |
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 はそこへ委譲されます。
const el = document.querySelector("ifc-viewer");
el.addEventListener("ifcviewer:validation-completed", (e) => {
updateScore(e.detail.qualityScore);
});
const stats = await el.getStats();ガイド
レシピ
実際にチームが作るものを、コピー&ペーストできる形で。
要素インスペクタ
3D 上でクリックし、IFC のプロパティセットを自前のパネルに読み込みます。
viewer.on("element-selected", async (e) => {
const data = await viewer.getElement(e.expressId, e.modelId);
renderPanel(data.name, data.globalId, data.propertySets);
});ヘルススコア ウィジェット
モデルの品質スコアと指摘件数をダッシュボードに表示します。
viewer.on("validation-completed", (v) => {
setScore(v.qualityScore); // 0–100, or null
setCounts(v.errors, v.warnings, v.info);
});IDS 適合性チェック
buildingSMART の IDS に対して検証し、仕様ごとに合否を一覧表示します。
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 が付きます。
// 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(" · ")));
});ブログ記事向けのシーン
白背景、敷地に置かれたモデル、夏至の夕方の影——事前に宣言、クリック不要。
// 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平面図と計測
階名で平面を切断し、読者に計測してもらい、結果を SI 単位で読み取ります。
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() で停留点を一度定義し、本文の横でツアーを自動再生させます。
// 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 を受け取ります——画面録画もアップロードも不要。
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 を受け取ります。
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() で。
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,
});本文とともに進むストーリー
ビューアを固定し、各段落でショットを決めます:壁だけを表示、回り込み、夕方の太陽。
// <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" } },
]);ページに組み込むライブ都市ツイン
公開済みのシーンを開き、アラートやクリックに反応し、自分のライブソースを横に追加して、結果へのリンクを共有します。
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" });アプリ内の製品シート
バージョンを固定し、ファイルを渡し、メーカーがタイプに記載した内容(熱貫流率、耐風圧等級、メーカー)を表示します。
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);自社ブランドに合わせる
アクセントカラー 1 色でビューアを自社プロダクトに揃えます。
new IfcViewer("#viewer", { accent: "#3fa866" });
// …or on the web component:
// <ifc-viewer accent="#3fa866" model="…"></ifc-viewer>言語ピッカー
ビューアに同梱された言語からセレクタを組み立てます。
// 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 以降、各リリースは決して変わらないパスにも公開されます。本番では固定してください。可変パスは常に最新リリースを指します。
<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 | 不変、1 年間キャッシュ | 本番 — テストしたコードがそのまま動きます。 |
固定した SDK はどのビューアを開く?
iframe は同一オリジンのライブなビューアで、凍結されたコピーではありません。固定した SDK は自身の API と挙動を保ち、中のビューアは改善を続けます。ビューアは公開済みのどの SDK が送るコマンドにも応答し(ビルドで検査)、古い固定版も動き続けます。
内容も固定する
SDK の隣にある versions.json に各バージョンの Subresource Integrity ハッシュがあります:
<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-error ではなく model-loaded か)、コンテナの高さがゼロでないか、バイト列が正しい IFC か。原因は model-error のメッセージに示されます。
モデルの読み込みで CORS エラーになります
CORS が関係するのは addFromUrl(url) だけです。バイト列を自分で扱えるなら、自分で取得して add(name, bytes) を呼んでください — データを直接渡すので CORS は関係しません。
iframe が真っ白、または読み込まれません
アプリが埋め込み可能である必要があります — ホスト側に X-Frame-Options: DENY や厳しい frame-ancestors の CSP があってはいけません。当方のホスティング版は埋め込みを許可しています。
add() のあと ArrayBuffer が空になります
add() はゼロコピーで受け渡すためにバッファを転送し、その結果あなたのコード側では detached になります。あとでもバイト列が必要ならコピー(bytes.slice(0))を渡してください。
読み込みやクエリがタイムアウトします
add()/addFromUrl() は loadTimeout 後に reject します(既定 120 秒。0 で無効)。クエリは 30 秒、checkIds は 120 秒です。低速な端末で大きなモデルを扱う場合は loadTimeout を大きくしてください。
対応ブラウザは?
WebAssembly と ES モジュールに対応した最新のブラウザであれば動作します — Chrome、Edge、Firefox、Safari。Internet Explorer は対象外です。
ローカルの開発サーバーで 404 になります
SPA の開発サーバーが HTML フォールバックで /sdk/ を横取りすることがあります。実際のビルドや静的ホスティングで試すか、/sdk/ifc-viewer.es.js モジュールを直接 import してください。