Skip to main content

IDS

Source

Copying and pasting? We've got you covered! You can find the full source code of this tutorial here.

Visualizing IDS Validation Results 📊


BIM managers and data quality teams need to verify that elements comply with project data requirements — such as all doors having a fire rating — but reviewing validation results element by element in a spreadsheet gives no spatial or quantitative overview of how many elements passed or failed. The IDS chart factory takes a validation result object and generates a chart that breaks down pass, fail, and unchecked counts visually, with no manual data transformation required. This tutorial covers defining an IDS specification with an entity applicability facet (all doors) and a property requirement facet (FireRating in Pset_DoorCommon); running the validation against a loaded model; highlighting passing elements in green and failing elements in red directly in the viewport; creating a pie chart and a bar chart from the IDS result; connecting a shared legend; and wiring highlight, filter by threshold, and reset actions to the pie chart. By the end, you'll have a validation dashboard with pass/fail charts linked to color-coded elements in the 3D model, giving an immediate spatial and quantitative view of IDS compliance.

import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";
import * as BUI from "@thatopen/ui";
import * as FRAGS from "@thatopen/fragments";
import * as THREE from "three";
import * as BUIC from "../..";

📋 Initializing the UI


As always, let's first initialize the UI library. Remember you only have to do it once in your entire app.

BUI.Manager.init();

🌎 Setting Up the 3D World


Our validation results will be linked to a 3D model, so we first need a place to display it. We'll set up a simple 3D world containing a scene, a camera, and a renderer.

const components = new OBC.Components();

const worlds = components.get(OBC.Worlds);
const world = worlds.create<
OBC.SimpleScene,
OBC.SimpleCamera,
OBC.SimpleRenderer
>();
world.name = "main";

const sceneComponent = new OBC.SimpleScene(components);
sceneComponent.setup();
world.scene = sceneComponent;

const viewport = document.createElement("bim-viewport");
const rendererComponent = new OBC.SimpleRenderer(components, viewport);
world.renderer = rendererComponent;

const cameraComponent = new OBC.SimpleCamera(components);
world.camera = cameraComponent;
await world.camera.controls.setLookAt(65, 19, -27, 12.6, -5, -1.4);

viewport.addEventListener("resize", () => {
rendererComponent.resize();
cameraComponent.updateAspect();
});

const grids = components.get(OBC.Grids);
grids.create(world);

components.init();

🧩 Configuring Loaders and Managers


Next, we'll configure the IfcLoader and FragmentsManager. These components are essential for loading our BIM model and processing its geometry and data so that it can be rendered and, more importantly, audited against our IDS rules.

const ifcLoader = components.get(OBC.IfcLoader);
ifcLoader.settings.autoSetWasm = false;
await ifcLoader.setup({
wasm: { absolute: false, path: "https://unpkg.com/web-ifc@0.0.77/" },
});

// `FragmentsManager.getWorker()` fetches the matching worker for this library version from unpkg and returns a blob URL.
// You can also pass your own URL to `fragments.init(...)` if you'd rather host the worker yourself.
const workerUrl = await OBC.FragmentsManager.getWorker();
const fragments = components.get(OBC.FragmentsManager);
fragments.init(workerUrl);

world.camera.controls.addEventListener("update", () => fragments.core.update());

// Remove z fighting
fragments.core.models.materials.list.onItemSet.add(({ value: material }) => {
if (!("isLodMaterial" in material && material.isLodMaterial)) {
material.polygonOffset = true;
material.polygonOffsetUnits = 1;
material.polygonOffsetFactor = Math.random();
}
});

📜 Defining the IDS Specification


This is the heart of our validation workflow. We'll use the IDSSpecifications component to programmatically define a data requirement. An IDS is made of "facets," which define what to check (applicability) and what to check for (requirements). In this example, we'll create a simple specification:

  • Applicability: Look for all elements of the type IFCDOOR.
  • Requirement: Check if they have a property named FireRating inside the Pset_DoorCommon property set.
const ids = components.get(OBC.IDSSpecifications);

const spec = ids.create("Sample", ["IFC4"]);
spec.description =
"All doors must have FireRating specified in Pset_DoorCommon";

const entity = new OBC.IDSEntity(components, {
type: "simple",
parameter: "IFCDOOR",
});

const property = new OBC.IDSProperty(
components,
{
type: "simple",
parameter: "Pset_DoorCommon",
},
{ type: "simple", parameter: "FireRating" },
);

spec.applicability.add(entity);
spec.requirements.add(property);

🚀 Loading the BIM Model


With our validation rule defined, let's load the BIM model we want to test. We will listen for when the model is added to the FragmentsManager to continue with our logic.

const name = "sample";

fragments.list.onItemSet.add(({ value: model }) => {
model.useCamera(world.camera.three);
world.scene.three.add(model.object);
fragments.core.update(true);
});

const fragPaths = [
"https://thatopen.github.io/engine_components/resources/frags/school_arq.frag",
];
await Promise.all(
fragPaths.map(async (path) => {
const modelId = path.split("/").pop()?.split(".").shift();
if (!modelId) return null;
const file = await fetch(path);
const buffer = await file.arrayBuffer();
return fragments.core.load(buffer, { modelId: name });
}),
);

✅ Running the Validation & Visualizing Results


Now that the model is loaded and the IDS is defined, let's run the test! We'll call spec.test() on our model. This will return a detailed result object. We'll then use a helper function, ids.getModelIdMap(), to easily get the IDs of all elements that passed and failed the validation. To provide immediate visual feedback, we'll use the FragmentsManager to highlight the passing elements in green and the failing ones in red directly in the 3D viewport.

