Source

adapter/AdapterDesite_34_40.js

/**
 * $Id$
 */

'use strict'

import { IModelViewer } from "./IModelViewer.js";
import { DESITE_COMMON } from "./AdapterDesite.js";
import { ApiResponse } from "../tools/ApiResponse.js";


/**
 * 
 * @category Model Viewer
 * 
 * @ignore
 * @classdesc Encapsulate functions common for DESITE version 3.4 and VDC-Manager 4.0. Not indended to be importable.
 */
export class DESITE_COMMON_34_40 extends DESITE_COMMON {

    constructor(t, n, v) {
        super(t, n, v)
    }

    //-----------------------------HELPERS------------------------------------------


    /** 
     * Maps internal object IDs to their corresponding external IDs.
     *
     * This method intentionally wraps and extends `desiteAPI.mapToExternalIDs`.
     * It should be used instead of the Desite API method whenever fallback
     * behavior is required.
     *
     * Mapping logic:
     * 1. Attempt to map all internal IDs using `desiteAPI.mapToExternalIDs`
     * 2. For IDs without a direct external mapping:
     *    - If the object is part of a composite (`cpIsPartOfComposite === true`),
     *      inherit the external ID of its parent object
     *    - Otherwise, fall back to using the internal ID itself
     *
     * This guarantees that every provided internal ID appears in the result
     * object, even if no external ID exists.
     *
     *
     * @async
     * @function mapToExternalIDs
     *
     * @param {string[]} internalIDs
     * Array of internal object IDs to map
     *
     * @returns {Promise<Record<string, string|null>>}
     * A promise resolving to an object where:
     * - keys are internal IDs
     * - values are external IDs, inherited parent external IDs,
     *   the internal ID itself, or `null` if no mapping could be resolved
     * @since 2025-12-16 RM
     */
    async mapToExternalIDs(internalIDs) {
        if (internalIDs != null || internalIDs.length != 0) {  //todo: better check condition
            let mappedPartial = await desiteAPI.mapToExternalIDs(internalIDs);

            let mappedObj = {};
            for (let internalID of internalIDs) {
                mappedObj[internalID] = mappedPartial[internalID] || null;
            }

            let missingExternalIDs = internalIDs.filter(id => !mappedObj[id]);

            for (let internalID of missingExternalIDs) {

                let composite = await desiteAPI.getPropertyValue(internalID, 'cpIsPartOfComposite', 'xs:boolean');

                if (composite) {

                    let parentID = await desiteAPI.getParent(internalID);
                    if (parentID) {
                        let parentMapping = await desiteAPI.mapToExternalIDs([parentID]);
                        let parentExternalID = parentMapping[parentID] || null;
                        if (parentExternalID) {
                            mappedObj[internalID] = parentExternalID;
                        }
                    }
                } else {
                    mappedObj[internalID] = internalID
                }
            }
            return mappedObj;
        } else {
            return {};
        }

    }

    /** 
     * Resolves the most appropriate internal ID for a given external object ID.
     *
     * This method wraps `desiteAPI.mapFromExternalIDs` and applies additional
     * resolution rules when multiple internal IDs are mapped to the same
     * external ID.
     *
     * Resolution logic:
     * 1. Map the external ID to internal IDs using `desiteAPI.mapFromExternalIDs`
     * 2. If no internal IDs are found, fall back to the original external ID
     * 3. If exactly one internal ID is found, return it
     * 4. If multiple internal IDs are found:
     *    - Prefer the first ID whose domain is NOT `"building"`
     *    - If all IDs belong to the `"building"` domain, fall back to the first ID
     *
     * This guarantees that the function always returns a usable ID.
     *
     * @async
     * @function resolveInternalId
     * @param {string} objId External object ID to resolve
     * @returns {Promise<string>} A promise resolving to the selected internal ID, 
     * or the original `objId` if no mapping could be resolved
     * 
     * @since 2025-12-16 RM
     */
    async resolveInternalId(objId) {
        const mapped = await desiteAPI.mapFromExternalIDs(objId);

        const mappedIds = Object.values(mapped)
            .flat()
            .filter(Boolean);

        // If nothing was mapped → fallback to original ID
        if (!mappedIds || mappedIds.length === 0) {
            return objId;
        }

        // Single mapped ID → return it
        if (mappedIds.length === 1) {
            return mappedIds[0];
        }

        // Multiple IDs → pick the first non-building domain ID
        for (const id of mappedIds) {
            const domain = await desiteAPI.getDomainByElement(id);
            if (domain !== "building") {
                return id;
            }
        }

        // All were "building" or none qualified → fallback to first
        return mappedIds[0];
    }
    //------------------------------------------------------------------------------

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

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

