How to query a model and create multiple Images with the HC Image Service

Overview

Using the HOOPS Communicator Image Service it is easy to quickly create multiple images of a Stream Cache model server-side. Common examples are creating images from specific camera orientations (front/top/side, etc.) or with different drawing modes (shaded, wireframe, etc.). The use-cases discussed in this post are slightly more advanced as they require first querying the model for information.

First, we will write code to create images for all CAD views of a model, then in the second example we create images for each floor in an IFC file. In both cases, the assumption is that an SCS model of the model has already been created (using converter or libconverter).

Creating Images for all CAD Views in a model

  1. Node.js needs to be installed on your machine for this example.

  2. You can run this sample code from an existing node project, otherwise create an empty project folder.

  3. Install the Image Service package by running npm install ts3d-hc-imageservice in the project folder.

  4. Create a new empty file called app.js in the project folder.

  5. Next we need to import the Image Service package. To do that, add the following code to the app.js file:

    const imageservice = require('ts3d-hc-imageservice');
    
  6. We want to find all CAD views of a model. For that we need to write a simple callback that will get executed in the browser context, which queries all CAD Views in an SCS file. Luckily the HOOPS Web Viewer provides a function to do that.

    function getAllCADViews() {
        return hwv.model.getCADViews();
    }
    
  7. We will also need a function that switches to a particular CAD View based on its id. The HOOPS Web Viewer provides a function to do that as well. As this function is asynchronous, we need to wait for the view to be activated before we can take the screenshot. We do that by specifying the function as async and then using the await keyword to wait for the hwv.model.activateCADView function to complete. For this example we also need to add a small delay, because any capping geometry is not yet completely drawn when the function returns. For most functions, adding this timeout is not necessary.

    async function setCADView(id) {
    await hwv.model.activateCADView(id, 0)
    await new Promise(r => setTimeout(r, 500));
    }
    
  8. Now we are ready to start the imageservice:

    await imageservice.start();
    
  9. Next, we need to make the initial call to query the CAD Views after the scs model has been loaded without actually rendering an image. This can be done by specifying evaluate:true as part of the options object. We also want to specificy a cacheID, so that the model will not be reloaded on subsequent calls to generateImage. Finally, we need to pass the callback to query the CAD Views we specified earlier. The model we are referencing is part of the HOOPS Communicator installation. Please adjust the path based on where you installed HOOPS Communicator.

    let cadviews = await imageservice.generateImage("c:/HOOPS_Communicator_2023_U1/quick_start/converted_models/standard/scs_models landinggear.scs", {callback: getAllCADViews,evaluate: true, cacheID: "landinggear" });
    
    
  10. The previous call will return an array of CAD Views. We can now iterate over the array and call generateImage for each CAD View. We also need to specify the cacheID we used in the previous step and specify the setCADView callback. We also want to specify a filename for each image (make sure the directory exists in the project). In this case, we use the CAD View name as the filename.

    for (let i in cadviews) {
        await imageservice.generateImage(null, { outputPath: "./images/" + cadviews[i] + ".png", callback: setCADView, callbackParam: parseInt(i), cacheID: "landinggear" });
    }
    
    
  11. Finally, we need to stop the imageservice after all png’s have been generated:

    await imageservice.shutdown();
    

Here is the complete code for this example. Note that the code that calls the image service is wrapped in an async function, so that we can use the await keyword. This is not strictly necessary, but makes the code easier to read:

const imageservice = require('ts3d-hc-imageservice');

function getAllCADViews() {
    return hwv.model.getCADViews();
}

async function setCADView(id) {
    await hwv.model.activateCADView(id, 0)
    await new Promise(r => setTimeout(r, 500));
}

(async () => {
    await imageservice.start();
    let cadviews = await imageservice.generateImage("c:/HOOPS_Communicator_2023_U1/quick_start/converted_models/standard/scs_models/landinggear.scs",
        { callback: getAllCADViews,evaluate: true, cacheID: "landinggear" });
    for (let i in cadviews) {
        await imageservice.generateImage(null, { outputPath: "./images/" + cadviews[i] + ".png", callback: setCADView, callbackParam: parseInt(i), cacheID: "landinggear" });
    }
    await imageservice.shutdown();
})();

