23 Jun 2021

Add Data Visualization Heatmaps for Rooms of non-Revit model part II - IFC

 

Last week, we discussed adding heat map support with Navisworks models (NWC): Add Data Visualization Heatmaps for Rooms of non-Revit model part I - NWC | Autodesk Forge. You may ask, "How about IFC?" Yeah, We got you covered! Today, this blog is to do a similar thing for IFC models, and here are my tricks.

 

 (Note. These tricks are working only for IFC translated with the modern pipeline.)

 

First, we need to rebuild the AEC Model Data for IFC as well. Fortunately, comparing to NWC, Forge Viewer has a native way to achieve this. We don't need to gather level data ourselves as the NWC one does. Just add an option called ifcLevelsEnabled to either loading the Autodesk.AEC.LevelsExtension extension or configuring the viewer instance.

// Load level extension manually
await viewer.loadExtension('Autodesk.AEC.LevelsExtension', { ifcLevelsEnabled: true } );

// Load level extension while starting the viewer
const config3d = {
  extensions: [
    'Autodesk.AEC.LevelsExtension'
  ],
  ifcLevelsEnabled: true
};
viewer = new Autodesk.Viewing.GuiViewer3D(htmlDiv, config3d);
viewer.start();

Second, we will rebuild room data for the Data Visualization extension. The step is almost the same like the NWC one, but there are few differences while getting room name and room DbIds.

async function getRoomName(dbId, model) {
    const result = await getPropAsync(dbId, model);
    const nameProp = result.properties.find(p => p.attributeName === 'lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Reference(Pset_SpaceCommon)_PG_IFC');

    return nameProp.displayValue;
}

async function getRoomDbIds(model) {
    return new Promise((resolve, reject) => {
        viewer.search( 'Rooms', resolve, reject, ['lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Category(Pset_SpaceCommon)_PG_IFC'])
    });
}

To get the attribute names of Room Name and Room Category, we can take advantage of the Viewer3D#getProperties to get selected room properties, then we get:

  • Attribute name of Room Name: lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Reference(Pset_SpaceCommon)_PG_IFC
  • Attribute name of Room Category: lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Category(Pset_SpaceCommon)_PG_IFC

(Note. If you cannot find these two properties in the model, they can be replaced with other similar properties as you want.)

IFC Room Props

Afterward, we can rebuild the room map with this code snippet. Before starting this step, don't forget to load Autodesk.DataVisualization extension.

const dataVizExtn = await viewer.loadExtension('Autodesk.DataVisualization');

async function getBoxAsync(dbId, model) {
    return new Promise((resolve, reject) => {
        const tree = model.getInstanceTree();
        const frags = model.getFragmentList();
        tree.enumNodeFragments(dbId, function(fragid) {
            let bounds = new THREE.Box3();
            frags.getWorldBounds(fragid, bounds);
            return resolve(bounds);
        }, true);
    });
}

function getLevel(box, floors) {
    const currentElevation = box.min.z;

    if (currentElevation < floors[0].zMin) {
        return floors[0];
    } else if (currentElevation > floors[floors.length - 1].zMax) {
        return floors[floors.length - 1];
    } else {
        return floors.find(f => f.zMin <= currentElevation && f.zMax >= currentElevation );
    }
}

async function getPropAsync(dbId, model) {
    return new Promise((resolve, reject) => {
        model.getProperties(dbId, result => resolve(result));
    });
}

async function getRoomName(dbId, model) {
    const result = await getPropAsync(dbId, model);
    const nameProp = result.properties.find(p => p.attributeName === 'lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Reference(Pset_SpaceCommon)_PG_IFC');

    return nameProp.displayValue;
}

async function getRoomDbIds(model) {
    return new Promise((resolve, reject) => {
        viewer.search( 'Rooms', resolve, reject, ['lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Category(Pset_SpaceCommon)_PG_IFC'])
    });
}

async function buildRoomMap(viewer) {
    const model = viewer.model;
    const levelExt = await viewer.loadExtension('Autodesk.AEC.LevelsExtension');
    const floors = levelExt.floorSelector.floorData;

    const dbIds = await getRoomDbIds(model);
    const DataVizCore = Autodesk.DataVisualization.Core;
    let levelRoomsMap = new DataVizCore.LevelRoomsMap();

    for ( let i = dbIds.length - 1; i >= 0; i-- ) {
        const dbId = dbIds[i];
        const box = await getBoxAsync(dbId, model);
        const level = getLevel(box, floors);
        const name =  await getRoomName(dbId, model);

        let room = new DataVizCore.Room(
            dbId, //Room's DbId
            name,
            box
        );

        levelRoomsMap.addRoomToLevel(level.name, room);
    }

    return levelRoomsMap;
}

const levelRoomsMap = await buildRoomMap(viewer.model, viewer);

Third, let's mock some sensors to see what the heatmap will like

const DataVizCore = Autodesk.DataVisualization.Core;
const devices = [];

for(let lvl in levelRoomsMap) {
    const rooms = levelRoomsMap[lvl];
    for (let i = rooms.length - 1; i >= 0; i--) {
        const room = rooms[i];
        const center = room.bounds.center();
        const device = {
            id: `${room.name} Device`, // An ID to identify this device
            position: center, // World coordinates of this device
            sensorTypes: ['temperature'], // The types/properties this device exposes
        }

        devices.push(device);
        room.addDevice(device);
    }
}