    /**
    * @since 01.2026, SW
    */
    async getModelIds(domains) {
        try {

            domains = [].concat(domains).join(";");

            const modelIds = await desiteAPI.getModelListByDomain(domains);

            if (!Array.isArray(modelIds) || modelIds.length === 0) {
                console.log(`No model IDs found for domains: ${domains}`);
                return new ApiResponse(
                    200,
                    [],
                    "No model IDs found."
                );
            }

            // get rootcontainerids for modelids
            const rootNodeIds = await Promise.all(
                modelIds.map(modelId => desiteAPI.getRootNodeByModel(modelId))
            );

            if (!Array.isArray(rootNodeIds) || rootNodeIds.length === 0) {
                return new ApiResponse(
                    200,
                    [],
                    "No model IDs found."
                );
            }

            let mapped = await this.mapToExternalIDs(rootNodeIds);

            let externalRootIds = Object.values(mapped);

            return new ApiResponse(
                200,
                externalRootIds,
                "Model IDs retrieved successfully."
            );
        } catch (err) {
            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while retrieving model IDs."
            );
        }
    }

    /**
     * @since 01.2026, SW - missing in IModelViewer
     */
    async getModelIdByObjectId(objId, domain = 'all') {

        try {

            domain = [].concat(domain).join(";");

            // Resolve correct internal ID
            let internalId;
            internalId = await this.resolveInternalId(objId);

            let modelId = await desiteAPI.getModelByElement(internalId, domain)

            let mapped = await this.mapToExternalIDs([modelId]);

            let externalModelId = Object.values(mapped).flat().filter(Boolean);

            if (externalModelId.length === 0) {
                console.log(`No IDs found for object: ${objId}`);
                return new ApiResponse(
                    200,
                    [],
                    "No model IDs found."
                );
            } else {
                return new ApiResponse(
                    200,
                    externalModelId,
                    "Model ID successfully retrieved."
                );
            }

        } catch (err) {
            return new ApiResponse(
                400,
                null,
                err.message
            );
        }

    }

    /**
     * @since 01.2026, SW
     */
    async createModel(name, domain = "geometry", settings) {

        try {

            domain = Array.isArray(domain) ? domain : [domain];

            const id = await desiteAPI.createModel(name, false, domain);

            if (id === -1) {
                return new ApiResponse(
                    500,
                    null,
                    `Model could not be created for domain '${domain}'.`
                );
            }

            if (id === -2) {
                return new ApiResponse(
                    400,
                    null,
                    `Domain '${domain}' is unknown or not unique.`
                );
            }

            const rootNodeId = await desiteAPI.getRootNodeByModel(id);
            if (!rootNodeId || rootNodeId <= 0) {
                return new ApiResponse(
                    500,
                    null,
                    `Could not retrieve root node for model ID ${id}.`
                );
            }

            const externalRootId = await this.mapToExternalIDs([rootNodeId]);

            return new ApiResponse(
                200,
                externalRootId,
                `New model '${name}' was successfully created in domain '${domain}'.`
            );

        } catch (err) {
            console.error("Error in createModel:", err);
            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while creating model."
            );
        }
    }

    /**
     * @since 01.2026, SW
     */
    async getChildIds(objId, depth = 0) {
        try {

            // Resolve internal ID
            let internalId;
            try {
                internalId = await this.resolveInternalId(objId);
            } catch (err) {
                throw new Error("Failed to resolve internal object ID.");
            }

            const containedInternalIds = await desiteAPI.getContainedElements(internalId, depth, true);

            let externalIds = []

            if (containedInternalIds != '' && containedInternalIds != null) {
                if (!Array.isArray(containedInternalIds)) {
                    return new ApiResponse(
                        500,
                        null,
                        "Invalid response from desiteAPI.getContainedElements(). Expected an array."
                    );
                }

                //If parent is composite, don't return itself as it's children
                let isComposite = await desiteAPI.getPropertyValue(internalId, "cpIsComposite", "xs:boolean")
                let mappedExternal

                if (isComposite === true) {
                    externalIds = containedInternalIds

                } else {
                    mappedExternal = await this.mapToExternalIDs(containedInternalIds);
                    externalIds = Object.values(mappedExternal);
                }
            }

            return new ApiResponse(
                200,
                externalIds,
                `Retrieved ${externalIds.length} child IDs for object '${objId}' (depth=${depth}).`
            );

        } catch (err) {
            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while retrieving child IDs."
            )
        }
    }

    /** 
    * Desite v3.4 and v4.0
    * @since 2025-12-16
    * @updated 01.2026, SW - use of eDomain in script, 
    * @example 
    * const eDomain = Infohub.ModelViewer.constructor.eDomain;
    * let geoObjectIds = await Infohub.ModelViewer.getObjectIds([eDomain.GEO]);
    */
    async getObjectIds(domain) {
        try {

            domain = [].concat(domain).join(";");

            let idlist = await desiteAPI.getAllElements(domain);

            // Map internal IDs → external IDs
            const mapped = await this.mapToExternalIDs(idlist);


            if (!mapped || typeof mapped !== "object") {
                return new ApiResponse(
                    400,
                    null,
                    "Mapping failed. Invalid API response."
                );
            }

            const externalIds = Object.values(mapped);

            return new ApiResponse(
                200,
                externalIds,
                `Successfully retrieved ${externalIds.length} external object IDs.`
            );

        } catch (err) {
            console.error("Error in getObjectIds:", err);

            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while retrieving object IDs."
            );
        }
    }

    /**
     * 
     * @since 01.2026, SW
     */
    async filterObjectsByProperties(objIds, filterExpression) {

        try {

            const mapped = await desiteAPI.mapFromExternalIDs(objIds);
            const internalIds = Object.values(mapped)
                .flat()
                .filter(Boolean);

            let filteredList = internalIds;
            let logicOperator = "and";
            let currentIndex = 0;

            while (currentIndex < filterExpression.length) {
                const token = filterExpression[currentIndex];

                if (Array.isArray(token) === false && typeof token[0] === "string") {
                    const possibleLogic = token[0].toLowerCase();
                    if (possibleLogic === "and" || possibleLogic === "or") {
                        logicOperator = possibleLogic;
                        currentIndex++;
                        continue;
                    }
                }

                // Expect 3 consecutive arrays for one condition: [property], [operator], [value]
                const propertyArr = filterExpression[currentIndex];
                const operatorArr = filterExpression[currentIndex + 1];
                const valueArr = filterExpression[currentIndex + 2];

                if (!propertyArr || !operatorArr || !valueArr) {
                    return new ApiResponse(
                        400,
                        null,
                        "Invalid filter expression format."
                    );
                }

                const propertyKey = propertyArr[0];
                const operator = operatorArr[0];
                const value = valueArr[0];

                if (!propertyKey || !operator) {
                    reject({
                        status: 400,
                        message: "Filter condition missing property or operator.",
                        data: null
                    });
                    return;
                }

                // Extract property name and datatype
                const [propertyName, propertyDatatype] = propertyKey.split("##");

                // Construct filter pattern
                let filterPattern = "";
                switch (operator) {
                    case "==":
                        filterPattern = `${value}`;
                        break;
                    case "!=":
                        filterPattern = `!${value}`;
                        break;
                    case ">":
                    case ">=":
                    case "<":
                    case "<=":
                        filterPattern = `${operator}${value}`;
                        break;
                    case "in":
                        if (Array.isArray(value)) {
                            filterPattern = value.join(" ");
                        } else {
                            filterPattern = `${value}`;
                        }
                        break;
                    case "match":
                        filterPattern = `${value}`;
                        break;
                    default:
                        filterPattern = `${value}`;
                        break;
                }

                const result = await desiteAPI.filterByProperty(
                    filteredList,
                    propertyName,
                    propertyDatatype,
                    filterPattern,
                    false,
                    "all"
                );

                // Combine with previous results
                if (logicOperator === "and") {
                    filteredList = filteredList.filter(id => result.includes(id));
                } else if (logicOperator === "or") {
                    filteredList = Array.from(new Set([...filteredList, ...result]));
                }

                currentIndex += 3;
            }

            let externalIds = await this.mapToExternalIDs(filteredList);
            externalIds = Object.values(mapped);

            return new ApiResponse(
                200,
                externalIds,
                `Filtering complete. ${filteredList.length} objects match criteria.`
            );

        } catch (err) {
            console.error("Error in filterObjectsByProperties:", err);
            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while filtering objects."
            );
        }

    }

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

    /**
    * Desite v3.4 and v4.0
    * @since 2025-12-16 
    * @updated 01.2026, SW - use of eDomain in script, e.g await Infohub.ModelViewer.getSelectedObjectIds([eDomain.GEO]);
    */
    async getSelectedObjectIds(domain) {
        try {

            domain = [].concat(domain).join(";");

            let idlist = await desiteAPI.getSelectedElements(domain);

            // Map internal IDs → external IDs
            const mapped = await this.mapToExternalIDs(idlist);

            if (!mapped || typeof mapped !== "object") {
                return new ApiResponse(
                    400,
                    null,
                    "Mapping failed. Invalid API response."
                );
            }

            const externalIds = Object.values(mapped);

            return new ApiResponse(
                200,
                externalIds,
                `Successfully retrieved ${externalIds.length} external object IDs.`
            );

        } catch (err) {
            console.error("Error in getSelectedObjectIds:", err);

            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while retrieving object IDs."
            );
        }
    }

    /**
    * Desite v3.4 and v4.0 
    * @since 2025-12-16
    * @updated 01.2026, SW - use of eDomain in script, e.g await Infohub.ModelViewer.getVisibleObjectIds([eDomain.GEO]);
    */
    async getVisibleObjectIds(domain) {
        try {
            domain = [].concat(domain).join(";");

            let idlist = await desiteAPI.getVisibleElements(domain);

            // Map internal IDs → external IDs
            const mapped = await this.mapToExternalIDs(idlist);

            if (!mapped || typeof mapped !== "object") {
                return new ApiResponse(
                    400,
                    null,
                    "Mapping failed. Invalid API response."
                );
            }

            const externalIds = Object.values(mapped);

            return new ApiResponse(
                200,
                externalIds,
                `Successfully retrieved ${externalIds.length} external object IDs.`
            );

        } catch (err) {
            console.error("Error in getVisibleObjectIds:", err);

            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while retrieving object IDs."
            );
        }
    }


    /**
     * Desite v3.4 and v4.0 
     * @since 2025-12-16
     */
    async setPropertyValue(objId, ptKeyStr, value) {

        console.log(`### DESITE 34/40<setPropertyValue( ${objId} )>`)

        try {

            const { propertyName, propertyDatatype } = this.parsePropertyKey(ptKeyStr);

            // normalize value
            if (typeof value === 'object') {
                value = JSON.stringify(value);
            }

            // Resolve internal ID
            const internalId = await this.resolveInternalId(objId);

            // Set property value via DESITE
            let code;

            try {
                code = await desiteAPI.setPropertyValue(internalId, propertyName, propertyDatatype, value);
            } catch (err) {
                return new ApiResponse(500, null, "Failed to set property value: " + (err.message || ""));
            }

            // Interpret DESITE return code
            let message;
            let status;

            switch (code) {
                case 1:
                    status = 200;
                    message = "Property value was set successfully.";
                    break;
                case 0:
                    status = 500;
                    message = "Undefined result. No action taken.";
                    break;
                case -1:
                    status = 404;
                    message = "Error: Object not found.";
                    break;
                case -2:
                    status = 400;
                    message = "Error: Unknown data type.";
                    break;
                case -3:
                    status = 400;
                    message = "Error: Writing was rejected (read-only or wrong format).";
                    break;
                case -4:
                    status = 400;
                    message = "Error: Illegal value type.";
                    break;
                default:
                    status = 500;
                    message = "Unknown return code.";
            }

            return new ApiResponse(
                status,
                code,
                message
            );

        } catch (err) {
            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while setting property value."
            );
        }
    }


    /*
    * Desite v3.4 and v4.0
    * @since 2025-12-16
    */
    async getPropertyValue(objId, ptKeyStr) {
        try {
            const { propertyName, propertyDatatype } =
                this.parsePropertyKey(ptKeyStr);

            // Resolve correct internal ID
            const internalId = await this.resolveInternalId(objId);

            // Retrieve value from DESITE
            let value;
            try {
                value = await desiteAPI.getPropertyValue(
                    internalId,
                    propertyName,
                    propertyDatatype
                );
            } catch (err) {
                return new ApiResponse(
                    500,
                    null,
                    "Failed to retrieve property value."
                );
            }

            if (value === undefined) {
                return new ApiResponse(
                    404,
                    null,
                    `No property value found for ${ptKeyStr}.`
                );
            }

            return new ApiResponse(
                200,
                value,
                `Successfully retrieved property value for ${ptKeyStr}.`
            );

        } catch (err) {
            return new ApiResponse(
                500,
                null,
                err.message || "Internal error while retrieving property value."
            );
        }
    }

    /*
    * Desite v3.4 and v4.0
    * @since 2025-12-16
    */
    async getPropertyTypeValues(ptKeyStr, options = {}) {
        try {
            const { propertyName, propertyDatatype } =
                this.parsePropertyKey(ptKeyStr);

            const objList = options.objects || [];      // array of objectIds
            const maxValues = options.max_values ?? 0;  // 0 = unlimited
            const domain = options.domain || "all";
            const checkInherited = options.checkInherited ?? true;
            const getNulls = options.getNullValues ?? false;

            const mapped = await desiteAPI.mapFromExternalIDs(objList);
            const internalIds = Object.values(mapped).flat().filter(Boolean);

            const values = await desiteAPI.getPropertyValuesByObjectList(
                propertyName,
                propertyDatatype,
                checkInherited,
                internalIds,
                maxValues,
                domain,
                getNulls
            );

            return new ApiResponse(
                200,
                values,
                "Values retrieved successfully."
            );

        } catch (err) {
            console.error("Error in getPropertyTypeValues:", err);

            return new ApiResponse(
                500,
                null,
                err.message ||
                "Internal error while retrieving property type values for an object list."
            );
        }
    }

    getAsJSON(objid) {
        return desiteAPI.mapFromExternalIDs(objid)
            .then(mapped => {

                const objidNotExternalArray = Object.values(mapped)
                    .flat()
                    .filter(Boolean);

                if (!objidNotExternalArray || objidNotExternalArray.length === 0) {
                    throw new Error(`No DESITE ID found for external ID: ${objid}`);
                }

                const objidNotExternal = objidNotExternalArray[0];

                return desiteAPI.getAsJSON(objidNotExternal)
            });

    }

    //!ptKeyStr in pname und ptype
    getPropertyValuesByObjectList(ptKeyStr, checkInherited, objectIdList, maxValues, domains, getNullValues) {
        return desiteAPI.mapFromExternalIDs(objectIdList)
            .then(mapped => {

                const objectIdListNotExternal = Object.values(mapped)
                    .flat()
                    .filter(Boolean);

                if (objectIdListNotExternal.length === 0) {
                    throw new Error(`No DESITE IDs found for given external IDs: ${JSON.stringify(objectIdList)}`);
                }

                return desiteAPI.getPropertyValuesByObjectList(
                    propertyName,
                    propertyDatatype,
                    checkInherited,
                    objectIdListNotExternal,
                    maxValues,
                    domains,
                    getNullValues
                );
            });
    }


    getPropertyValuesByObject(objid, filterpattern) {
        return desiteAPI.mapFromExternalIDs(objid)
            .then(mapped => {

                const objidNotExternalArray = Object.values(mapped)
                    .flat()
                    .filter(Boolean);

                if (!objidNotExternalArray || objidNotExternalArray.length === 0) {
                    throw new Error(`No DESITE ID found for external ID: ${objid}`);
                }

                const objidNotExternal = objidNotExternalArray[0];

                return desiteAPI.getPropertyValuesByObject(objidNotExternal, filterpattern)
            });

    }

    setAllVisible(repaint, domains) {
        return desiteAPI.setAllElementsVisible(true, domains)
    }

    /**
     * @updated 01.2026, SW
     */
    async setVisible(objIds, flagOnOff, exclusively) {

        try {

            const internalIds = await Promise.all(
                objIds.map(objId => this.resolveInternalId(objId))
            );

            const idsArray = Array.isArray(internalIds)
                ? internalIds.map(String)
                : internalIds.split(",").map(s => s.trim());

            // Exclusive mode: hide everything else first
            if (exclusively) {

                const domains = await Promise.all(
                    internalIds.map(async (objId) => {
                        const domain = await desiteAPI.getDomainByElement(objId.toString());
                        return domain;
                    })
                );

                const uniqueDomains = [...new Set(domains.filter(d => d))];

                await desiteAPI.setAllElementsVisible(false, uniqueDomains);

                await desiteAPI.setElementsVisible(true, idsArray);

                return new ApiResponse(
                    200,
                    idsArray.length,
                    `Exclusively set ${idsArray.length} elements visible in ${uniqueDomains.length} domain(s).`
                );

            } else {
                // Non-exclusive: simply set visibility on the given elements
                desiteAPI.setElementsVisible(flagOnOff, idsArray)
                    .then(() => {
                        return new ApiResponse(
                            200,
                            {
                                idsArray,
                                flagOnOff,
                                exclusively: false
                            },
                            `Successfully set visibility=${flagOnOff} for ${idsArray.length} elements.`
                        );
                    })
                    .catch(err => {
                        console.error("Error in setVisible (non-exclusive):", err);
                        return new ApiResponse(
                            500,
                            null,
                            err.message || "Internal error while setting visibility."
                        );
                    });
            }

        } catch (err) {
            return new ApiResponse(
                400,
                null,
                err.message || "Unexpected error in setVisible."
            );
        }
    }

    setSelected(objIds, flagOnOff, exclusively) {
        if (exclusively) {
            let domainPromises = objIds.map(objId => desiteAPI.getDomainByElement(objId));

            return Promise.all(domainPromises)
                .then(domains => {
                    let uniqueDomains = [...new Set(domains)];

                    return desiteAPI.setAllElementsSelected(flagOnOff, uniqueDomains);
                })
                .then(() => {
                    return desiteAPI.setElementsSelected(flagOnOff, objIds);
                });
        } else {
            return desiteAPI.setElementsSelected(flagOnOff, objIds);
        }
    }

    // GEOMETRY    

    /**
     * Different return JSON than DESITE 3.2
     */
    getMaterial(matId) {
        return desiteAPI.getMaterialData(matId)
    }

    checkRegExp(value, pattern) {
        try {
            const regex = new RegExp(pattern);
            return regex.test(value);
        } catch (e) {
            return false;
        }
    }
}

