Source

adapter/AdapterColneoPro.js

// ///@ts-check
/// <reference path="../tools/Authenticate.js" />

/**
 * $Id: AdapterColneoPro.js 9671 2026-01-07 15:52:16Z jochen.hanff $
 */

'use strict'

import { IModelViewer , ProjectContext } from "./IModelViewer.js";
import { ApiResponse } from "../tools/ApiResponse.js";
import { AuthenticationServices } from "../tools/Authenticate.js";

////////////////////////////////////////////////////////
///   COLNEO pro
////////////////////////////////////////////////////////

/**
 *
 *  @category Model Viewer
 *
 *  @extends {IModelViewer}
 * 
 */
export class AdptCOLNEOpro_10 extends IModelViewer {

    constructor() {

        super( "COLNEOpro_10", "COLNEO pro", "1.0.1"  )

        cnCoreAPI.selectionStateChanged.connect(this.onSelectionChanged.bind(this));
        cnCoreAPI.visibilityStateChanged.connect(this.onVisibilityChanged.bind(this));

    }

    /**
     *
     * @param {*} cb Call Back
     * returns : {
     * }
     * @param {*} user
     */
    loginUser__idp( cb , user = null )  {

        console.log( '### <AdptCOLNEOpro_10.loginUser> ')

        /////////////////////////////////////////
        cnApplicationAPI.getUser((ret_user) => {

            // console.log("ret user:")
            // console.log(JSON.stringify(ret_user))

            if (ret_user.status < 300) {

                this.setAccessToken( ret_user , (ret_accesstoken) => {

                    return cb({
                        'status': ret_accesstoken.status,
                        'data': ret_accesstoken
                    })

                })

            } else {

                const urlParams = new URLSearchParams(window.location.search);

                const authcode = urlParams.get('authcode');
                const user = urlParams.get('user');

                if (authcode != undefined && authcode.length > 0) {

                    console.log("GOT AUTHCODE")

                    cnApplicationAPI.loginUser(user, authcode, (ret_login) => {

                        console.log("=> logged in")

                        cnApplicationAPI.getUser((ret_user) => {
                            this.setAccessToken(ret_user, (ret_accesstoken) => {
                                return cb({
                                    'status': ret_accesstoken.status,
                                    'data': ret_accesstoken
                                })
                            })
                        })

                    })

                } else {
                    const l = window.location
                    // window.location = `https://idp.colneo.services/cnapp_login?redirect=${l}&uselocalstorage=false&user=jochen.hanff@colneo.email`
                    let qu = ''
                    if( user ) {
                        qu = `&user=${qu}`
                    }
                    window.location = `https://idp.colneo.services/cnapp_login?redirect=${l}&uselocalstorage=false${qu}`
                }

            }

        })

    }

    /**
     *
     * Get user,token
     * Uses 'old' IAM login for now.
     *
     * returns {
     *      'status' :
     *      'data'  : {
     *          'user'  : " ... "
     *          'token' : " ... "
     *      }
     * }
     *
     * @param {*} cb
     * @param {*} user
     *
     */
    async loginUser( cb, user = null) {

        // Check if user already logged in with COLNEO pro.
        // If not, use cn_login

        // will be changed later to -> IDP cnapp_login

        cnApplicationAPI.getUser( async (ret_user) => {

            // console.log("ret user from app:")
            // console.log(JSON.stringify(ret_user))

            if (ret_user.status < 300) {

                // console.log("=> GOT USER")

                let u = ret_user.data.userid
                let rt = await cnApplicationAPI.getAccessToken()

                // console.log("RET TOKEN:")
                // console.log( JSON.stringify(rt) )

                let ret_iam_t = await AuthenticationServices.login_IAM( u , rt.data )

                let iam_t = ret_iam_t.data

                // console.log("RESPONSE IAM LOGIN")
                // console.log( JSON.stringify(ret_iam_t) )

                return cb({
                    'status': 200,
                    'data': {
                        'userid'    : u,
                        'token'     : iam_t.token
                    }
                })

            } else {

                let url = window.location // .href
                let auth_target_ = new URL(url.protocol + url.host + "/cn_login")

                console.log(`auth : ${auth_target_}`)

                //
                // check if user and token are stored in local storage
                // if so, return user/token, otherwise forward to
                // webpage ./cn_login

                let u = window.localStorage.getItem('user')
                let t = window.localStorage.getItem('token')

                if (user != undefined) {

                    if (u != user) {
                        t = null
                    }

                    u = user

                }

                if (t == undefined) {

                    let queryString = window.location.search;
                    let urlParams = new URLSearchParams(queryString);

                    urlParams.set('redirect', window.location) // window.location.href ???

                    if (typeof _default_userid_ != 'undefined') {
                        urlParams.set('userid', _default_userid_) // window.location.href ???
                    }

                    let target = auth_target_

                    target.search = urlParams.toString()

                    window.location = target

                } else {

                    cnApplicationAPI.loginUser(u, t, async (ret) => {

                        // alert('loggedin')

                        return cb({
                            'status': 200,
                            'data': {
                                'userid': u,
                                'token' : t
                            }
                        })

                    })


                }

            }

        })

    }