const idsResult = await spec.test([new RegExp(name)]);
const { fail, pass } = ids.getModelIdMap(idsResult);

const highlightPromises = [fragments.resetHighlight()];

highlightPromises.push(
fragments.highlight(
{
customId: "green",
color: new THREE.Color("green"),
renderedFaces: FRAGS.RenderedFaces.ONE,
opacity: 1,
transparent: false,
},
pass,
),
);

highlightPromises.push(
fragments.highlight(
{
customId: "red",
color: new THREE.Color("red"),
renderedFaces: FRAGS.RenderedFaces.ONE,
opacity: 1,
transparent: false,
},
fail,
),
);

highlightPromises.push(fragments.core.update(true));

await Promise.all(highlightPromises);

📈 Creating the IDS Chart


With the validation complete, it's time to visualize the results. We'll use the idsChart factory, which is specifically designed for this purpose. We simply pass the idsResult object we obtained from the validation step, and the factory will generate charts that clearly display the number of passing, failing, and unchecked elements.

const [pieChart] = BUIC.charts.idsChart({
type: "pie",
addLabels: true,
idsResult,
components,
});

const [barChart] = BUIC.charts.idsChart({
type: "bar",
addLabels: false,
idsResult,
components,
});

pieChart.borderColor = "#00000000";
barChart.borderColor = "#00000000";

🏷️ Adding Interactive Labels


To complement our charts, we'll add a <bim-chart-legend> component. This will act as a legend, showing the different result types (Pass, Fail, Unchecked). In a more advanced implementation, you could connect this to the Highlighter to allow users to toggle the visibility of element groups by clicking the labels.

const labels = BUI.Component.create<BUI.ChartLegend>(() => {
return BUI.html`
<bim-chart-legend>
<bim-label slot="missing-data">No data to display</bim-label>
<bim-label slot="no-chart">No chart attached</bim-label>
</bim-chart-legend>
`;
});

pieChart.addEventListener("data-loaded", () => {
labels.charts = [...labels.charts, pieChart];
});

barChart.addEventListener("data-loaded", () => {
labels.charts = [...labels.charts, barChart];
});

✨ Interacting with Chart Data


Our charts also support further client-side interaction. To demonstrate, we'll create buttons that use the chart's built-in highlight(), filterByValue(), and reset() methods to dynamically explore the results without re-running the validation.

const onHighlight = ({ target }: { target: BUI.Button }) => {
target.loading = true;

pieChart.highlight((entry) => {
if (!("value" in entry)) return false;
return entry.value > 100;
});

target.loading = false;
};

const highlightButton = BUI.Component.create(() => {
return BUI.html`
<bim-button label="Highlight" @click=${onHighlight}></bim-button>
`;
});

const onFilter = ({ target }: { target: BUI.Button }) => {
target.loading = true;

pieChart.filterByValue((entry) => {
if (!("value" in entry)) return false;
return entry.value > 100;
});

target.loading = false;
};

const filterButton = BUI.Component.create(() => {
return BUI.html`
<bim-button label="Filter" @click=${onFilter}></bim-button>
`;
});

const onReset = ({ target }: { target: BUI.Button }) => {
target.loading = true;

pieChart.reset();

target.loading = false;
};

const resetButton = BUI.Component.create(() => {
return BUI.html`
<bim-button label="Reset" @click=${onReset}></bim-button>
`;
});

🏗️ Assembling the UI Panel


Now, let's bring all our UI elements together. We'll create a <bim-panel> that neatly organizes our pie and bar charts, the legend, and our action buttons into a clean, professional-looking dashboard.

const chartPanel = BUI.Component.create(() => {
return BUI.html`
<bim-panel style="display: flex; flex-direction: column; height: 100%;">
<bim-panel-section label="IDS Result Pie Chart" icon="raphael:piechart" style="flex: 1;">
${pieChart}
</bim-panel-section>
<bim-panel-section label="IDS Result Bar Chart" icon="raphael:barchart" style="flex: 1;">
${barChart}
</bim-panel-section>
<bim-panel-section label="Labels" icon="raphael:tag" style="flex: 0.1;">
${labels}
</bim-panel-section>
<bim-panel-section label="Actions" style="display: flex; flex-direction: column; gap: 1.5rem;">
${highlightButton}
${filterButton}
${resetButton}
</bim-panel-section>
</bim-panel>`;
});

🤝 Linking Chart Events to the 3D Model


Although we've already highlighted the initial validation results, we also need to set up the Highlighter component. This ensures that future interactions (e.g., from clicking on chart segments, which can be implemented separately) can also be visually linked back to the 3D model.

const highlighter = components.get(OBCF.Highlighter);
highlighter.setup({ world });

🏁 Final Layout


Finally, let's create a <bim-grid> element to define the overall layout of our application, placing our newly created validation dashboard alongside the 3D viewport.

const app = document.createElement("bim-grid") as BUI.Grid<["main"]>;
app.layouts = {
main: {
template: `
"chartPanel viewport 3fr"
/25rem 1fr
`,
elements: { chartPanel, viewport },
},
};

app.layout = "main";
document.body.append(app);

🎉 Congratulations!


Excellent work! You've just built a complete, interactive IDS validation dashboard from scratch. You've learned how to define data requirements, test a BIM model, and visualize the pass/fail results with charts that are directly linked to the 3D view. This is a crucial tool for any data quality workflow, and you're now equipped to implement it in your own applications. Well done!