Source

adapter/IModelViewer.js

//
// $Id: IModelViewer.js 9716 2026-01-13 14:01:08Z jochen.hanff $


const selectionChanged  = new CustomEvent("selection-changed");
const visibilityChanged = new CustomEvent("visibility-changed");

import { Context } from "../infohub/Context.js"
import { ApiResponse } from "../tools/ApiResponse.js";


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

    project = null
    scope = null
    connectedby = null

    constructor() { }

    setScopeProject(scope, project, connectedby) {
        this.project = project
        this.scope = scope
        this.connectedby = connectedby
    }

}

/**
 *
 *  @category Model Viewer 
 * 
 *  @class
 *  @classdesc
 *  Common interface and base class for Model Viewers such as DESITE, COLNEO Pro, Web Viewer, etc. 
 *
 *  To create an instance of a ModelViewer use one of the Factory Methods of
 *  the specific service classes such as `COLNEOproAdapterServices`, etc.
 * 
 *  Example:
 * ```js
 *  const { COLNEOproAdapterServices } = await import("./node_modules/@colneo/infohub-js-api/dist/adapter/AdapterColneoPro.js"); 
 *  Infohub.ModelViewer = await COLNEOproAdapterServices.createAdapter()
 * ```
 * 
 *  If the user changes the selection or visibility of objects in the 3D-scene these events are thrown:
 *  ```js
 *  Event `selection-changed` when selection of objects in 3d view is changed
 *  Event `visibility-changed` when visibility of objects in 3d view is changed
 *  ``` 
 * 
 *  To connect to these events use {@link IModelViewer#connectModelViewer connectModelViewer()}.
 * 
*/
export class IModelViewer extends EventTarget {

    apiName = null
    viewerName = null
    viewerVersion = null

    static eDomain = {
        GEO: "geo",
        ACT: "act",
        BOQ: "boq",
        DOC: "doc",
        TYP: "typ",
        ISS: "iss",
        RES: "res",
        BST: "bst",
        CST: "cst",
        ALL: "all"
    };

    // infohub project context ( scope , project )
    /* ProjectContext */ _infohub_project_ctx = null

    constructor(t = 'unknow', n = 'unknown', v = '0.0.0') {
        super()
        this.apiName = t;
        this.viewerName = n;
        this.viewerVersion = v;
        this._infohub_project_ctx = new ProjectContext()
    }

    get eDomain() {
        return this.constructor.eDomain;
    }

    // ======== user, infohub context (scope,project) ========