    logoutUser( cb ) {

        console.log("LOGOUT")

        cnApplicationAPI.logoutUser()       //aab causing issue function not found in colneo pro

        window.localStorage.removeItem('token')
        window.localStorage.removeItem('user')
        // window.location.reload()
        window.location.replace(window.location.origin + window.location.pathname);

        return cb({
            'status' : 200
        })

    }

    /**
     *
     * @param {Context} context
     * @param {*} cb
     */
    async restoreProjectContext( context, cb ) {

        const is = await cnProjectAPI.getInfohubSettings()

        if ( is.status < 300 ) {

            const scope         = is.data.scope_id
            const project_sid   = is.data.project_shortid
            const connectedby   = is.data.connected_by

            this._infohub_project_ctx.setScopeProject( scope, project_sid , connectedby );
            context.setScopeAndProjectShortId( scope, project_sid )

            return cb({
                'status': 200,
                'data': this._infohub_project_ctx
            })

        } else {
            return cb({
                'status' : is.status
            })
        }

    }

    async resetProjectContext( context , cb ) {

         return cb({
                'status': 200,
                'data': this._infohub_project_ctx
            })

    }

    /**
     * @since 2025-10-14, aab
     */
    setInfohubSettings( settings ) {
        return cnProjectAPI.setInfohubSettings(settings)
    }

    /**
     * @since 2025-10-14, aab
     */
    getInfohubSettings( ) {
        return cnProjectAPI.getInfohubSettings()
    }

    // ======== Project Methods ========

    /**
     * @since 2025-12-02, SW
     */
    getProjectId() {
        return cnProjectAPI.getInfo("id").then(result => {
            return new ApiResponse( result.status, result.data?.id || null, result.message );
        });
    }

    getProjectDirectory() {
        return cnProjectAPI.getInfo().then(result => {
            return new ApiResponse( result.status, result.data?.filePath || null, result.message );
        });
    }

    getProjectInfo() {
        return cnProjectAPI.getInfo().then(result => {
            return new ApiResponse( result.status, result.data || null, result.message );
        });
    }

    /**
     *
     * @param {*} ret_user
     * @param {*} cb
     * 
     */
    setAccessToken( ret_user , cb ) {

        cnApplicationAPI.getAccessToken((ret_token) => {

            console.log("RET TOKEN")
            console.log(JSON.stringify(ret_token))

            if (ret_token.status < 300) {
                return cb(()=>{
                    let r    = new ApiResponse(200,null, "Access granted")
                    r.token  = ret_token.data
                    r.userid = ret_user.data.userid
                    r.avatar = ret_user.data.avatar
                    r.name   = ret_user.data.name
                    return r;
                })
            } else {
                return cb(
                    new ApiResponse(ret_token.status, null, "Access denied")
                )
            }

        })

    }

    /**
     *
     * Open URL/Link in external browser or in webform module
     *
     * @param {*} url
     * @param {*} target
     */
    openUrl(url, target = '') {
        // target empty uses the default browser
        return cnGuiAPI.openUrl(url, target || '');
    }


    getSelectedObjectIds(domains) {
        return cnCoreAPI.getSelectedObjectIds( domains )
    }

    setSelected(objIds, flagOnOff, exclusively) {
        return cnCoreAPI.setSelected( objIds, flagOnOff, exclusively )
    }

