autodesk-forgeautodesk-data-visualization

ForgeViewer: creating a SurfaceShadingNode without Revit-Room


Is there a way to create a SurfaceShadingNode without passing a dbId of existing Room created in Revit?

According to API documentation, I understand that I can create a LevelRoomMap using custom bounds and then use it to generate SurfaceShadingData. For that, I have to restructure my code.

So, I would be grateful if someone could help me with way to define a SurfaceShadingNode with custom bounds.

Thanks! Bandu


Solution

  • Unfortunately, it is impossible to create SurfaceShadingNode without passing dbIds due to its implementation, but we can use Scene Builder to add custom geometries with the dbId support.

    /////////////////////////////////////////////////////////////////////
    // Copyright (c) Autodesk, Inc. All rights reserved
    // Written by Forge Partner Development
    //
    // Permission to use, copy, modify, and distribute this software in
    // object code form for any purpose and without fee is hereby granted,
    // provided that the above copyright notice appears in all copies and
    // that both that copyright notice and the limited warranty and
    // restricted rights notice below appear in all supporting
    // documentation.
    //
    // AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL FAULTS.
    // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
    // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
    // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
    // UNINTERRUPTED OR ERROR FREE.
    /////////////////////////////////////////////////////////////////////
    
    (function () {
        /**
         * Helper of converting THREE.Box3 to THREE.Mesh
         * @class
         */
        class BoxMeshHelper extends THREE.Mesh {
            constructor(box) {
                const geometry = new THREE.BufferGeometry();
                const positionNumComponents = 3;
                const normalNumComponents = 3;
                const uvNumComponents = 2;
                geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(36 * positionNumComponents), positionNumComponents));
                geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(36 * positionNumComponents), normalNumComponents));
                geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(36 * uvNumComponents), uvNumComponents));
    
                super(geometry, new THREE.MeshPhongMaterial({ color: 0xffff00, side: THREE.DoubleSide, opacity: 0.4, transparent: true }));
    
                this.type = 'BoxMeshHelper';
                this.box = box;
    
                this.positionNumComponents = 3;
                this.normalNumComponents = 3;
                this.uvNumComponents = 2;
    
                this.update();
            }
    
            update() {
                const box = this.box;
    
                if (box.isEmpty()) return;
    
                const min = box.min;
                const max = box.max;
    
                /*
                  5____4
                1/___0/|
                | 6__|_7
                2/___3/
        
                0: max.x, max.y, max.z
                1: min.x, max.y, max.z
                2: min.x, min.y, max.z
                3: max.x, min.y, max.z
                4: max.x, max.y, min.z
                5: min.x, max.y, min.z
                6: min.x, min.y, min.z
                7: max.x, min.y, min.z
                */
    
                const vertices = [
                    // front
                    { pos: [min.x, min.y, max.z], norm: [0, 0, 1], uv: [0, 1], },
                    { pos: [max.x, min.y, max.z], norm: [0, 0, 1], uv: [1, 1], },
                    { pos: [min.x, max.y, max.z], norm: [0, 0, 1], uv: [0, 0], },
    
                    { pos: [min.x, max.y, max.z], norm: [0, 0, 1], uv: [0, 0], },
                    { pos: [max.x, min.y, max.z], norm: [0, 0, 1], uv: [1, 1], },
                    { pos: [max.x, max.y, max.z], norm: [0, 0, 1], uv: [1, 0], },
                    // right
                    { pos: [max.x, min.y, max.z], norm: [1, 0, 0], uv: [0, 1], },
                    { pos: [max.x, min.y, min.z], norm: [1, 0, 0], uv: [1, 1], },
                    { pos: [max.x, max.y, max.z], norm: [1, 0, 0], uv: [0, 0], },
    
                    { pos: [max.x, max.y, max.z], norm: [1, 0, 0], uv: [0, 0], },
                    { pos: [max.x, min.y, min.z], norm: [1, 0, 0], uv: [1, 1], },
                    { pos: [max.x, max.y, min.z], norm: [1, 0, 0], uv: [1, 0], },
                    // back
                    { pos: [max.x, min.y, min.z], norm: [0, 0, -1], uv: [0, 1], },
                    { pos: [min.x, min.y, min.z], norm: [0, 0, -1], uv: [1, 1], },
                    { pos: [max.x, max.y, min.z], norm: [0, 0, -1], uv: [0, 0], },
    
                    { pos: [max.x, max.y, min.z], norm: [0, 0, -1], uv: [0, 0], },
                    { pos: [min.x, min.y, min.z], norm: [0, 0, -1], uv: [1, 1], },
                    { pos: [min.x, max.y, min.z], norm: [0, 0, -1], uv: [1, 0], },
                    // left
                    { pos: [min.x, min.y, min.z], norm: [-1, 0, 0], uv: [0, 1], },
                    { pos: [min.x, min.y, max.z], norm: [-1, 0, 0], uv: [1, 1], },
                    { pos: [min.x, max.y, min.z], norm: [-1, 0, 0], uv: [0, 0], },
    
                    { pos: [min.x, max.y, min.z], norm: [-1, 0, 0], uv: [0, 0], },
                    { pos: [min.x, min.y, max.z], norm: [-1, 0, 0], uv: [1, 1], },
                    { pos: [min.x, max.y, max.z], norm: [-1, 0, 0], uv: [1, 0], },
                    // top
                    { pos: [max.x, max.y, min.z], norm: [0, 1, 0], uv: [0, 1], },
                    { pos: [min.x, max.y, min.z], norm: [0, 1, 0], uv: [1, 1], },
                    { pos: [max.x, max.y, max.z], norm: [0, 1, 0], uv: [0, 0], },
    
                    { pos: [max.x, max.y, max.z], norm: [0, 1, 0], uv: [0, 0], },
                    { pos: [min.x, max.y, min.z], norm: [0, 1, 0], uv: [1, 1], },
                    { pos: [min.x, max.y, max.z], norm: [0, 1, 0], uv: [1, 0], },
                    // bottom
                    { pos: [max.x, min.y, max.z], norm: [0, -1, 0], uv: [0, 1], },
                    { pos: [min.x, min.y, max.z], norm: [0, -1, 0], uv: [1, 1], },
                    { pos: [max.x, min.y, min.z], norm: [0, -1, 0], uv: [0, 0], },
    
                    { pos: [max.x, min.y, min.z], norm: [0, -1, 0], uv: [0, 0], },
                    { pos: [min.x, min.y, max.z], norm: [0, -1, 0], uv: [1, 1], },
                    { pos: [min.x, min.y, min.z], norm: [0, -1, 0], uv: [1, 0], },
                ];
    
                const positions = [];
                const normals = [];
                const uvs = [];
                for (const vertex of vertices) {
                    positions.push(...vertex.pos);
                    normals.push(...vertex.norm);
                    uvs.push(...vertex.uv);
                }
    
                this.geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(positions), this.positionNumComponents);
                this.geometry.attributes.normal = new THREE.BufferAttribute(new Float32Array(normals), this.normalNumComponents);
                this.geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvs), this.uvNumComponents);
    
                this.geometry.attributes.position.needsUpdate = true;
                this.geometry.attributes.normal.needsUpdate = true;
                this.geometry.attributes.uv.needsUpdate = true;
    
                this.geometry.computeBoundingSphere();
            }
        }
    
        class DataVizCustomBoundsExt extends Autodesk.Viewing.Extension {
            constructor(viewer, options) {
                super(viewer, options);
    
                this.dbIdPrefix = 1000;
            }
    
            get dataVizExt() {
                return this.viewer.getExtension('Autodesk.DataVisualization');
            }
    
            get sceneBuilderExt() {
                return this.viewer.getExtension('Autodesk.Viewing.SceneBuilder');
            }
    
            async renderBounds(bounds) {
                if (!(bounds instanceof THREE.Box3)) return;
    
                let mesh = new BoxMeshHelper(bounds);
                let boundMesh = new THREE.Mesh(
                    mesh.geometry.clone(),
                    this.boundsMaterial
                );
                boundMesh.dbId = ++this.dbIdPrefix;
    
                this.modelBuilder.addMesh(
                    boundMesh
                );
    
                const shadingGroup = new Autodesk.DataVisualization.Core.SurfaceShadingGroup('Bounds');
    
                const boundNode = new Autodesk.DataVisualization.Core.SurfaceShadingNode(`Bound ${boundMesh.dbId}`, boundMesh.dbId);
                boundNode.addPoint(
                    new Autodesk.DataVisualization.Core.SurfaceShadingPoint('Bound sensor', bounds.center(), ['Temperature'])
                );
    
                shadingGroup.addChild(boundNode);
    
                const heatmapData = new Autodesk.DataVisualization.Core.SurfaceShadingData();
                heatmapData.addChild(shadingGroup);
    
                // Initialize with model loaded from forge
                let model = this.modelBuilder.model;
                heatmapData.initialize(model);
                await this.dataVizExt.setupSurfaceShading(model, heatmapData);
    
                this.dataVizExt.renderSurfaceShading('Bounds', 'Temperature', this.getSensorValue);
            }
    
            getSensorValue() {
                return Math.random();
            }
    
            async load() {
                await Promise.all([
                    this.viewer.loadExtension('Autodesk.Viewing.SceneBuilder'),
                    this.viewer.loadExtension('Autodesk.DataVisualization'),
                ]);
    
                await this.viewer.waitForLoadDone();
    
                this.modelBuilder = await this.sceneBuilderExt.addNewModel({
                    conserveMemory: false,
                    modelNameOverride: 'Custom Bounds',
                    loadAsHidden: true
                });
    
                const matName = 'bounds-mat';
                const boundsMat = new THREE.MeshPhongMaterial({ color: 0xffff00, side: THREE.DoubleSide, opacity: 0.4, transparent: true });
                this.modelBuilder.addMaterial(matName, boundsMat);
                this.boundsMaterial = this.modelBuilder.findMaterial(matName);
    
                return true;
            }
    
            unload() {
                this.dataVizExt.removeSurfaceShading(this.modelBuilder.model);
                this.viewer.unloadModel(this.modelBuilder.model);
                return true;
            }
        }
    
        Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.DataVizCustomBoundsExt', DataVizCustomBoundsExt);
    })();
    
    
    const dataVizCustomBoundsExt = await viewer.loadExtension('Autodesk.ADN.DataVizCustomBoundsExt');
    
    // Custom bounds
    let bounds = new THREE.Box3(new THREE.Vector3(-87.49999999999999, -123.7500076293945, -29.434711456298828), new THREE.Vector3(0, -0.0000018146972706745137, 0));
    
    dataVizCustomBoundsExt.renderBounds(bounds);
    

    Demo: https://youtu.be/O_itfTAvfrQ?t=11

    enter image description here