const structureInfo = new DataVizCore.ModelStructureInfo(viewer.model);
let shadingData = await structureInfo.generateSurfaceShadingData(devices, levelRoomsMap);

await dataVizExtn.setupSurfaceShading(viewer.model, shadingData);

// Set heatmap colors for temperature
dataVizExtn.registerSurfaceShadingColors('temperature', [0x00ff00, 0xff0000]);

function getSensorValue() {
    return Math.random(); // Your random function 🙂
}

// Generate the heatmap graphics (do this one time, this is heavier)
dataVizExtn.renderSurfaceShading(Object.keys(levelRoomsMap), 'temperature', getSensorValue);
// Do this as many times as you want (this is lightweight)
dataVizExtn.updateSurfaceShading(getSensorValue);

viewer.getExtension('Autodesk.AEC.LevelsExtension').floorSelector.selectFloor(0);

 

Here is the final result:

Heatmap on IFC

Lastly, put them together:

// Load level extension manually
await viewer.loadExtension('Autodesk.AEC.LevelsExtension', { ifcLevelsEnabled: true } );

async function getBoxAsync(dbId, model) {
    return new Promise((resolve, reject) => {
        const tree = model.getInstanceTree();
        const frags = model.getFragmentList();
        tree.enumNodeFragments(dbId, function(fragid) {
            let bounds = new THREE.Box3();
            frags.getWorldBounds(fragid, bounds);
            return resolve(bounds);
        }, true);
    });
}

function getLevel(box, floors) {
    const currentElevation = box.min.z;

    if (currentElevation < floors[0].zMin) {
        return floors[0];
    } else if (currentElevation > floors[floors.length - 1].zMax) {
        return floors[floors.length - 1];
    } else {
        return floors.find(f => f.zMin <= currentElevation && f.zMax >= currentElevation );
    }
}

async function getPropAsync(dbId, model) {
    return new Promise((resolve, reject) => {
        model.getProperties(dbId, result => resolve(result));
    });
}

async function getRoomName(dbId, model) {
    const result = await getPropAsync(dbId, model);
    const nameProp = result.properties.find(p => p.attributeName === 'lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Reference(Pset_SpaceCommon)_PG_IFC');

    return nameProp.displayValue;
}

async function getRoomDbIds(model) {
    return new Promise((resolve, reject) => {
        viewer.search( 'Rooms', resolve, reject, ['lcldrevit_parameter_Pset_SpaceCommon_tab:lcldrevit_parameter_Category(Pset_SpaceCommon)_PG_IFC'])
    });
}

async function buildRoomMap(viewer) {
    const model = viewer.model;
    const levelExt = await viewer.loadExtension('Autodesk.AEC.LevelsExtension');
    const floors = levelExt.floorSelector.floorData;

    const dbIds = await getRoomDbIds(model);
    const DataVizCore = Autodesk.DataVisualization.Core;
    let levelRoomsMap = new DataVizCore.LevelRoomsMap();

    for ( let i = dbIds.length - 1; i >= 0; i-- ) {
        const dbId = dbIds[i];
        const box = await getBoxAsync(dbId, model);
        const level = getLevel(box, floors);
        const name =  await getRoomName(dbId, model);

        let room = new DataVizCore.Room(
            dbId, //Room's DbId
            name,
            box
        );

        levelRoomsMap.addRoomToLevel(level.name, room);
    }

    return levelRoomsMap;
}


async function initHeatmap(viewer) {
    const model = viewer.model;
    const dataVizExtn = await viewer.loadExtension('Autodesk.DataVisualization');
    const DataVizCore = Autodesk.DataVisualization.Core;
    const levelRoomsMap = await buildRoomMap(viewer);
    const devices = [];

    for(let lvl in levelRoomsMap) {
        const rooms = levelRoomsMap[lvl];
        for (let i = rooms.length - 1; i >= 0; i--) {
            const room = rooms[i];
            const center = room.bounds.center();
            const device = {
                id: `${room.name} Device`, // An ID to identify this device
                position: center, // World coordinates of this device
                sensorTypes: ['temperature'], // The types/properties this device exposes
            }

            devices.push(device);
            room.addDevice(device);
        }
    }

    const structureInfo = new DataVizCore.ModelStructureInfo(model);
    let shadingData = await structureInfo.generateSurfaceShadingData(devices, levelRoomsMap);

    await dataVizExtn.setupSurfaceShading(model, shadingData);

    // Set heatmap colors for temperature
    dataVizExtn.registerSurfaceShadingColors('temperature', [0x00ff00, 0xff0000]);

    function getSensorValue() {
        return Math.random(); // Your random function 🙂
    }

    // Generate the heatmap graphics (do this one time, this is heavier)
    dataVizExtn.renderSurfaceShading(Object.keys(levelRoomsMap), 'temperature', getSensorValue);
    // Do this as many times as you want (this is lightweight)
    dataVizExtn.updateSurfaceShading(getSensorValue);

    viewer.getExtension('Autodesk.AEC.LevelsExtension').floorSelector.selectFloor(0);
}

await initHeatmap(viewer);

That's it! Hope you enjoy it~ 

Related Article