    setAllSelected( domains, flagOnOff ) {
        return cnCoreAPI.getObjectIds( domains ).then((result) => {
            return cnCoreAPI.setSelected( result.data, flagOnOff, false ).then(function (result_set) {
                return result_set
            })
        })
    }

    getObjectsRootId(domain) {
        return cnCoreAPI.getObjectsRootId( domain )
    }
    getObjects(objIdList, opt ) {
        return cnCoreAPI.getObjects(objIdList, opt )
    }

    getObjectIds(domains) {
        return cnCoreAPI.getObjectIds(domains)
    }

    /**
     * Collect ids of contained objects in object list 'objIdList'
     *
     * @param {*} objIdList
     * @param {*} depth
     */
    async getChildIds(objIdList, depth = 0) {

        let s = new Set()

        for (const id of objIdList) {
            const cids = (await cnCoreAPI.getChildIds(id, depth)).data
            for (const cid of cids) {
                s.add(cid);
            }
        }

        return new ApiResponse(200, [...s]);

    }

    filterObjectsByProperties( objIds, filterExpression) {
        return cnCoreAPI.filterObjectsByProperties(objIds,filterExpression)
    }

    filterObjectsByStatus(objIds, statusCode, statusFlag) {
        return cnCoreAPI.filterObjectsByStatus(objIds, statusCode, statusFlag)
    }

    // ======== Visibility  ========


    setVisible(objIds, flagOnOff, exclusively) {
        return cnCoreAPI.setVisible( objIds , flagOnOff , exclusively )
    }

    setAllVisible( domains, flagOnOff ) {
        return cnCoreAPI.getObjectIds( domains ).then((result) => {
            return cnCoreAPI.setVisible( result.data, flagOnOff, false )
        })
    }

    // ======== Viewpoint Methods ========

    async showHomeViewpoint() {
        return cnView3dAPI.getHomeViewpointId().then( (result) => {
            console.log( `home : ${JSON.stringify(result)}`)
            return cnView3dAPI.activateViewpoint(result.data).then(function (result_activate) {
                return result_activate;
            })
        })
    }

    // ======== Styles ========

    async resetStyles( domains ) {
        return cnCoreAPI.getObjectIds( domains ).then((result) => {
            return cnCoreAPI.resetStyles(result).then(function (result_reset) {
                return result_reset // .data;
            })
        })
    }

    applyStyleToObjects( styleId , objectIds ) {
        return cnCoreAPI.applyStyle( styleId , objectIds )
    }

    createStyle( styleDef ) {
        return cnCoreAPI.createStyle( styleDef )
    }

    getSmartSetsRootId(domain) {
        return cnCoreAPI.getSmartSetsRootId( domain )
    }
    
    createSmartSet( parentId, name, objectIds, options = null ) {
        return cnCoreAPI.createSmartSet( parentId, name, objectIds, options )
    }

    createSmartSetsFromSchema(schema) {
        return cnCoreAPI.createSmartSetsFromSchema( schema )
    }

    // === Properties ===

    getPropertyValue(objId, ptKeyStr) {
        return cnCoreAPI.getPropertyValue(objId, ptKeyStr)
    }

    getPropertyValues(objIds, ptKeyStr) {
        return cnCoreAPI.getPropertyValues(objIds, ptKeyStr)
    }

    getPropertyTypeValues(ptKeyStr, options){
        return cnCoreAPI.getPropertyTypeValues(ptKeyStr, options)
    }

    getVisibleObjectIds(domains) {
        return cnCoreAPI.getVisibleObjectIds(domains)
    }

    setPropertyValue(objId, ptKeyStr, value) {
        return cnCoreAPI.setPropertyValue(objId, ptKeyStr, value)
	}

    setProperties(objIds, propertyMap) {
        return cnCoreAPI.setProperties( objIds, propertyMap )
    }

    setPropertiesByMap( propertyMap ) {
        return cnCoreAPI.setPropertiesByObjectMap( propertyMap )
    }

    deletePropertyType(ptKeyStr) {
        return cnCoreAPI.deletePropertyType(ptKeyStr)
    }

    // GEOMETRY
     