Creating Images for all Floors in an IFC Model

This example is analogous to the previous one, but with a somewhat more complex query function. See Steps 1 - 5 from the previous example to get started.

  1. There is no function in HOOPS Communicator to directly query all floors of an IFC model, but it is fairly straightforward to retrieve this information from the model tree. Basically, this function iterates over all nodes in a model and checks if the node is a floor. If it is, it adds the node to an array. If it is not, it recursively calls itself for all children of the node. The function returns an array of all floors in the model. Important to note that this function is “self-contained” and does not rely on any function outside the context of the browser.

    async function findAllFloors() {
        let allfloors = [];
        async function findFloorsRecursive(nodeid) {
            let props = await hwv.model.getNodeProperties(nodeid);
            if (props && props.TYPE && props.TYPE == "IFCBUILDINGSTOREY") {
                allfloors.push({ nodeid: nodeid, name: hwv.model.getNodeName(nodeid) });
            }
            let children = hwv.model.getNodeChildren(nodeid);
            for (let i = 0; i < children.length; i++) {
                await findFloorsRecursive(children[i]);
            }
        }
        await findFloorsRecursive(hwv.model.getRootNode());
        return allfloors;
    }
    
  2. Next, we need a function that isolates a particular floor in the model based on its nodeid:

    async function isolateNode(nodeid) {
        await hwv.view.isolateNodes([nodeid], 0);
    }
    
  3. Now we are ready to start the imageservice:

    await imageservice.start();
    
  4. …and query the floors of the model

    let floors = await imageservice.generateImage("c:/HOOPS_Communicator_2023_U1/quick_start/converted_models/standard/scs_models/arboleda.scs", {callback:findAllFloors,callbackParam:null,evaluate:true,cacheID:"arboleda"});
    
  5. Next, we iterate over the array of floors and call generateImage for each floor. We also need to specify the cacheID and callback function we used in the previous step. We also want to specify a filename for each image. In this case, we use the floor name as the filename.

    for (let i=0;i<res.floors.length;i++) {
        await imageservice.generateImage(null, {outputPath:"./images/" + res.floors[i].name + ".png",callback:isolateNode,callbackParam:floors[i].nodeid,cacheID:"arboleda"});    
    }
    
  6. Finally, we need to stop the imageservice:

    await imageservice.shutdown();
    

Here is the complete code for this example:

const imageservice = require('ts3d-hc-imageservice');

async function findAllFloors() {
    let allfloors = [];
    async function findFloorsRecursive(nodeid) {
        let props = await hwv.model.getNodeProperties(nodeid);
        if (props && props.TYPE && props.TYPE == "IFCBUILDINGSTOREY") {
            allfloors.push({ nodeid: nodeid, name: hwv.model.getNodeName(nodeid) });
        }
        let children = hwv.model.getNodeChildren(nodeid);
        for (let i = 0; i < children.length; i++) {
            await findFloorsRecursive(children[i]);
        }
    }
    await findFloorsRecursive(hwv.model.getRootNode());
    return allfloors;
}

async function isolateNode(nodeid) {
    await hwv.view.isolateNodes([nodeid], 0);
}

 (async () => {
        await imageservice.start();
        let floors = await imageservice.generateImage("c:/HOOPS_Communicator_2023_U1/quick_start/converted_models/standard/scs_models/arboleda.scs", 
        {callback:findAllFloors,callbackParam:null,evaluate:true,cacheID:"arboleda"});
        for (let i=0;i<res.floors.length;i++) {
            await imageservice.generateImage(null, {outputPath:"./images/" + res.floors[i].name + ".png",callback:isolateNode,callbackParam:floors[i].nodeid,cacheID:"arboleda"});    
        }
        await imageservice.shutdown();
})();

Summary

Using the image service is a fast and easy way to generate customized images of your models. The examples above only really scratch the surface of what is possible. Please, also note while the examples above are written in JavaScript and are using the image service node module directly, the same functionality is available via the REST API.