diff --git a/src/index.js b/src/index.js index f17074053aaaaae18bcd79943151dccda353d39e..858aee80f32744975863462a8dcc1f0b5abb80c7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,20 @@ -import { ToolboxType, WindowSlot, NotificationType } from '@vcmap/ui'; -import { Cesium3DTileStyle, Math as CMath, Ellipsoid, Cartesian3, ConstantProperty, Transforms, HeadingPitchRoll, Cesium3DTileset, Matrix4 } from '@vcmap-cesium/engine'; -import { AbstractInteraction, EventType } from '@vcmap/core'; -import { name, version, mapVersion } from '../package.json'; -import BIMOptions, { bimOptionsId } from './upload.vue'; -import objectList, { objectListId } from './objectList.vue'; -import scaleControl, { scaleControlId } from './scaleControl.vue'; -import * as Cesium from "@vcmap-cesium/engine"; +import {NotificationType, ToolboxType, WindowSlot} from '@vcmap/ui'; +import * as Cesium from '@vcmap-cesium/engine'; +import { + Cartesian3, + Cesium3DTileset, + Cesium3DTileStyle, + ConstantProperty, + Ellipsoid, + HeadingPitchRoll, + Math as CMath, + Transforms +} from '@vcmap-cesium/engine'; +import {AbstractInteraction, EventType} from '@vcmap/core'; +import {mapVersion, name, version} from '../package.json'; +import BIMOptions, {bimOptionsId} from './upload.vue'; +import objectList, {loadCatalogModel, objectListId} from './objectList.vue'; +import scaleControl, {scaleControlId} from './scaleControl.vue'; let model; @@ -56,6 +65,7 @@ function setScale(scaleValue) { const position = currentlyScaledModel.position.getValue(); const orientation = currentlyScaledModel.orientation; const uri = currentlyScaledModel.model.uri; + const fromCatalog = currentlyScaledModel.fromCatalog; vcMap.getEntities().remove(currentlyScaledModel); removePlacedModel(currentlyScaledModel); @@ -70,6 +80,7 @@ function setScale(scaleValue) { scale: scaleValue, show: true, }, + fromCatalog: fromCatalog, }); placedModels.push({ model: newModel, @@ -102,6 +113,7 @@ function saveObjects() { position: [wgs84X, wgs84Y, height], heading: heading, scale: m.model.model.scale.getValue(), + fromCatalog: m.model.fromCatalog.valueOf(), }); } outputObject.placedObjects = outputArray; @@ -127,7 +139,7 @@ function saveFile(fileName, urlFile){ } -function setupModel(vcsApp, windowPos, url, name) { +function setupModel(vcsApp, windowPos, url, name, fromCatalog) { const vcMap = vcsApp.maps.activeMap; const scene = vcMap.getScene(); const ray = scene.camera.getPickRay(windowPos); @@ -141,6 +153,7 @@ function setupModel(vcsApp, windowPos, url, name) { show: true, scale: 1, }, + fromCatalog: fromCatalog, }); placedModels.push({ model: model, @@ -180,7 +193,6 @@ class MoveInteraction extends AbstractInteraction { let xDif = pickResult.x - initialPickResult.x; let yDif = pickResult.y - initialPickResult.y; let zDif = pickResult.z - initialPickResult.z; - // pickResult.z = pickResult.z + heightDif; let pos = initialPosition.clone(); pos.x = pos.x + xDif; pos.y = pos.y + yDif; @@ -289,8 +301,7 @@ class RotateAction extends AbstractInteraction { if (hpRoll.heading < 0.0) { hpRoll.heading += CMath.TWO_PI; } - const orientation = new ConstantProperty(Transforms.headingPitchRollQuaternion(modelPos, hpRoll)); - model.orientation = orientation; + model.orientation = new ConstantProperty(Transforms.headingPitchRollQuaternion(modelPos, hpRoll)); } return event; } @@ -343,7 +354,7 @@ export default function smartVillagesPlugin(config, baseUrl) { const objects = JSON.parse(content); const vcMap = app.maps.activeMap; for (const loadingObject of objects.placedObjects) { - let objectUrl = undefined; + let objectUrl; for (const loadedObject of pluginState.objects) { if (loadedObject.name === loadingObject.name) { objectUrl = loadedObject.url; @@ -351,12 +362,29 @@ export default function smartVillagesPlugin(config, baseUrl) { } } if (objectUrl === undefined) { - app.notifier.add({ - type: NotificationType.ERROR, - message: "Objekt mit dem namen: " + loadingObject.name + " konnte nicht in den geladenen Objekten gefunden werden. Konfiguration konnte nicht vollständig geladen werden.", - }); - // abort - return; + if (loadingObject.fromCatalog === true){ + // Load the model into memory + const model = await loadCatalogModel(loadingObject.name).catch(e => { + console.error(e); + app.notifier.add({ + type: NotificationType.ERROR, + message: "Katalogobject: " + loadingObject.name + " konnte nicht geladenen werden. Konfiguration konnte nicht vollständig geladen werden.", + }); + return undefined; + }); + if (model === undefined) { + return; + } else { + objectUrl = model.url; + } + }else { + app.notifier.add({ + type: NotificationType.ERROR, + message: "Objekt mit dem namen: " + loadingObject.name + " konnte nicht in den geladenen Objekten gefunden werden. Konfiguration konnte nicht vollständig geladen werden.", + }); + // abort + return; + } } const cartPos = Cartesian3.fromDegrees(loadingObject.position[0], loadingObject.position[1], loadingObject.position[2]); // Add the model to the viewer @@ -367,13 +395,13 @@ export default function smartVillagesPlugin(config, baseUrl) { uri: objectUrl, scale: loadingObject.scale, show: true, + fromCatalog: loadingObject.fromCatalog, }, }); const roll = new HeadingPitchRoll(); hpRollMap.set(model, roll); roll.heading = loadingObject.heading; - const orientation = new ConstantProperty(Transforms.headingPitchRollQuaternion(model.position.getValue(), roll)); - model.orientation = orientation; + model.orientation = new ConstantProperty(Transforms.headingPitchRollQuaternion(model.position.getValue(), roll)); placedModels.push({ model: model, @@ -527,11 +555,6 @@ export default function smartVillagesPlugin(config, baseUrl) { } const map = vcsApp.maps.getByType("CesiumVisualisationType"); - console.log(map); - - // tileset.allTilesLoaded.addEventListener(function() { - // console.log('All tiles are loaded'); - // }); const bimGroup = { id: 'bim-functions', diff --git a/src/objectList.vue b/src/objectList.vue index 7045585a936dd38db584f79f9e287b12ed9e14f7..1b896716ad1d0b61f34da0e4e30f0ddc7b49c226 100644 --- a/src/objectList.vue +++ b/src/objectList.vue @@ -6,10 +6,9 @@ Die Objektbibliothek. @@ -113,7 +112,7 @@ import { VcsTreeview, VcsTreeviewTitle, } from '@vcmap/ui'; -import {VCard, VCol, VContainer, VDialog, VForm, VRow, VSheet, VSwitch,} from 'vuetify/components'; +import {VCard, VCol, VContainer, VDialog, VForm, VRow, VSheet, VSwitch, VExpansionPanels} from 'vuetify/components'; import {computed, inject, onMounted, ref} from 'vue'; import {name} from '../package.json'; import * as THREE from 'three'; @@ -126,21 +125,21 @@ const preloadedModels = ref({}); const defaultItems = []; const items = ref(defaultItems); -let setupModelFunction = undefined; -let app = undefined; -let projectStateObject = undefined; +let setupModelFunction; +let app; +let projectStateObject; -async function loadCataloguesFromConfig(config) { +async function loadCatalogsFromConfig(config) { const configJson = JSON.parse(JSON.stringify(config)); const catalogues = Object.values(configJson.objectCatalogues); for (const cat of catalogues) { - const catJson = await retrieveCatalogueJson(cat.url); + const catJson = await retrieveCatalogJson(cat.url); if (!catJson) continue; const category = cat.category.toString(); const linkPrefix = cat.url.replace(/\/[^/]*catalog\.json$/, '/'); - const entries = getCatalogueEntries(catJson, linkPrefix); + const entries = getCatalogEntries(catJson, linkPrefix); if (!objLib.value[category]) { objLib.value[category] = entries; @@ -152,21 +151,22 @@ async function loadCataloguesFromConfig(config) { } -async function retrieveCatalogueJson(link) { +async function retrieveCatalogJson(link) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); try { const response = await fetch(link, { signal: controller.signal }); if (!response.ok) { - throw new Error(`HTTP error ${response.status} for ${link}`); + console.error(`Received HTTP error ${response.status} for ${link}`); + return null; } return await response.json(); } catch (error) { if (error.name === 'AbortError') { - console.warn(`Fetch aborted (timeout) for: ${link}`); - } else { - console.error(`Fetch failed for ${link}:`, error); + console.error(`Catalog.json request timed out: ${link}`); + }else{ + console.error(`Encountered error during catalog.json request: ${link}`, error); } return null; } finally { @@ -174,7 +174,7 @@ async function retrieveCatalogueJson(link) { } } -function getCatalogueEntries(json, linkPrefix) { +function getCatalogEntries(json, linkPrefix) { const part = {}; @@ -210,72 +210,75 @@ function getCatalogueEntries(json, linkPrefix) { return part; } -async function retrieveCatalogModel(modelLink){ - if(preloadedModels.value[modelLink] != null){ - const catModel = preloadedModels.value[modelLink]; +async function placeCatalogModel(modelLink){ + loadCatalogModel(modelLink).then(model => { + const catModel = model; const x = window.innerWidth / 2; const y = window.innerHeight / 2; const cart = new Cartesian2(x, y); - setupModelFunction(app, cart, catModel.url, catModel.modelLink); - } else { - const loader = new GLTFLoader(); - loader.load( - modelLink, - function (gltf) { - const bounds = new THREE.Box3().setFromObject(gltf.scene); - const x = bounds.min.x + (bounds.max.x - bounds.min.x) / 2; - const z = bounds.min.z + (bounds.max.z - bounds.min.z) / 2; - const m = new THREE.Matrix4().makeTranslation(-x, -bounds.min.y, -z); - gltf.scene.applyMatrix4(m); - - const exporter = new GLTFExporter(); - const options = { - binary: true, - animations: gltf.animations, - }; + setupModelFunction(app, cart, catModel.url, catModel.modelLink, true); + }).catch(error => { + console.error(error); + app.notifier.add({ + type: NotificationType.ERROR, + message: + 'Ein Fehler ist beim Laden des Katalogobjektes aufgetreten.', + timeout: 20000, + }); + }); +} - exporter.parse( - gltf.scene, - function (output) { - const blob = new Blob([output], { type: 'application/octet-stream' }); - const url = URL.createObjectURL(blob); - - projectStateObject.objects.push({ - name: modelLink, - url: url, - }); - const x = window.innerWidth / 2; - const y = window.innerHeight / 2; - const cart = new Cartesian2(x, y); - setupModelFunction(app, cart, url, modelLink); - preloadedModels.value[modelLink] = { - url: url, - modelLink: modelLink, - }; - }, - function (error) { - app.notifier.add({ - type: NotificationType.ERROR, - message: - 'Ein Fehler ist beim Laden der glTF Datei aufgetreten. Bitte vergewissern Sie sich, dass es sich um eine gültige glTF2.0 Datei handelt.', - timeout: 20000, - }); - }, - options - ); - }, - undefined, - function (error) { - app.notifier.add({ - type: NotificationType.ERROR, - message: - 'Ein Fehler ist beim Laden der glTF Datei aufgetreten. Bitte vergewissern Sie sich, dass es sich um eine gültige glTF2.0 Datei handelt.' + - ' Fehlermeldung: ' + error.message, - timeout: 20000, - }); - } - ); - } +export async function loadCatalogModel(modelLink){ + return new Promise( (resolve, reject) => { + if(preloadedModels.value[modelLink] != null){ + // Model is already in memory + resolve(preloadedModels.value[modelLink]); + } else{ + const loader = new GLTFLoader(); + loader.load( + modelLink, + function (gltf) { + const bounds = new THREE.Box3().setFromObject(gltf.scene); + const x = bounds.min.x + (bounds.max.x - bounds.min.x) / 2; + const z = bounds.min.z + (bounds.max.z - bounds.min.z) / 2; + const m = new THREE.Matrix4().makeTranslation(-x, -bounds.min.y, -z); + gltf.scene.applyMatrix4(m); + + const exporter = new GLTFExporter(); + const options = { + binary: true, + animations: gltf.animations, + }; + + exporter.parse( + gltf.scene, + function (output) { + const blob = new Blob([output], { type: 'application/octet-stream' }); + const url = URL.createObjectURL(blob); + + projectStateObject.objects.push({ + name: modelLink, + url: url, + }); + preloadedModels.value[modelLink] = { + url: url, + modelLink: modelLink, + }; + resolve(preloadedModels.value[modelLink]); + }, + function (error) { + reject(error); + }, + options + ); + }, + undefined, + function (error) { + reject(error); + } + ); + } + }) } function uploadModel() { @@ -303,8 +306,6 @@ function uploadModel() { }); let url = URL.createObjectURL(blob); - - const loader = new GLTFLoader(); loader.load(url, function(gltf) { let bounds = new THREE.Box3().setFromObject(gltf.scene); @@ -337,7 +338,7 @@ function uploadModel() { const x = window.innerWidth / 2; const y = window.innerHeight / 2; const cart = new Cartesian2(x, y); - setupModelFunction(app, cart, url, name); + setupModelFunction(app, cart, url, name, false); }, } ] @@ -346,10 +347,11 @@ function uploadModel() { }, // called when there is an error in the generation - function ( error ) { + function ( error ) { app.notifier.add({ type: NotificationType.ERROR, - message: "Ein Fehler ist beim Laden der glTF Datei aufgetreten. Bitte vergewissern Sie sich, dass es sich um eine gültige glTF2.0 Datei handelt.", + message: "Ein Fehler ist beim Laden der glTF Datei aufgetreten. Bitte vergewissern Sie sich, dass es sich um eine gültige glTF2.0 Datei handelt." + + " Fehlermeldung: " + error.message, timeout: 20000, }); }, options); @@ -362,8 +364,6 @@ function uploadModel() { }); }); } - - reader.readAsArrayBuffer(file); } }); @@ -403,6 +403,7 @@ function uploadModel() { VContainer, VRow, VCol, + VExpansionPanels, }, setup() { app = inject('vcsApp'); @@ -430,8 +431,7 @@ function uploadModel() { const dialog = ref(false); setupModelFunction = setupModel; projectStateObject = pluginState; - // loadCataloguesFromConfig(config).then(response => {console.log(objLib);}); - onMounted(()=>loadCataloguesFromConfig(config).then(response => {console.log(objLib)})); + onMounted(()=>loadCatalogsFromConfig(config).then(() => {console.log("Object library loaded")})); return { draggable, selectable, @@ -525,8 +525,8 @@ function uploadModel() { loadModel() { uploadModel(); }, - fetchCatalogueModel(modelLink) { - retrieveCatalogModel(modelLink); + placeCatalogModel(modelLink) { + placeCatalogModel(modelLink); }, loadConfiguration() { loadObjects(app);