    async transformObject( id , T , includeContained ) {
         console.error(`ERROR in ${this.apiName}.openUrl(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // === TOOLS ====
    
    async calcOptOBB(objIds) {    
        return cnCoreAPI.calculateMinimalOBB( objIds )
    }

    async checkOverlapObjects(id1, id2) {
        return cnToolsAPI.checkOverlapOBB( id1, id2 ).then( (result) => {
            const maxOverlap = Math.max( result.data.coverage_obb1, result.data.coverage_obb2 );
            return new ApiResponse( result.status, maxOverlap, result.message );
        })
    }

    async checkOverlapObjectList(idList, overlapAllowed = 0.1, statusCallback = null) {


        if(idList.length < 2) {
            console.log("Need at least two object ids to check overlap.");
            return new ApiResponse(400, null, "Need at least two object ids to check overlap.");
		}

        // calc obbs
        const resOBBcalc = await cnCoreAPI.calculateMinimalOBB(idList);
        if(resOBBcalc.status > 201) {
            console.log("Error calculating OBBs: " + resOBBcalc.message);
            return new ApiResponse(resOBBcalc.status, null, "Error calculating OBBs: " + resOBBcalc.message);
        }
        if(resOBBcalc.data < 2) {
            console.log("Need at least two objects with geometry to check overlap.");
            return new ApiResponse(400, null, "Need at least two objects with geometry to check overlap.");
        }

        if(statusCallback) {
            statusCallback({
                status: 'OBBs calculated for objects. Starting overlap checks...',
                progress: 0.1
            });
        }



        // cnToolsAPI.checkOverlapOBB(idList[0], id)

        // get obbs
        // this way we can avoid calculating objects without obb --> faster
        const resOBB = await cnCoreAPI.getPropertyValues(idList, 'cnOOBB##xs:object');
        if(resOBB.status !== 200) {
            console.log("Error getting property [cnOOBB##xs:object]: " + resOBB.message);
            return new ApiResponse(resOBB.status, null, "Error getting property [cnOOBB##xs:object]: " + resOBB.message);
        }

        let obbs = resOBB.data;
        // let tolerance = overlapAllowed; // 10cm tolerance
        // let matchedObjPairs = new Set();
        let matchedObjPairs = []

        let start = Date.now()
        const totalNoOfChecks = (idList.length * idList.length - idList.length) / 2; // half matrix under main diagonal
        let checkCounter = 0;
        const callbacks = 50; // number of progress callbacks during entire check
        const stepsSize = Math.max(1, Math.floor(totalNoOfChecks / callbacks));


        // const appInfo = await cnApplicationAPI.getInfo();
        // const builddate = Date(appInfo.data.buildDate);
        // let useOldFunctionName = false;
        // if(builddate < new Date('2025-12-12')) useOldFunctionName = true;

        // compare each obb with the others
        for(let i=0; i<idList.length; i++) {
            const id1 = idList[i];
            const obb1 = obbs[id1].value;
            // no obb, no check
            if(obb1 == null || obb1 == {}) {
                // console.log(`No OBB for object ${id1}, skipping overlap check.`);
                checkCounter += (idList.length - i); // skip all checks for this object
                continue;
            }

            for(let k=i+1; k<idList.length; k++) {
                const id2 = idList[k];
                const obb2 = obbs[id2].value;

                checkCounter++;

                // no obb, no check
                if(obb2 == null || obb2 == {}) {
                    // console.log(`No OBB for object ${id2}, skipping overlap check.`);
                    continue;
                }

                const resOverlap = await cnToolsAPI.checkOverlapObjects( id1, id2 );


                // progress callback only every callbacks times
                if(statusCallback && (checkCounter % stepsSize == 0)) {
                    statusCallback({
                        status: 'Checking overlap between objects ' + id1 + ' and ' + id2,
                        progress: 0.1 + 0.9 * ( (checkCounter) / totalNoOfChecks )
                    });
                }



                // const resOverlap = await cnToolsAPI.checkOverlapOBB( obb1, obb2 );

                // console.log(`Overlap check between ${id1} and ${id2} took ${Date.now() - start} ms.`);

                // console.log(`Result: ${resOverlap.status}`);

                if(resOverlap.status == 200) { // could calculate an overlap

                    if( resOverlap.data.overlap && (
                        resOverlap.data.coverage_obb1 > overlapAllowed
                        || resOverlap.data.coverage_obb2 > overlapAllowed ) ) {
                            // matchedObjPairs.add( [id1 , id2] );

                            // console.log(`Overlap detected between ${id1} and ${id2}`);

                            matchedObjPairs.push( {
                                id1: id1,
                                id2: id2,
                                maxOverlap: Math.max(resOverlap.data.coverage_obb1, resOverlap.data.coverage_obb2)
                            } );
                        }
                }

                //////////// BUGFIX ar 2025-11-26, function is not working properly

                // const resOverlap = cnToolsAPI.checkOverlapOBB(obb1, obb2);

                /// BUGFIX, calculate it manually here:
                // if((Math.abs(obb1.o[0] - obb2.o[0])) < tolerance
                // && (Math.abs(obb1.o[1] - obb2.o[1])) < tolerance
                // && (Math.abs(obb1.o[2] - obb2.o[2])) < tolerance
                // && (Math.abs(obb1.v1[0] - obb2.v1[0])) < tolerance
                // && (Math.abs(obb1.v1[1] - obb2.v1[1])) < tolerance
                // && (Math.abs(obb1.v1[2] - obb2.v1[2])) < tolerance
                // && (Math.abs(obb1.v2[0] - obb2.v2[0])) < tolerance
                // && (Math.abs(obb1.v2[1] - obb2.v2[1])) < tolerance
                // && (Math.abs(obb1.v2[2] - obb2.v2[2])) < tolerance
                // && (Math.abs(obb1.v3[0] - obb2.v3[0])) < tolerance
                // && (Math.abs(obb1.v3[1] - obb2.v3[1])) < tolerance
                // && (Math.abs(obb1.v3[2] - obb2.v3[2])) < tolerance) {
                //     console.log(`Overlap detected between ${id1} and ${id2}`);
                //     matchedObjects.add( [id1 , id2] );
                // }
                /////////// BUGFIX END calculate manually
            }
        }

        console.log(`Overlap check took ${Date.now() - start} ms.`);


        // postprocess matched pairs
        // let matchedList = []; // list of pairs, triplets, ...
        // matchedObjPairs.forEach( pair => {
        //     // if matched list does not contain either id of pair apend pair as new entry
        //     let found = false;
        //     for( let i=0; i<matchedList.length; i++ ) {
        //         let entry = matchedList[i];
        //         if( entry.indexOf( pair[0] ) > -1){
        //             // append id2 to this entry
        //             if( entry.indexOf( pair[1] ) == -1 ) {
        //                 entry.push( pair[1] );
        //             }
        //             found = true;
        //             break;
        //         } else if( entry.indexOf( pair[1] ) > -1 ) {
        //             // append id1 to this entry
        //             if( entry.indexOf( pair[0] ) == -1 ) {
        //                 entry.push( pair[0] );
        //             }
        //             found = true;
        //             break;
        //         }
        //     }
        //     if(!found) {
        //         matchedList.push( pair );
        //     }
        // });



        // console.log("Overlap check completed. Overlapping groups found: " + matchedList.length);
        // console.log(JSON.stringify(matchedList));

        return new ApiResponse(200, matchedObjPairs, `Overlap check completed. ${matchedObjPairs.length} overlapping groups found.`);
    }

    setDrawEdges(onOff) {
        return cnView3dAPI.getConfig().then( (result) => {
            let cfg = result.data;
            cfg.edges.enabled = onOff;
            return cnView3dAPI.setConfig(cfg).then( (result_set) => {
                return result_set;
            } );
        })
    }

    getDrawEdges() {
        return cnView3dAPI.getConfig().then( (result) => {
            let cfg = result.data;
            return new ApiResponse(200, cfg.edges.enabled, "Draw edges mode retrieved");
        })
    }

}

/**
 *
 *  @category Model Viewer
 *
 */
export class COLNEOproAdapterServices {

    constructor() {
    }

    /**
     *  Create instance of COLNEO pro API class
     *
     *  ```js
     *  const { COLNEOproAdapterServices } = await import("./node_modules/@colneo/infohub-js-api/dist/adapter/AdapterColneoPro.js");
     *  Infohub.ModelViewer = await COLNEOproAdapterServices.createAdapter()
     *  ```
     * 
     *  @returns {Promise<IModelViewer>} Model Viewer instance
     *
     */
    static async createAdapter() {
        return new AdptCOLNEOpro_10();
    }

}