bimatter-viewer-react
v0.8.5
Published
React viewer for Bimatter `.bmt` models and `.ifc` models.
Readme
bimatter-viewer-react
React viewer for Bimatter .bmt models and .ifc models.
All about us you can see on our website Bimatter
Vanila js viewer core: bimatter-viewer
Write your issuse here: GitHub
License
bimatter-viewer-react is licensed under PolyForm-Noncommercial-1.0.0.
You may use it for testing, evaluation, research, education, and personal noncommercial projects. Commercial use requires separate written permission.
Create A New React + Vite + TypeScript Project
npm create vite@latest bimatter-viewer-demo -- --template react-ts
cd bimatter-viewer-demo
npm installInstall the viewer:
npm install bimatter-viewer-reactBasic Usage
Put your .bmt or .ifc files in public/models:
public/
models/
architecture.bmt
structure.bmtReplace src/index.css or add global sizing:
html,
body,
#root {
height: 100%;
margin: 0;
}Then replace src/App.tsx:
import { Viewer } from "bimatter-viewer-react";
function App() {
return (
<Viewer
modelUrls={["/models/architecture.bmt", "/models/structure.bmt"]}
/>
);
}
export default App;Run the app:
npm run devIFC Models
public/
ifc-parser.wasm
models/
architecture.ifcFor IFC loading you need to copy ifc parser wasm file into your public folder. You can take it from bimatter-viewer-react/public/ifc-parser.wasm or by command:
cp node_modules/bimatter-viewer-react/public/ifc-parser.wasm public/ifc-parser.wasmAfter that, IFC models can be loaded the same way as BMT models:
<Viewer modelUrls={["/models/architecture.ifc"]} />You can also load mixed model lists:
<Viewer
modelUrls={[
"/models/architecture.bmt",
"/models/structure.bmt",
"/models/architecture.ifc",
]}
/>IFC spaces are loaded by default when the IFC contains room geometry, but they are hidden in the viewer by default. Disable loading them when room volumes are not needed:
await viewerRef.current?.models.loadModels(["/models/architecture.ifc"], {
clearViewer: true,
useIfcSpace: false,
});Loader API
You can load models through the viewer API and keep parsed data in your app state:
import { useState } from "react";
import {
Viewer,
type ViewerApi,
type ViewerLoadedModels,
} from "bimatter-viewer-react";
function App() {
const [modelsData, setModelsData] = useState<ViewerLoadedModels>({});
function loadInitialModels(viewer: ViewerApi) {
void viewer.models.loadModels(
["/models/architecture.bmt", "/models/structure.bmt"],
{
clearViewer: true,
onModelsDataChange: setModelsData,
},
);
}
return <Viewer modelsData={modelsData} onReady={loadInitialModels} />;
}viewer.models.loadModels accepts paths or browser File objects:
await viewerRef.current?.models.loadModels(["/models/model.bmt"]);
await viewerRef.current?.models.loadModels(["/models/model.ifc"]);
await viewerRef.current?.models.loadModels(["/models/model.ifc"], {
useIfcSpace: false,
});
await viewerRef.current?.models.loadModels(["/models/model.bmt"], {
materialMode: "performance",
});
await viewerRef.current?.models.loadModels(["/models/model.bmt"], {
useDoubleSideMaterial: true,
});
await viewerRef.current?.models.loadModels([
"/models/model.bmt",
"/models/model.ifc",
]);
await viewerRef.current?.models.loadModels(files);materialMode: "performance" uses a cheaper unlit material. Use "quality" or omit it for the default lit material.
useDoubleSideMaterial is disabled by default for better FPS. Enable it only when the model needs back faces to be visible.
Use useWorker to parse BMT and IFC models outside the main thread:
const models = await viewerRef.current?.models.loadModels(
["/models/model.ifc"],
{
clearViewer: true,
chunk: 200,
onModelsDataChange: setModelsData,
useWorker: true,
onModelProgress: (event) => {
if (!event) return;
console.log(event.phase, Math.round(event.progress * 100));
},
onChunk: (chunk) => {
console.log(chunk.modelID, chunk.geometryID);
},
},
);IFC worker loading uses the same parser wasm file. By default, ifc-parser.wasm is resolved relative to the app document base URL, so it works with Vite base paths like /my-app/ when the file is in that app's public folder. Pass a custom wasmPath only when the file is hosted elsewhere:
await viewerRef.current?.models.loadModels(["/models/model.ifc"], {
useWorker: true,
wasmPath: "/ifc-parser.wasm",
});For IFC, chunk controls how many geometries are packed before a streamed mesh is emitted. For BMT, chunks are read as they are already stored in the file.
When you render chunks yourself in onChunk, keep collectWorkerChunks: false to avoid keeping the same streamed geometry in the loader helper until the final result:
await viewerRef.current?.models.loadModels(["/models/model.bmt"], {
collectWorkerChunks: false,
useWorker: true,
onChunk: (chunk) => {
// Store chunk.geometry in your own model state.
},
});File Upload Example
import { useRef, useState } from "react";
import {
Viewer,
type ViewerApi,
type ViewerLoadedModels,
} from "bimatter-viewer-react";
function App() {
const viewerRef = useRef<ViewerApi>(null);
const [modelsData, setModelsData] = useState<ViewerLoadedModels>({});
async function onFilesChange(files: FileList | null) {
if (!files?.length) return;
await viewerRef.current?.models.loadModels(Array.from(files), {
clearViewer: true,
onModelsDataChange: setModelsData,
});
}
return (
<>
<input
accept=".bmt,.ifc"
multiple
type="file"
onChange={(event) => onFilesChange(event.target.files)}
/>
<Viewer ref={viewerRef} modelsData={modelsData} />
</>
);
}Geometry Utils
Use ref to call viewer actions:
import { useRef } from "react";
import { Viewer, type ViewerApi } from "bimatter-viewer-react";
function App() {
const viewerRef = useRef<ViewerApi>(null);
return (
<>
<button onClick={() => viewerRef.current?.camera.fitCamera()}>
Fit
</button>
<button
onClick={() => viewerRef.current?.geometryUtils.hideSelected()}
>
Hide selected
</button>
<button
onClick={() =>
viewerRef.current?.geometryUtils.isolateSelected()
}
>
Isolate selected
</button>
<button onClick={() => viewerRef.current?.geometryUtils.showAll()}>
Show all
</button>
<Viewer ref={viewerRef} modelUrls={["/models/model.bmt"]} />
</>
);
}IFC space visibility is a separate mesh-level layer. When spaces are disabled through setIfcSpacesVisibility(false), showAll() does not show them again; use setIfcSpacesVisibility(true) or toggleIIfcSpacesVisibility() for that.
Colorize Elements
Use viewerRef.current.colors to apply temporary color overrides to model elements. The color is applied by modelID and element ids:
viewerRef.current?.colors.setColor(0, [10, 20, 30], "#ff3355");
viewerRef.current?.colors.rebuildByColors({
0: {
"#ff3355": [10, 20, 30],
"#3b82f6": [40, 50],
},
1: {
"#22c55e": [10, 20],
},
});
viewerRef.current?.colors.rebuildByColors(
{
0: {
"#ff3355": [10, 20, 30],
"#3b82f6": [40, 50],
},
},
"#d1d5db",
);
viewerRef.current?.colors.rebuildModelByColors(
0,
{
"#ff3355": [10, 20, 30],
"#3b82f6": [40, 50],
},
"#d1d5db",
);
viewerRef.current?.colors.clearColor(0, [10, 20]);
viewerRef.current?.colors.clearModelColors(0);
viewerRef.current?.colors.clearAllColors();Example with current selection:
import { useRef, useState } from "react";
import {
Viewer,
type ViewerApi,
type ViewerSelection,
} from "bimatter-viewer-react";
function App() {
const viewerRef = useRef<ViewerApi>(null);
const [selected, setSelected] = useState<ViewerSelection>({});
function colorizeSelected() {
Object.entries(selected).forEach(([modelID, ids]) => {
viewerRef.current?.colors.setColor(Number(modelID), ids, "#ff3355");
});
}
return (
<>
<button onClick={colorizeSelected}>Colorize selected</button>
<button onClick={() => viewerRef.current?.colors.clearAllColors()}>
Clear colors
</button>
<Viewer
ref={viewerRef}
modelUrls={["/models/model.bmt"]}
selected={selected}
onSelectedChange={setSelected}
/>
</>
);
}Convert To BMT
Use viewerRef.current.converter to export loaded models to the BMT format. With activeView: true, the converter uses the current viewer state and exports only visible elements.
import { useRef } from "react";
import { Viewer, type ViewerApi } from "bimatter-viewer-react";
function downloadFiles(files: { blob: Blob; name: string }[]) {
files.forEach((file) => {
const url = URL.createObjectURL(file.blob);
const link = document.createElement("a");
link.href = url;
link.download = file.name;
link.click();
URL.revokeObjectURL(url);
});
}
function App() {
const viewerRef = useRef<ViewerApi>(null);
function convertToBmt() {
const result = viewerRef.current?.converter.convertToBmt({
activeView: true,
fileName: "model",
useMinVersion: true,
});
if (result) {
downloadFiles(result.files);
}
}
return (
<>
<button onClick={convertToBmt}>Export BMT</button>
<Viewer ref={viewerRef} modelUrls={["/models/model.ifc"]} />
</>
);
}Converter options:
viewerRef.current?.converter.convertToBmt({
activeView: true,
fileName: "model",
fileNames: {
0: "architecture",
1: "structure",
},
filterElement: ({ modelID, elementID }) => {
return modelID === 0 && elementID !== 10;
},
useMinVersion: true,
});activeView exports only currently visible elements. This includes hide/isolate state because the viewer passes the current scene state to the converter.
useMinVersion exports name.min.bmt plus name_props.json. Without it, props and structure are written into one .bmt file.
fileName sets a base name for exported files. fileNames sets names per modelID.
filterElement is an optional custom element filter. Return true to keep an element in the exported BMT.
IFC file conversion is also available through the same API:
await viewerRef.current?.converter.convertIfcFileToBmt(files, {
fileName: "converted_ifc",
useIfcColors: true,
useIfcElementAssembly: true,
useMinVersion: true,
});useIfcColors, useIfcElementAssembly, and useIfcSpace are used when converting IFC files directly. They do not change already loaded model data in convertToBmt. useIfcSpace is enabled by default and loads IFCSPACE geometry as a separate almost-transparent gray mesh when the IFC contains room geometry.
Controlled Selection
Selection can be controlled by your app state, including Zustand, Redux, or local React state.
import { useState } from "react";
import { Viewer, type ViewerSelection } from "bimatter-viewer-react";
function App() {
const [selected, setSelected] = useState<ViewerSelection>({});
return (
<Viewer
modelUrls={["/models/model.bmt"]}
selected={selected}
onSelectedChange={setSelected}
/>
);
}Filtered Elements Collector
Use selector.collector() to query element properties from loaded models:
import type { IfcClass } from "bimatter-viewer-react";
const wallType: IfcClass = "IfcWall";
const walls = viewerRef.current?.selector
.collector()
.ofType(wallType)
.toElements();
const basicWallIds = viewerRef.current?.selector
.collector()
.ofModel(0)
.ofType("IfcWall")
.Where((element) => element.props.Name.startsWith("basic wall"))
.toElementsIds();
const levels = viewerRef.current?.properties.getAllLevels(true);
const firstLevel = levels?.[0]?.[0];
const firstLevelWallIds = firstLevel
? viewerRef.current?.selector
.collector()
.ofLevel(firstLevel)
.ofType("IfcWall")
.toElementsIds()
: [];Without ofModel(), toElements() returns { [modelID]: { elementID, props }[] } and toElementsIds() returns { [modelID]: number[] }.
With ofModel(0), toElements() returns { elementID, props }[] and toElementsIds() returns number[].
With ofLevel(level), toElements() returns { elementID, props }[] and toElementsIds() returns number[]. Use properties.getAllLevels(true) so each level includes elements.
ofType() accepts IfcClass | string, so TypeScript suggests IFC classes like "IfcWall" while still allowing custom type strings.
Using Colorize With Collectors
Collectors can be used directly as color inputs. This is useful when you want to highlight elements by IFC type, level, or property value:
const viewer = viewerRef.current;
viewer?.colors.rebuildModelByColors(
0,
{
"#ff3355":
viewer.selector
.collector()
.ofModel(0)
.Where((element) => {
return (
viewer.properties.getParamValueByName(
element.props,
"System Name",
"Mechanical",
) === "Mechanical Supply Air 2"
);
})
.toElementsIds() ?? [],
},
"#d1d5db",
);You can also combine level filters with type filters:
const viewer = viewerRef.current;
const firstLevel = viewer?.properties.getAllModelLevels(0, true)[0];
if (viewer && firstLevel) {
viewer.colors.rebuildModelByColors(
0,
{
"#3b82f6": viewer.selector
.collector()
.ofLevel(firstLevel)
.ofType("IfcWall")
.toElementsIds(),
},
"#d1d5db",
);
}Performance Mode
Use performanceMode for heavy models or weaker GPUs:
<Viewer modelUrls={["/models/model.bmt"]} performanceMode />It caps canvas DPR at 1, disables antialiasing, asks the browser for a high-performance GPU, and uses materialMode="performance" unless another material mode is passed.
You can also switch only the model material:
<Viewer modelUrls={["/models/model.bmt"]} materialMode="performance" />Stats And Profiling
Enable FPS stats and console timing logs:
<Viewer modelUrls={["/models/model.bmt"]} showStats />Or via API:
viewerRef.current?.utils.setShowStats(true);When enabled, the viewer logs timing for selection, hide, isolate, show, and reset actions.
Navigation Cube
The navigation cube is enabled by default:
<Viewer modelUrls={["/models/model.bmt"]} />Disable it with a prop:
<Viewer modelUrls={["/models/model.bmt"]} showNavCube={false} />Or control it through the viewer API:
viewerRef.current?.utils.setShowNavCube(true);
viewerRef.current?.utils.toggleShowNavCube();Grid Axes
Control model grid axes through the viewer API:
viewerRef.current?.utils.setShowGridAxes(true);
viewerRef.current?.utils.toggleShowGridAxes();
viewerRef.current?.utils.setGridAxisVisibility("right", false);
viewerRef.current?.utils.setGridAxesVisibility({
bottom: true,
left: true,
right: false,
top: false,
});HotKeys
Selection
Left Click - Select element
Shift + Left Click - Multi-select element
Ctrl + Left Click - Toggle element selection
Shift + Drag Right - Select elements fully inside rectangle
Shift + Drag Left - Select elements intersected by rectangle
Ctrl + Drag Right - Unselect elements fully inside rectangle
Ctrl + Drag Left - Unselect elements intersected by rectangle
Visibility
C - Clear hidden / isolated elements
H - Hide selected elements
I - Isolate selected elements
Clipping
P - Create clipping plane by model intersection
ALT + Drag - Drag clipping plane
Dimensions
M - Toggle dimension drawing mode