    /**
     *
     *  @group Authentication
     * 
     *  @description
     *  Get 'user' and 'token' from ModelViewer.
     *
     *  @returns {ApiResponse<Object>} response 
     *
     *  @example
     *  {
     *      "status"    : 200,
     *      "data"      : {
     *          "user": "JohnDoe",
     *          "token": "abc123"
     *      }
     *  }
     *
     * @param {function}    cb      Callback function
     * @param {string}      user    Login this user (optional)
     *
     */
    loginUser(cb, user = null) {
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *  @category Authentication
     *  
     *  @description
     *  Logout user.
     *  Clear user and token.
     * 
     *  @param {function}    cb  Callback function
     *  @returns {ApiResponse<Object>} response 
     *  
     */
    logoutUser(cb) {
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *
     * Restore project context (scope,project) from model viewer (from current project)
     * and store it in infohub context ctx.
     *
     * @param {Context} ctx
     * @param {*} cb Call back
     * @returns {Promise<ApiResponse>} Returns object with status and ProjectContext data
     */
    async restoreProjectContext(ctx, cb) {
        console.error(`ERROR in ${this.apiName}.getProjectInfo(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    async saveProjectContext(cb) {
        console.error(`ERROR in ${this.apiName}.getProjectInfo(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    async getProjectContext() {
        return this._infohub_project_ctx
    }

    /**
     *  Get name and version of the current ModelViewer.
     *  
     *  @returns {object} JSON object containing name and version.
     *  @example {
     *      "name"      : "...",
     *      "type"      : "...",
     *      "version"   : ".."
     *  }
     * 
     *  @since 10.2025
     */
    getInfo() {
        return {
            name: this.viewerName,
            type: this.apiName,
            version: this.viewerVersion
        }
    }

    /**
     *
     * @param {object} settings {
     *       'scope_id'        : ' ... ',
     *       'project_shortid' : ' ... ',
     *       'connected_by'    : ' ... '
     *   }
     * @returns {ApiResponse<void>}
     * complies with COLNEOpro-API, 2025-10-14, ar
     */
    setInfohubSettings(settings) {
        console.error(`ERROR in ${this.apiName}.setInfohubSettings(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *
     * @returns {object} settings object
     * @example {
     *       'scope_id'        : ' ... ',
     *       'project_shortid' : ' ... ',
     *       'connected_by'    : ' ... '
     *   }
     */
    getInfohubSettings() {
        console.error(`ERROR in ${this.apiName}.getInfohubSettings(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Dump info to console
     * @updated 12.01.2026, SW
     */
    dump() {
        console.log(JSON.stringify(this.getInfo(), null, 2))
    }

    /**
     *
     * @method     
     * 
     * @description
     * Connect signals `selection changed` and `visibility changed` to given functions.
     *
     * @returns {Object} 
     * ```js
     * {
     *      'status' : ...
     * }
     * ```
     *
     * @param {function} on_sc Function to be called on selection changed, null to disconnect
     * @param {function} on_vc Function to be called on visibility changed, null to disconnect
     * @param {*} cb
     *
     */
    connectModelViewer(on_sc, on_vc, cb) {

        this.addEventListener("selection-changed", on_sc)
        this.addEventListener("visibility-changed", on_vc)

        return cb({
            'status': 200
        })

    }

    /**
     * add selection listener, no extra call back function
     * @param {*} listener
     * @returns {Promise<null>}
     * @since 08.12.2025, ar
     */
    addSelectionEventListener(listener) {
        return this.addEventListener("selection-changed", listener)
    }

    /**
     * add visibility listener, no extra call back function
     * @param {*} listener
     * @returns {Promise<null>}
     * @since 08.12.2025, ar
     */
    addVisibilityEventListener(listener) {
        return this.addEventListener("visibility-changed", listener)
    }

    /**
     * remove selection listener, no extra call back function
     * @param {*} listener
     * @returns {Promise<null>}
     * @since 08.12.2025, ar
     */
    removeSelectionEventListener(listener) {
        return this.removeEventListener("selection-changed", listener)
    }

    /**
     * remove visibility listener, no extra call back function
     * @param {*} listener
     * @returns {Promise<null>}
     * @since 08.12.2025, ar
     */
    removeVisibilityEventListener(listener) {
        return this.removeEventListener("visibility-changed", listener)
    }

    /**
     *
     */
    onSelectionChanged() {
        this.dispatchEvent(selectionChanged)
    }

    /**
     *
     */
    onVisibilityChanged() {
        this.dispatchEvent(visibilityChanged)
    }

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

    /**
     * Get project metadata
     * @returns {ApiResponse<object>} //RM
     * @since 10.2025
     */
    getProjectInfo(projectId) {
        console.error(`ERROR in ${this.apiName}.getProjectInfo(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get project id
     * @since 12.2025
     */
    getProjectId() {
        console.error(`ERROR in ${this.apiName}.getProjectId(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get project directory
     * @returns {ApiResponse<string>} file path
     */
    getProjectDirectory(projectId) {
        console.error(`ERROR in ${this.apiName}.getProjectDirectory(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Save current project, show SaveFileDialog if project was newly created and no file path is available.
     * @returns {ApiResponse<boolean>} True if project was saved successfully.
     */
    saveProject() {
        console.error(`ERROR in ${this.apiName}.saveProject(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ======== Model Methods ========

    /**
     * Get model IDs for given domains.
     * @param {eDomain[]} domains - Domain (e.g. 'all') or combination as array, e.g. ['geo', 'act'].
     * @returns {ApiResponse<string[]>} Array of model IDs.
     * @since 10.2025
     * @updated 01.2026, SW - domains instead of domain
     */
    getModelIds(domains) {
        console.error(`ERROR in ${this.apiName}.getModelIds(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get metadata for given model.
     * @param {string} modelId - Model ID.
     * @returns {ApiResponse<object>}
     * @since 10.2025
     */
    getModelMetadata(modelId) {
        console.error(`ERROR in ${this.apiName}.getModelMetadata(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Create model. Synchronizes with Infohub.
     * @param {string} name - Model name.
     * @param {string} domain - Domain (e.g. 'geo')
     * @param {object} settings - Model settings.
     * @returns {ApiResponse<string>} ID of newly created model.
     */
    createModel(name, domain, settings) {
        console.error(`ERROR in ${this.apiName}.createModel(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Add model to coordination model. Synchronizes with Infohub.
     * @param {string} modelId - Model ID.
     * @returns {ApiResponse<boolean>} True if successfully added
     * @updated 01.2026, SW modelId instead of filename
     */
    importModel(modelId) {
        console.error(`ERROR in ${this.apiName}.addModel(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * remove model from project. Synchronizes with Infohub.
     * @param {string} modelId - Model ID.
     * @returns {ApiResponse<boolean>} True if model was successfully removed.
     */
    removeModel(modelId) {
        console.error(`ERROR in ${this.apiName}.removeModel(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ======== Object Methods ========

    /**
     * Get root object ID for given domain.
     * @param {*} domain - Domain (e.g. 'geo')
     * @returns {ApiResponse<string>} Root object ID
     */
    getObjectsRootId(domain) {
        console.error(`ERROR in ${this.apiName}.getObject(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get object by ID. Synchronizes data with Infohub.
     * @param {string} objId - ID of object.
     * @returns {ApiResponse<cnObject>} returns object in GOM format
     */
    getObject(objId, keys) {
        return this.getObject([objId], keys)
    }

    /**
     *
     * Get objects in GOM format
     * 
     * @param {Array<string>} objIdList     Object Ids
     * @param {*} opt      
            `
                {
                   "properties"     : {  ... options ... }          // if empty, return all
                    "geometry"      : {  ... options ... },
                    "relations"     : {},
                    "nodes"         : {}
                }
            `
     *
    *
    *   @example
    *   getObjects(
    *       ["obj1", "obj2"], 
    *       {
    *           properties: { 
    *               "propertytypes": [          // whitelist by default
    *                   "cnName##xs:string",
    *                   "MyNumber##xs:double"
    *               ]
    *           },
    *           geometry: {},
    *           relations: {},
    *           nodes: {}
    *       }
    *   );
    * 
    */
    getObjects(objIdList, opt) {
        console.error(`ERROR in ${this.apiName}.getObject(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *  Collect object Ids contained in objects of objIdList, serach to depth of 'depth' or the
     *  complete hiererchy if depth is 0 (default)
     *
     * @param {*} objIdList     Collect child ids from these root objects
     * @param {*} depth         [optional] Recursion depth, default = 0 (maximum depth)
     */
    async getChildIds(objIdList, depth = 0) {
        console.error(`ERROR in ${this.apiName}.getObject(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Create a new object. Synchronizes data with Infohub.
     * @param {string} parentId - ID of parent object
     * @param {object} data - Data for the new object in JSON notation
     * @example {
                    info: {
                        object_type : "geo_container",
                        object_name : "MyObject"
                    },
                    properties: {
                        ...
                    },
                    geometry: {
                        ...
                    }
                }
     * @returns {ApiResponse<string>} ID of newly created object.
     */
    createObject(parentId, data) {
        console.error(`ERROR in ${this.apiName}.createObject(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Delete objects by ID. Synchronizes data with Infohub.
     * @param {string[]} objIds - IDs of objects to delete.
     * @returns {ApiResponse<number>} Number of objects deleted.
     */
    deleteObjects(objIds) {
        console.error(`ERROR in ${this.apiName}.deleteObjects(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get all object IDs for given domains as list.
     * @param {eDomain[]} domain Domain (e.g. 'all') or combination as array, e.g. ['geo', 'act'].
     * @returns {ApiResponse<string[]>} Array of object IDs.
     */
    getObjectIds(domain) {
        console.error(`ERROR in ${this.apiName}.getObjectIds(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Filter objects by property values.
     * @param {string[]} objIds - Object IDs of objects to filter.
     * @param {any[]} filterExpression - Filter criteria.
     * @example [ "$ifcEntity##xs:string" , "==" , "IfcWall" ]
     * @example [ "$ifcEntity##xs:string" , "in" , ["IfcWall", "IfcWallStandardCase"]] , "and" , "$cnVolume##xs:double", ">=" , 2.5 ]]
     * @example [ "$ifcEntity##xs:string" , "match" , "/^IfcWall/g"]]
     * @returns {ApiResponse<string[]>} Filtered object IDs.
     */
    filterObjectsByProperties(objIds, filterExpression) {
        console.error(`ERROR in ${this.apiName}.filterObjectsByProperties(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Filter objects by status.
     * @param {string[]} objIds - Object IDs of objects to filter.
     * @param {string} statusCode - one of [ 'selected', 'visible', 'wired', 'locked'].
     * @param {boolean} statusFlag - true: status, false: inverted status.
     * @returns {ApiResponse<string[]>} Filtered object IDs.
     */
    filterObjectsByStatus(objIds, statusCode, statusFlag) {
        console.error(`ERROR in ${this.apiName}.filterObjectsByStatus(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ======== Object Status Methods ========

    /**
     * Get IDs of currently visible objects in given domain/domains.
     * @param {eDomain[]} domains - Domain (e.g. 'all') or combination as array, e.g. ['geo', 'act'].
     * @returns {ApiResponse<string[]>} returns IDs of currently visible objects in given domain/domains
     * @since 10.2025
     */
    getVisibleObjectIds(domains) {
        console.error(`ERROR in ${this.apiName}.getVisibleObjectIds(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Set visibility of objects.
     * @param {string[]} objIds - Object IDs.
     * @param {boolean} flagOnOff - true: visible, false: hidden
     * @param {boolean} exclusively - If the 'exclusivly' flag is set to true, the domains affected are determined by the specified objects.
     *                                The remaining objects in these domains are set to the inverse status.
     * @returns {ApiResponse<number>} Number of objects whose status changed.
     * @since 10.2025
     * 
     */
    setVisible(objIds, flagOnOff, exclusively) {
        console.error(`ERROR in ${this.apiName}.setVisible(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *  Set visibility of all objects of given domains.
     *  @param {eDomain[]}   domains     Array of domains to be taken into account
     *  @param {boolean}     flagOnOff   true = show, false = hide
     *  @returns {ApiResponse<void>}
     *  @example
     *      
     *      // assume a COLNEO pro adapter is instantiated (e.g. in index.js) ...
     *      const { COLNEOproAdapterServices } = await import("./node_modules/@colneo/infohub-js-api/dist/adapter/AdapterColneoPro.js");
     *      Infohub.ModelViewer = await COLNEOproAdapterServices.createAdapter( Infohub.Context )
     *      
     *      // set visibility for objects in domain 'geometry'
     *      Infohub.ModelViewer.setAllVisible( [ Infohub.ModelViewer.eDomain.GEO ], true )
     * 
     *  @since 01.12.2025, ar
     * 
     */
    setAllVisible(domains, flagOnOff) {
        console.error(`ERROR in ${this.apiName}.setAllVisible(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Toggle visibility of given objects.
     * @param {string[]} objIds - Object IDs to toggle.
     * @returns {ApiResponse<void>}
     */
    toggleVisibility(objIds) {
        console.error(`ERROR in ${this.apiName}.toggleVisibility(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Check if object is visible.
     * @param {string} objId - Object ID.
     * @returns {ApiResponse<boolean>} True if object is visible.
     */
    isVisible(objId) {
        console.error(`ERROR in ${this.apiName}.isVisible(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get IDs of currently selected objects.
     * @param {eDomain[]} domains - Domain (e.g. 'all') or combination as array, e.g. ['geo', 'act'].
     * @returns {ApiResponse<string[]>} returns IDs of currently selected objects
     * @since 10.2025
     */
    getSelectedObjectIds(domains) {
        console.error(`ERROR in ${this.apiName}.getSelectedObjectIds(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *  Set selection of objects.
     * 
     *  @param {string[]} objIds - Object IDs.
     *  @param {boolean} flagOnOff - Selection flag.
     *  @param {boolean} exclusively - If true, clear all other selections.
     *      
     * 
     *  @returns {ApiResponse<number>} Number of objects whose selection changed.
     *  @since 10.2025
     */
    setSelected(objIds, flagOnOff, exclusively) {
        console.error(`ERROR in ${this.apiName}.setSelected(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Toggle selection status of given objects.
     * @param {string[]} objIds - Object IDs to toggle.
     * @returns {ApiResponse<void>}
     */
    toggleSelection(objIds) {
        console.error(`ERROR in ${this.apiName}.toggleSelection(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * 
     *  Set all objects of specified domains as selected or deselected.
     * 
     *  Example:
     * 
     *  ```js
     *  // assume a COLNEO pro adapter is instantiated (e.g. in index.js) ...
     *  const { COLNEOproAdapterServices } = await import("./node_modules/@colneo/infohub-js-api/dist/adapter/AdapterColneoPro.js");
     *  Infohub.ModelViewer = await COLNEOproAdapterServices.createAdapter()
     *
     *  // clear selection of objects in domain 'geometry'
     *  Infohub.ModelViewer.setAllSelected( [ Infohub.ModelViewer.eDomain.GEO ], false )
     * ```
     * 
     * 
     *  @param {boolean} flagOnOff
     *  @param {string[]} domains     
     * 
     * @returns {ApiResponse<void>}
     */
    setAllSelected(domains, flagOnOff) {
        console.error(`ERROR in ${this.apiName}.setAllSelected(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Check if object is selected.
     * @param {string} objId - Object ID.
     * @returns {ApiResponse<boolean>} True if object is selected.
     */
    isSelected(objId) {
        console.error(`ERROR in ${this.apiName}.isSelected(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ======== Hierarchy Methods ========

    /**
     * Get parent ID of object.
     * @param {string} objId - Object ID.
     * @returns {ApiResponse<string>} - ID of closest parent
     * @since 10.2025
     */
    getParentId(objId) {
        console.error(`ERROR in ${this.apiName}.getParentId(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get root node of object trees for a given domain.
     * @param {string} domain - Domain (e.g. 'geo')
     * @returns {ApiResponse<string>} returns ID of root node of object trees for a given domain
     * @since 10.2025
     */
    getRootNodeId(domain) {
        console.error(`ERROR in ${this.apiName}.getRootNodeId(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get root nodes of smartsets for given domain.
     * @param {string} domain - Domain (e.g. 'geo')
     * @returns {ApiResponse<string>}
     * @since 10.2025
     */
    getRootNodeIdOfSets(domain) {
        console.error(`ERROR in ${this.apiName}.getRootNodeIdOfSets(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ======== Property Methods ========

    /**
     * Get property value for object.
     * @param {string} objId - Object ID.
     * @param {string} ptKeyStr - Access key of property type in the format name##datatype.
     * @returns {ApiResponse<any>} -alue of property, depending on the datatype
     */
    getPropertyValue(objId, ptKeyStr) {
        console.error(`ERROR in ${this.apiName}.getPropertyValue(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get property values for multiple objects.
     * @param {string[]} objIds - Object IDs.
     * @param {string} ptKeyStr - Access key of property type in the format name##datatype.
     * @returns {ApiResponse<object[]>} Map with object IDs as keys.
     * @example data: {
                    "id1" : {
                        "value": 1.234,
                        "comment": ""
                    },
                    ...
                }
     */
    getPropertyValues(objIds, ptKeyStr) {
        console.error(`ERROR in ${this.apiName}.getPropertyValues(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get unique property values for type.
     * @param {string} ptKeyStr - Access key of property type in the format name##datatype.
     * @param {object} options - Options for filtering.
     * @example * // Example options object:
                * const options = {
                *   objects: ['obj_1', 'obj_2'],
                *   precision: 0.0001,
                *   case_sensitive: true,
                *   interval_size: 2.0,
                *   interval_offset: 1.0,
                *   date_grouping: 'week',
                *   max_values: 0
                * };
     * @returns {ApiResponse<string[]>} returns list of unique property values
     * @since 10.2025
     */
    getPropertyTypeValues(ptKeyStr, options) {
        console.error(`ERROR in ${this.apiName}.getPropertyTypeValues(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get all properties of object.
     * @param {string} objId - Object ID.
     * @param {boolean} inclPropsFromTypes - Include props from types.
     * @param {object} filter - Filter criteria.
     * @example * // Example filter object:
                * const filter = {
                *  name:       "MyPset:MyNumber*",
                *  datatype :  ["xs:double", "xs:float"]
                * };
     * @returns {ApiResponse<object>}
     * @example data: {
                    "MyPset:MyNumber##xs:double": "11",
                    "MyPset:MyNumberNew##xs:float": 42,
                }
     */
    getProperties(objId, inclPropsFromTypes, filter) {
        console.error(`ERROR in ${this.apiName}.getProperties(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get property type metadata.
     * @param {string} ptKeyStr - Access key of property type in the format name##datatype.
     * @returns {ApiResponse<object|null>}
     * @example data: {
                    name: "cnName",
                    displayName: "cn:Name",
                    datatype: "xs:string",
                    readonly: false,
                    ...
                }
     */
    getPropertyTypeInfo(ptKeyStr) {
        console.error(`ERROR in ${this.apiName}.getPropertyTypeInfo(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get property types used by object.
     * @param {string} objId - Object ID.
     * @param {object} filter - Filter criteria.
     * @example * // Example filter object:
                * const filter = {
                *  name:       "MyPset:MyNumber*",
                *  datatype :  ["xs:double", "xs:float"]
                *  readOnly: true
                * };
     * @returns {ApiResponse<object[]>}
     * @example data: [
                    {
                        name: "MyPset:MyNumber",
                        displayName: "MyPset:MyNumber",
                        datatype: "xs:double",
                        readonly: true,
                        ...
                    }
                ]
     *
     */
    getPropertyTypesByObject(objId, filter) {
        console.error(`ERROR in ${this.apiName}.getPropertyTypesByObject(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get a list of all available property types matching filter.
     * @param {object} filter - Filter criteria.
     * @example * // Example filter object:
                * const filter = {
                *  name:       "MyPset:MyNumber*",
                *  datatype :  ["xs:double", "xs:float"]
                *  readOnly: true
                * };
     * @returns {ApiResponse<string[]>}
     */
    getPropertyTypes(filter) {
        console.error(`ERROR in ${this.apiName}.getPropertyTypes(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Set single property value. Synchronizes with Infohub.
     * @param {string} objId - Object ID.
     * @param {string} ptKeyStr - property typeidö in the format name##datatype.
     * @param {string} value - New value.
     * @returns {ApiResponse<boolean>}
     */
    setPropertyValue(objId, ptKeyStr, value) {
        console.error(`ERROR in ${this.apiName}.setPropertyValue(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *
     * Set multiple property values for each object in object list.
     *
     *  @param {string[]}   objIds      - Array of Object IDs.
     *  @param {object}     properties  - Key/value pairs, key format name##datatype
     *
     *  @example // Example properties object:

                    const properties = {
                        "myType##xs:string"     : "ABC",
                        "myNumber##xs:double"   : 1.234,
                        "myBoolean##xs:boolean" : true
                    };

     * @returns {ApiResponse<boolean>}
     */
    setProperties(objIds, properties) {
        console.error(`ERROR in ${this.apiName}.setProperties(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *  Set properties in a batch by a property map object -> properties
     *
     *  @param {*} propertyMap {
     *
     *          "object id 1" : {
     *              "proptypeid1" : value1,
     *              "proptypeid2" : value2
     *          },
     *          "object id 2" : {
     *              "proptypeid1" : value1,
     *              "proptypeid2" : value2,
     * *            "proptypeid3" : value3,
     *          },
     *  }
     *
     *  @returns {ApiResponse<boolean>}
     *
     */
    setPropertiesByMap(propertyMap) {
        console.error(`ERROR in ${this.apiName}.setProperties(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Create or update property type. Synchronizes with Infohub.
     * @param {object} propData - Property type definition.
     * @example * // Example propData objects:
                propData1 = {
                    name: "MyLength",
                    datatype: "xs:double",
                    unit: "m"
                }
                propData2 = {
                    name: "TrafficLight",
                    datatype: "xs:string",
                    values: ["red", "yellow", "green"]
                    readOnly: true
                }
     * @param {boolean} updateExisting - Whether to update existing type.
     * @returns {ApiResponse<boolean>}
     */
    createPropertyType(propData, updateExisting) {
        console.error(`ERROR in ${this.apiName}.createPropertyType(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Delete exsisting property type. Synchronizes with Infohub.
     * 
     * @param {string} ptKeyStr - Access key of property type in the format name##datatype.
     * 
     * @returns {ApiResponse<boolean>}
     * 
     */
    deletePropertyType(ptKeyStr) {
        console.error(`ERROR in ${this.apiName}.deletePropertyType(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }


        
    /**
     * Reset camera to home viewpoint. 
     * 
     *  If a home viewpoint does not exist, the camera and clipping planes are reset
     *  
     *  @example
     *      Infohub.ModelViewer.showHomeViewpoint()     
     *  @returns {ApiResponse<void>}
     *  
     *  @since 01.2026
     * 
     */
    async showHomeViewpoint() {
        console.error(`ERROR in ${this.apiName}.showHomeViewpoint(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get list of all viewpoint ids.
     * 
     * @returns {ApiResponse<string[]>}
     * 
     * @since 10.2025
     */
    getViewpointIds() {
        console.error(`ERROR in ${this.apiName}.getViewpointIds(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Create a new viewpoint.
     * @param {string | null} parentId - ID of parent object
     * @param {object} options
     * @returns {ApiResponse<string>} ID of newly created viewpoint.
     */
    createViewpoint(parentId, options) {
        console.error(`ERROR in ${this.apiName}.createViewpoint(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Update existing viewpoint.
     * @param {string} objId - Object ID
     * @param {object} data - Data for the viewpoint in JSON notation
     * @returns {ApiResponse<boolean>} True if update was successful.
     * @since 10.2025
     */
    updateViewpoint(objId, data) {
        console.error(`ERROR in ${this.apiName}.updateViewpoint(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Activate a viewpoint.
     * @param {string} objId - Object ID
     * @returns {ApiResponse<boolean>} True if viewpoint was activated.
     */
    activateViewpoint(objId) {
        console.error(`ERROR in ${this.apiName}.activateViewpoint(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get viewpoint data.
     * @param {string} objId - Object ID
     * @returns {ApiResponse<object>} JSON object with viewpoint data.
     */
    getViewpointData(objId) {
        console.error(`ERROR in ${this.apiName}.getViewpointData(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Set viewpoint data.
     * @param {string} objId - Object ID
     * @param {object} viewpointData - Viewpoint data to set.
     * @returns {ApiResponse<boolean>} True if viewpoint data was set.
     */
    setViewpointData(objId, viewpointData) {
        console.error(`ERROR in ${this.apiName}.setViewpointData(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Delete a viewpoint.
     * @param {string} objId - Object ID
     * @returns {ApiResponse<boolean>} True if viewpoint was deleted.
     */
    deleteViewpoint(objId) {
        console.error(`ERROR in ${this.apiName}.deleteViewpoint(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ======== Style / Material / Color Methods ========

    /**
     * Get list of Ids of all available styles.
     * @returns {ApiResponse<string[]>} Array of style object Ids.
     * @since 10.2025
     */
    getStyleIds() {
        console.error(`ERROR in ${this.apiName}.getStyleIds(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get styles / materials / colors applied to given objects.
     * @param {string[]} objIDs - Object IDs.
     * @returns {ApiResponse<object[]>} Array of applied style / material objects.
     * @since 10.2025
     */
    getStyles(objIDs) {
        console.error(`ERROR in ${this.apiName}.getStyles(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Get information about specific style / material attributes.
     * @param {string} styleId - Style ID.
     * @param {string} key - If specified, only the corresponding attribute is returned, otherwise the entire object.
     * @returns {ApiResponse<object|string>} Style attribute info.
     * @since 10.2025
     */
    getStyleData(styleId, key) {
        console.error(`ERROR in ${this.apiName}.getStyleData(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Create a new style / material.
     * @param {object} styleData - Style definition.
     * @example * // Example styleData:
     * const styleData = {
            * id: "12345",        // optional
            * name: "MyStyle",    // not empty
            * color: "#rrggbbaa"  // valid color value in hexadecimal RGBA format
            * }
     * @returns {ApiResponse<string>} ID of newly created style.
     */
    createStyle(styleData) {
        console.error(`ERROR in ${this.apiName}.createStyle(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Update style / material.
     * @param {string} styleId - Style ID.
     * @param {object} styleData - Updated style data.
     * @returns {ApiResponse<boolean>} True if style was updated.
     */
    updateStyle(styleId, styleData) {
        console.error(`ERROR in ${this.apiName}.updateStyle(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Delete styles / materials.
     * @param {string[]} styleIds - IDs of styles to delete.
     * @returns {ApiResponse<boolean>} True if styles deleted.
     * @since 10.2025
     */
    deleteStyles(styleIds) {
        console.error(`ERROR in ${this.apiName}.deleteStyles(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Apply style to objects.
     * @param {string} styleId - Style ID.
     * @param {string[]} objIds - Object IDs.
     * @returns {ApiResponse<boolean>} True if objects style has been changed
     */
    applyStyleToObjects(styleId, objIds) {
        console.error(`ERROR in ${this.apiName}.applyStyleToObjects(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Reset styles to default.
     * @param {string[]} objIds - Object IDs.
     * @returns {ApiResponse<boolean>} True if reset succeeded. styles reset to default
     * @since 10.2025
     */
    resetStylesOfObjects(objIds) {
        console.error(`ERROR in ${this.apiName}.resetStylesOfObjects(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * 
     *  Reset styles (materials,colors) of all objects
     * 
     * @returns {ApiResponse<void>}
     * 
     * @example
     *  Infohub.ModelViewer.resetStyles( [ Infohub.ModelViewer.eDomain.GEO ] )
     * 
     */
    async resetStyles(domains) {
        console.error(`ERROR in ${this.apiName}.resetStylesOfObjects(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ======== SmartSet Methods ========

    getSmartSetsRootId(domain) {
        console.error(`ERROR in ${this.apiName}.getSmartSetsRootId(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * 
     * Create new SmartSet.
     * 
     * @param {string}      parentId    Id of parent or domain name (eDomain) for topmost
     * @param {string}      name        Name of the SmartSet.
     * @param {string[]}    objectIds   List of object IDs (root nodes, children will be automatically collected).
     *                                  If all objects should be taken into account, use getObjectsRootId(domain) to get ID of root node of domain
     *                                  and add root node of domain as SmartSet Object only. To add specific objects to a SmartSet (Set by enumeration)
     *                                  use objects which are leafs in the object hierarchy as SmartSet Objects.
     * @param {object|null} options     Additional options for SmartSet creation.
     * 
        {
            "filter": {
                "skip_openings": true,
                "skip_assembly_parts": false,
                "visible_only": false
            },
            "group_by": {
                "propertytypes": [
                    "ifcEntity##xs:string",
                    "cnVolume##xs:double"
                ],
                "options": {
                    "decimal_precision": 4,
                    "case_sensitive": true,
                    "skip_null_values": true,
                    "interval_size": 5.0,
                    "interval_offset": 0.0,
                    "date_grouping": "week"
                }
            }
        }
     * 
     * @returns {ApiResponse<string>}   ID of newly created SmartSet.
     * 
     * @since 10.2025
     * @update 09.12.2025, ar
     */
    async createSmartSet(parentId, name, objectIds, options = null) {
        console.error(`ERROR in ${this.apiName}.createSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }


    /**
     * Delete a SmartSet.
     * @param {string} smartSetId - Id of SmartSet.
     * @returns {ApiResponse<boolean>} True if deleted.
     * @since 10.2025
     */
    async deleteSmartSet(smartSetId) {
        console.error(`ERROR in ${this.apiName}.deleteSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Add objects to SmartSet.
     * @param {string} smartSetId - SmartSet ID.
     * @param {string[]} objIds - Object IDs.
     * @param {boolean} includeChildren true: tree structure, false: flat list
     * @returns {ApiResponse<int>}      Number of added objects
     * @since 10.2025
     */
    async addObjectsToSmartSet(smartSetId, objIds, includeChildren) {
        console.error(`ERROR in ${this.apiName}.addObjectsToSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Remove objects from SmartSet.
     * @param {string} smartSetId - SmartSet ID.
     * @param {string[]} objIds - Object IDs to remove.
     * @returns {ApiResponse<boolean>} True if objects effectivily removed
     * @since 10.2025
     */
    async removeObjectsFromSmartSet(smartSetId, objIds) {
        console.error(`ERROR in ${this.apiName}.removeObjectsFromSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Create SmartSets from schema.
     * @param {object} schema - Schema definition.
     * @returns {ApiResponse<string[]>} Array of SmartSet IDs.
     */
    createSmartSetsFromSchema(schema) {
        console.error(`ERROR in ${this.apiName}.createSmartSetsFromSchema(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Update SmartSet. Synchronizes with Infohub.
     * @param {string} smartSetId - SmartSet ID.
     * @returns {ApiResponse<boolean>} True if SmartSet was updated.
     */
    updateSmartSet(smartSetId) {
        console.error(`ERROR in ${this.apiName}.updateSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Add style to SmartSet.
     * @param {string} smartSetId - ID the SmartSet.
     * @param {object} options
     * @returns {ApiResponse<string>} ID of newly created style.
     * @since 10.2025
     */
    addStyleSchemaToSmartSet(smartSetId, options) {
        console.error(`ERROR in ${this.apiName}.addStyleSchemaToSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Remove style from SmartSet.
     * @param {string} smartSetId - ID the SmartSet.
     * @returns {ApiResponse<boolean>} True if successfully removed.
     * @since 10.2025
     */
    removeStyleSchemaFromSmartSet(smartSetId) {
        console.error(`ERROR in ${this.apiName}.removeStyleSchemaFromSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Activate style schema scheme.
     * @param {string} smartSetId - ID the SmartSet.
     * @returns {ApiResponse<boolean>} True if successfully activated.
     * @since 10.2025
     */
    activateStyleSchemaScheme(smartSetId) {
        console.error(`ERROR in ${this.apiName}.activateStyleSchemaScheme(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Add viewpoint to SmartSet.
     * @param {string} smartSetId - ID the SmartSet.
     * @param {object} options
     * @returns {ApiResponse<status>}
     * @since 10.2025
     */
    addViewpointToSmartSet(smartSetId, options) {
        console.error(`ERROR in ${this.apiName}.addViewpointToSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * remove viewpoint to SmartSet.
     * @param {string} smartSetId - ID the SmartSet.
     * @returns {ApiResponse<boolean>} True if successfully removed.
     * @since 10.2025
     */
    removeViewpointFromSmartSet(smartSetId) {
        console.error(`ERROR in ${this.apiName}.removeViewpointFromSmartSet(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *
     * Open URL/Link in Browser (target = null -> default browser )
     *
     * @param {*} url
     * @param {*} target
     */
    openUrl(url, target) {
        console.error(`ERROR in ${this.apiName}.openUrl(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ------------
    /**
    * 
    * return {
        'intern'    : [ ix , iy , iz ],
        'global'    : [  x ,  y ,  z ]
        }
    * 
    */
    getPickedPoint() {
        console.error(`ERROR in ${this.apiName}.getPickedPoint(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *
     *  Rotate and Translate object.
     * 
     *  Rotation is executed first around point 'rotatePoint' with rotation matrix 'rotateMat6rix',
     *  then object is translated.
     * 
     *  if includeContained is true ont only the geometry of the object itself is transformed but 
     *  also the included objects ('id' could be a model, a container or an assembly)
     *
     * @param {*} id
     * @param {*} T = {
     *  'move' : [],
     *  'rotate' : {
     *      'pivot' : [ x , y , z ],
     *      'alpha' : val_deg
     *  }    
     * }
     * 
     *  @returns {ApiResponse}
     */
    async transformObject(id, T, includeContained) {
        console.error(`ERROR in ${this.apiName}.openUrl(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    // ------------

    /**
     * Check overlap of oriented bounding boxes (OBBs) for given IDs every object against every other object.
     * Objects having no geometry are ignored.
     * @param {string[]} idList
     * @param {number} overlapAllowed - Minimum overlap to report (0.0 - 1.0), default: 0.1
     * @param {function|null} statusCallback - Optional callback function to report progress status.
     * @returns {ApiResponse<object[]>} data: Array of overlapping OBB pairs. [ {
                            id1: id1,
                            id2: id2,
                            maxOverlap: 0.9
                        }, ... ]
     */
    checkOverlapObjectList(idList, overlapAllowed = 0.1, statusCallback = null) {
        console.error(`ERROR in ${this.apiName}.checkOverlapObjectList(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Calculate optimized oriented bounding boxes (OBBs) for given object IDs.
     * @param {string[]} objIds
     * @returns {ApiResponse<object>} Number of affected objects
     */
    async calcOptOBB(objIds) {
        console.error(`ERROR in ${this.apiName}.checkOverlapObjectList(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * Calculate overlap  of oriented bounding boxes (OBBs) for two given object IDs.
     * @param {string} id1
     * @param {string} id2
     * @returns {ApiResponse<number>} max overlap. example: 0.9 for 90% overlap
     */
    async checkOverlapObjects(id1, id2) {
        console.error(`ERROR in ${this.apiName}.checkOverlapObjectList(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     * 
     *  Set viewer configuration. <br>
     *  Changes configuration partially!
     * 
     *  @param {*} cfg = {
     *      "edges" : bool,
     *      "grid"  : {
     *          "show"  : bool,
     *          "color" : #hex
     *      },
     *      "background" : {
     *          "color" : #hex
     *      }
     * }
     * 
     *  @returns {Promise<ApiResponse>}
     * 
     */
    async setConfig( cfg ) {
        console.error(`ERROR in ${this.apiName}.checkOverlapObjectList(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *  @returns {Promise<ApiResponse>}
     */
    async getConfig() {
        console.error(`ERROR in ${this.apiName}.checkOverlapObjectList(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *
     * DEPRECATED !
     * 
     * @param {boolean} onOff
     * @returns {ApiResponse}
     * @since 28.11.2025, ar
     */
    setDrawEdges(onOff) {
        console.error(`ERROR in ${this.apiName}.setDrawEdges(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

    /**
     *  DEPRECATED !
     * @returns {ApiResponse<boolean>}
     * @since 28.11.2025, ar
     */
    getDrawEdges() {
        console.error(`ERROR in ${this.apiName}.getDrawEdges(): No model viewer instantiated.`)
        return Promise.reject("No model viewer instantiated");
    }

}