Open5
ThatOpenのIFCローダーをNext.jsで動かす

Next.jsのプロジェクトを作成する
npx create-next-app@latest
モジュールのインストール
npm i @thatopen/components @thatopen/fragments web-ifc three

wasmの設定
このスクラップに書いたように、Macだとwasmはローカルに配置しないと動作しないらしい。Next.jsの設定を変更してwasmをロードできるようにする、
next.config.ts
import type { NextConfig } from "next";
import path from "path";
const nextConfig: NextConfig = {
outputFileTracingRoot: path.join(__dirname, './'),
webpack: (config) => {
// クライアント側でのfsやpathを使用不可にする
config.resolve.fallback = {
fs: false,
path: false,
}
// WebAssemblyの非同期読み込み設定
config.experiments = {
asyncWebAssembly: true,
layers: true,
}
return config
},
// WASMファイルのMIMEタイプ設定
async headers() {
return [
{
source: "/(.*).wasm",
headers: [
{
key: "Content-Type",
value: "application/wasm",
},
],
},
];
},
};
export default nextConfig;

wasmをローカルにコピーする
% cp node_modules/web-ifc/web-ifc.wasm public/
% cp node_modules/web-ifc/web-ifc-mt.wasm public/

IfcLoaderExampleコンポーネントを作成する
'use client'
import * as OBC from "@thatopen/components"
import { useEffect, useRef, useState } from 'react'
export const IfcLoaderExample = () => {
const containerRef = useRef<HTMLDivElement>(null)
const [components, setComponents] = useState<OBC.Components | null>(null)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const initScene = async () => {
if (!containerRef.current) return
// Components初期化
const components = new OBC.Components()
// World設定
const worlds = components.get(OBC.Worlds)
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBC.SimpleRenderer
>()
world.scene = new OBC.SimpleScene(components)
world.scene.setup()
world.scene.three.background = null
// Renderer設定
world.renderer = new OBC.SimpleRenderer(components, containerRef.current)
world.camera = new OBC.OrthoPerspectiveCamera(components)
await world.camera.controls.setLookAt(78, 20, -2.2, 26, -4, 25)
components.init()
// Grid追加
components.get(OBC.Grids).create(world)
// IFCLoader設定(ローカル指定)
const ifcLoader = components.get(OBC.IfcLoader)
await ifcLoader.setup({
autoSetWasm: false,
wasm: {
path: "/",
absolute: true,
}
})
// FragmentsManager設定
const githubUrl = "https://thatopen.github.io/engine_fragment/resources/worker.mjs"
const fetchedUrl = await fetch(githubUrl)
const workerBlob = await fetchedUrl.blob()
const workerFile = new File([workerBlob], "worker.mjs", {
type: "text/javascript",
})
const workerUrl = URL.createObjectURL(workerFile)
const fragments = components.get(OBC.FragmentsManager)
fragments.init(workerUrl)
// カメラが停止しているときにフラグメントを更新
world.camera.controls.addEventListener("rest", () =>
fragments.core.update(true)
)
// フラグメント追加時の処理
fragments.list.onItemSet.add(({ value: model }) => {
model.useCamera(world.camera.three)
world.scene.three.add(model.object)
fragments.core.update(true)
})
// State更新
setComponents(components)
}
initScene()
}, [])
const loadExampleIfc = async () => {
if (!components) return
setIsLoading(true)
try {
const ifcLoader = components.get(OBC.IfcLoader)
const file = await fetch("https://thatopen.github.io/engine_components/resources/ifc/school_str.ifc")
const data = await file.arrayBuffer()
const buffer = new Uint8Array(data)
await ifcLoader.load(buffer, false, "example", {
processData: {
progressCallback: (progress) => console.log("Loading progress:", progress),
},
})
} catch (error) {
console.error("Error loading IFC:", error)
} finally {
setIsLoading(false)
}
}
return (
<div style={{ position: "relative", width: "100%", height: "600px" }}>
<div
ref={containerRef}
style={{ width: "100%", height: "100%", border: "1px solid #ccc" }}
/>
<div style={{
position: "absolute",
top: "10px",
right: "10px",
background: "white",
padding: "10px",
borderRadius: "5px",
boxShadow: "0 2px 10px rgba(0,0,0,0.1)"
}}>
<button
onClick={loadExampleIfc}
disabled={isLoading}
style={{
padding: "8px 16px",
marginBottom: "8px",
backgroundColor: isLoading ? "#ccc" : "#007bff",
color: "white",
border: "none",
borderRadius: "4px",
cursor: isLoading ? "not-allowed" : "pointer",
display: "block",
width: "100%"
}}
>
{isLoading ? "Loading..." : "Load Example IFC"}
</button>
{isLoading && <p style={{ color: "#007bff", fontSize: "12px" }}>Loading...</p>}
</div>
</div>
)
}

コンポーネントを呼び出す