/**
* $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)
}
}
Source