/**
 *  @category Model Viewer
 *
 *  @classdesc
 *      Implements the interface to model viewer.
 *      Overwrites functions for DESITE 3.4.
 *
 *  @extends {IModelViewer}
 *
 */
export class AdptDESITE_34 extends DESITE_COMMON_34_40 {

    constructor() {
        super("DESITE_34", "DESITE", "3.4")
        // desiteAPI.selectionChanged.connect(this.onSelectionChanged)
        // desiteAPI.visibilityChanged.connect(this.onVisibilityChanged)
        desiteAPI.selectionChanged.connect(this.onSelectionChanged.bind(this));
        desiteAPI.visibilityChanged.connect(this.onVisibilityChanged.bind(this));
    }

    async openUrl(url, target) {
        return new ApiResponse(200, await desiteAPI.openLink(url, true));
    }

    async getPickedPoint() {

        const p = await desiteAPI.getPickedPoint()
        // console.log( "picked: " + JSON.stringify(p) )     

        // get model coordinates ...

        // const pglobal = p['p']
        // const pintern = p['ip']

        let picked = {
            'intern': p['ip'],
            'coordinates': p['p']
        }

        // console.log( "picked: " + JSON.stringify(pglobal) )     
        return new ApiResponse(200, picked);

    }

}

/**
 *  @category Model Viewer
 *
 *  @classdesc
 *      Model Viewer Adapter. <br>
 *      Implements the interface to model viewer.
 *      Overwrites functions for VDC 4.0.
 *
 *  @extends {IModelViewer}
 *
 */
export class AdptVDC_40 extends DESITE_COMMON_34_40 {

    constructor() {
        super("DESITE_40", "DESITE", "4.0")
        vdcApp.selectionChanged.connect(this.onSelectionChanged)
        vdcApp.visibilityChanged.connect(this.onVisibilityChanged)
    }

}