Commit 12fb48eb authored by Matthias Betz's avatar Matthias Betz
Browse files

platzieren mit geolocation

parent 3e7044fb
import {NotificationType, ToolboxType, WindowSlot} from '@vcmap/ui';
import { NotificationType, ToolboxType, WindowSlot } from '@vcmap/ui';
import * as Cesium from '@vcmap-cesium/engine';
import {
Cartesian3,
......@@ -8,14 +8,13 @@ import {
Ellipsoid,
HeadingPitchRoll,
Math as CMath,
Transforms
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';
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;
let conditions = [[true, true]];
......@@ -30,18 +29,21 @@ let hpRoll;
let app;
let currentlyScaledModel;
const hidingFeatures = [];
const hidingListener = function(tile) {
const hidingListener = function (tile) {
let allHidden = true;
for (const featureToHide of hidingFeatures) {
if (featureToHide.loops < 10) {
allHidden = false;
const feature = tile.content.getFeature(featureToHide.id);
if (feature !== undefined) {
app.maps.activeMap.layerCollection.globalHider.addFeature("manual", feature);
app.maps.activeMap.layerCollection.globalHider.hideObjects(["manual"]);
app.maps.activeMap.layerCollection.globalHider.addFeature(
'manual',
feature,
);
app.maps.activeMap.layerCollection.globalHider.hideObjects(['manual']);
featureToHide.loops++;
}
}
}
}
}
if (allHidden) {
const primitives = app.maps.activeMap.getScene().primitives;
......@@ -51,10 +53,16 @@ const hidingListener = function(tile) {
continue;
}
tileSet.tileVisible.removeEventListener(hidingListener);
}
}
}
};
const pluginState = {
files: [],
objects: [],
geolocations: {},
};
function updateHideCondition(gmlId) {
conditions.unshift(['${id} === "' + gmlId + '"', false]);
}
......@@ -104,7 +112,9 @@ function saveObjects() {
if (tempRoll !== undefined) {
heading = tempRoll.heading;
}
const ellipPos = Ellipsoid.WGS84.cartesianToCartographic(m.model.position.getValue());
const ellipPos = Ellipsoid.WGS84.cartesianToCartographic(
m.model.position.getValue(),
);
const wgs84X = CMath.toDegrees(ellipPos.longitude);
const wgs84Y = CMath.toDegrees(ellipPos.latitude);
const height = ellipPos.height;
......@@ -113,7 +123,7 @@ function saveObjects() {
position: [wgs84X, wgs84Y, height],
heading: heading,
scale: m.model.model.scale.getValue(),
fromCatalog: m.model.fromCatalog.valueOf(),
fromCatalog: m.model.fromCatalog.valueOf(),
});
}
outputObject.placedObjects = outputArray;
......@@ -123,13 +133,13 @@ function saveObjects() {
}
outputObject.hiddenObjects = hiddenObjects;
let textData = JSON.stringify(outputObject);
let blobData = new Blob([textData], {type: "application/json"});
let blobData = new Blob([textData], { type: 'application/json' });
saveFile('ObjektPositionierung.json', window.URL.createObjectURL(blobData));
}
function saveFile(fileName, urlFile){
let a = document.createElement("a");
a.style = "display: none";
function saveFile(fileName, urlFile) {
let a = document.createElement('a');
a.style = 'display: none';
document.body.appendChild(a);
a.href = urlFile;
a.download = fileName;
......@@ -138,12 +148,79 @@ function saveFile(fileName, urlFile){
a.remove();
}
async function placeModelAtPosition(vcMap, lon, lat, modelUrl, name) {
// Create Cartographic position
const position = Cesium.Cartographic.fromDegrees(lon, lat);
// Wait for terrain height
const [updatedPosition] = await Cesium.sampleTerrainMostDetailed(
vcMap.getCesiumWidget().terrainProvider,
[position],
);
const height = updatedPosition.height + 10;
console.log("Height: " + height);
console.log("Lon: " + lon);
console.log("Lat: " + lat);
const cartesianPosition = Cartesian3.fromDegrees(lon, lat, height);
console.log(cartesianPosition);
// Add the model to the viewer
model = vcMap.getEntities().add({
name: name,
position: cartesianPosition,
model: {
uri: modelUrl,
show: true,
scale: 1,
},
fromCatalog: false,
});
placedModels.push({
model: model,
name: name,
});
vcMap.requestRender();
vcMap.getCesiumWidget().camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1500),
orientation: {
heading: Cesium.Math.toRadians(0.0), // rotation from north
pitch: Cesium.Math.toRadians(-45.0), // negative to look down
roll: 0.0,
},
duration: 2.0, // seconds
});
}
function setupModel(vcsApp, windowPos, url, name, fromCatalog) {
const vcMap = vcsApp.maps.activeMap;
const geolocation = pluginState.geolocations[name];
if (geolocation === undefined) {
setupModelWithoutGeolocation(
vcMap,
windowPos,
center,
name,
url,
fromCatalog,
vcsApp,
);
} else {
const lon = geolocation.lon;
const lat = geolocation.lat;
placeModelAtPosition(vcMap, lon, lat, url, name);
}
}
function setupModelWithoutGeolocation(
vcMap,
windowPos,
center,
name,
url,
fromCatalog,
vcsApp,
) {
const scene = vcMap.getScene();
const ray = scene.camera.getPickRay(windowPos);
const center = scene.globe.pick(ray, scene);
center = scene.globe.pick(ray, scene);
// Add the model to the viewer
model = vcMap.getEntities().add({
name: name,
......@@ -162,11 +239,14 @@ function setupModel(vcsApp, windowPos, url, name, fromCatalog) {
vcMap.requestRender();
const interaction = new PickPlaceInteraction();
interaction.setActive(EventType.CLICKMOVE);
activeMouseEvent = vcsApp.maps.eventHandler.addExclusiveInteraction(interaction, () => {});
activeMouseEvent = vcsApp.maps.eventHandler.addExclusiveInteraction(
interaction,
() => {},
);
}
function removePlacedModel(modelToBeRemoved) {
placedModels = placedModels.filter(function(m) {
placedModels = placedModels.filter(function (m) {
return m.model !== modelToBeRemoved;
});
}
......@@ -180,10 +260,7 @@ class MoveInteraction extends AbstractInteraction {
} else if (event.pointerEvent === 3 && model !== undefined) {
const cesiumMap = event.map;
const scene = cesiumMap.getScene();
const pickResult = scene.globe.pick(
event.ray,
scene
);
const pickResult = scene.globe.pick(event.ray, scene);
if (initialPickResult === undefined) {
initialPickResult = pickResult;
}
......@@ -204,7 +281,6 @@ class MoveInteraction extends AbstractInteraction {
}
}
class PickPlaceInteraction extends AbstractInteraction {
async pipe(event) {
if (event.pointerEvent === 2) {
......@@ -214,14 +290,11 @@ class PickPlaceInteraction extends AbstractInteraction {
} else if (event.pointerEvent === 3 && model !== undefined) {
const cesiumMap = event.map;
const scene = cesiumMap.getScene();
const pickResult = scene.globe.pick(
event.ray,
scene
);
const pickResult = scene.globe.pick(event.ray, scene);
pickResult.z = pickResult.z + heightDif;
model.position.setValue(pickResult);
}
return event;
}
}
......@@ -252,11 +325,15 @@ class HeightInteraction extends AbstractInteraction {
if (dif === 0) {
return event;
}
heightDif = heightDif - (dif / 50);
heightDif = heightDif - dif / 50;
let pos = model.position.getValue();
let cart = Cesium.Cartographic.fromCartesian(pos);
cart.height -= (dif/50);
pos = Cesium.Cartesian3.fromRadians(cart.longitude, cart.latitude, cart.height);
cart.height -= dif / 50;
pos = Cesium.Cartesian3.fromRadians(
cart.longitude,
cart.latitude,
cart.height,
);
currentMousePosition = [event.windowPosition.x, event.windowPosition.y];
model.position.setValue(pos);
}
......@@ -301,7 +378,9 @@ class RotateAction extends AbstractInteraction {
if (hpRoll.heading < 0.0) {
hpRoll.heading += CMath.TWO_PI;
}
model.orientation = new ConstantProperty(Transforms.headingPitchRollQuaternion(modelPos, hpRoll));
model.orientation = new ConstantProperty(
Transforms.headingPitchRollQuaternion(modelPos, hpRoll),
);
}
return event;
}
......@@ -313,11 +392,6 @@ class RotateAction extends AbstractInteraction {
* @returns {import("@vcmap/ui/src/vcsUiApp").VcsPlugin<PluginConfig, PluginState>}
*/
export default function smartVillagesPlugin(config, baseUrl) {
const pluginState = {
files: [],
objects: [],
};
return {
get name() {
return name;
......@@ -334,22 +408,22 @@ export default function smartVillagesPlugin(config, baseUrl) {
saveObjects,
loadObjects(app) {
// Create an input element
const inputElement = document.createElement("input");
const inputElement = document.createElement('input');
// Set its type to file
inputElement.type = "file";
// Set accept to the file types you want the user to select.
inputElement.type = 'file';
// Set accept to the file types you want the user to select.
// Include both the file extension and the mime type
inputElement.accept = ".json, .txt";
inputElement.accept = '.json, .txt';
// set onchange event to call callback when user has selected file
inputElement.addEventListener("change", e => {
inputElement.addEventListener('change', (e) => {
const fileInput = e.target;
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = async readerEvent => {
reader.onload = async (readerEvent) => {
const content = readerEvent.target.result;
const objects = JSON.parse(content);
const vcMap = app.maps.activeMap;
......@@ -362,31 +436,44 @@ export default function smartVillagesPlugin(config, baseUrl) {
}
}
if (objectUrl === undefined) {
if (loadingObject.fromCatalog === true){
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;
});
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 {
} 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.",
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]);
const cartPos = Cartesian3.fromDegrees(
loadingObject.position[0],
loadingObject.position[1],
loadingObject.position[2],
);
console.log(cartPos);
// Add the model to the viewer
model = vcMap.getEntities().add({
name: loadingObject.name,
......@@ -401,8 +488,13 @@ export default function smartVillagesPlugin(config, baseUrl) {
const roll = new HeadingPitchRoll();
hpRollMap.set(model, roll);
roll.heading = loadingObject.heading;
model.orientation = new ConstantProperty(Transforms.headingPitchRollQuaternion(model.position.getValue(), roll));
model.orientation = new ConstantProperty(
Transforms.headingPitchRollQuaternion(
model.position.getValue(),
roll,
),
);
placedModels.push({
model: model,
name: loadingObject.name,
......@@ -422,21 +514,20 @@ export default function smartVillagesPlugin(config, baseUrl) {
const tileSet = primitives.get(i);
if (!(tileSet instanceof Cesium3DTileset)) {
continue;
}
}
tileSet.style = new Cesium3DTileStyle({
show: {
conditions: conditions,
},
});
show: {
conditions: conditions,
},
});
}
}
}
};
reader.readAsText(file);
});
// dispatch a click event to open the file dialog
inputElement.dispatchEvent(new MouseEvent("click"));
inputElement.dispatchEvent(new MouseEvent('click'));
},
get config() {
return config;
......@@ -444,11 +535,17 @@ export default function smartVillagesPlugin(config, baseUrl) {
initialize(vcsApp) {
app = vcsApp;
vcsApp.contextMenuManager.addEventHandler(async (event) => {
vcsApp.contextMenuManager.addEventHandler(async (event) => {
const actions = [];
const pick = vcsApp.maps.activeMap.getScene().pick(event.windowPosition);
if (pick !== undefined && pick.primitive !== undefined && pick.primitive.id !== undefined) {
model = pick.primitive.id
const pick = vcsApp.maps.activeMap
.getScene()
.pick(event.windowPosition);
if (
pick !== undefined &&
pick.primitive !== undefined &&
pick.primitive.id !== undefined
) {
model = pick.primitive.id;
actions.push({
id: 'move',
name: 'Objekt verschieben',
......@@ -457,7 +554,13 @@ export default function smartVillagesPlugin(config, baseUrl) {
initialPickResult = undefined;
const interaction = new MoveInteraction();
interaction.setActive(EventType.CLICKMOVE);
activeMouseEvent = vcsApp.maps.eventHandler.addExclusiveInteraction(interaction, () => { console.log("removed") });
activeMouseEvent =
vcsApp.maps.eventHandler.addExclusiveInteraction(
interaction,
() => {
console.log('removed');
},
);
},
});
actions.push({
......@@ -466,7 +569,13 @@ export default function smartVillagesPlugin(config, baseUrl) {
callback() {
const interaction = new HeightInteraction();
interaction.setActive(EventType.CLICKMOVE);
activeMouseEvent = vcsApp.maps.eventHandler.addExclusiveInteraction(interaction, () => { console.log("removed") });
activeMouseEvent =
vcsApp.maps.eventHandler.addExclusiveInteraction(
interaction,
() => {
console.log('removed');
},
);
},
});
actions.push({
......@@ -480,7 +589,13 @@ export default function smartVillagesPlugin(config, baseUrl) {
}
const interaction = new RotateAction();
interaction.setActive(EventType.CLICKMOVE);
activeMouseEvent = vcsApp.maps.eventHandler.addExclusiveInteraction(interaction, () => { console.log("removed") });
activeMouseEvent =
vcsApp.maps.eventHandler.addExclusiveInteraction(
interaction,
() => {
console.log('removed');
},
);
},
});
......@@ -495,12 +610,12 @@ export default function smartVillagesPlugin(config, baseUrl) {
component: scaleControl,
slot: WindowSlot.DYNAMIC_LEFT,
state: {
headerTitle: "Skalierung",
headerTitle: 'Skalierung',
},
},
name,
);
}
},
});
actions.push({
......@@ -511,13 +626,12 @@ export default function smartVillagesPlugin(config, baseUrl) {
removePlacedModel(model);
},
});
} else if (event.feature) {
} else if (event.feature) {
actions.push({
id: 'delete',
name: 'Objekt verstecken',
callback() {
const gmlId = event.feature.getProperty("id");
const gmlId = event.feature.getProperty('id');
if (gmlId !== undefined) {
updateHideCondition(gmlId);
event.feature.tileset.style = new Cesium3DTileStyle({
......@@ -531,16 +645,19 @@ export default function smartVillagesPlugin(config, baseUrl) {
});
} else {
const { activeMap } = vcsApp.maps;
activeMap.layerCollection.globalHider.addFeature("manual", event.feature);
activeMap.layerCollection.globalHider.addFeature(
'manual',
event.feature,
);
hidingFeatures.push({
id: event.feature.featureId,
loops: 0,
});
activeMap.layerCollection.globalHider.hideObjects(["manual"]);
activeMap.layerCollection.globalHider.hideObjects(['manual']);
}
},
});
}
}
return actions;
}, name);
this._app = vcsApp;
......@@ -550,23 +667,14 @@ export default function smartVillagesPlugin(config, baseUrl) {
* @returns {Promise<void>}
*/
async onVcsAppMounted(vcsApp) {
for (const map of vcsApp.maps) {
console.log(map);
}
const map = vcsApp.maps.getByType("CesiumVisualisationType");
const bimGroup = {
id: 'bim-functions',
type: ToolboxType.GROUP,
icon: "ifc",
icon: 'ifc',
disabled: false,
title: 'BIM Funktionen',
};
vcsApp.toolboxManager.add(
bimGroup,
name,
);
vcsApp.toolboxManager.add(bimGroup, name);
/**
* @type {Array<import("@vcmap/ui").ButtonComponentOptions>}
......@@ -589,7 +697,7 @@ export default function smartVillagesPlugin(config, baseUrl) {
width: 400,
},
state: {
headerTitle: "IFC nach gltf Konvertierung",
headerTitle: 'IFC nach gltf Konvertierung',
},
},
name,
......@@ -611,7 +719,7 @@ export default function smartVillagesPlugin(config, baseUrl) {
component: objectList,
slot: WindowSlot.DYNAMIC_LEFT,
state: {
headerTitle: "Objektverwaltung",
headerTitle: 'Objektverwaltung',
},
},
name,
......@@ -631,15 +739,10 @@ export default function smartVillagesPlugin(config, baseUrl) {
},
},
},
];
const groupButtonManager = vcsApp.toolboxManager.get(
'bim-functions',
).buttonManager;
buttonComponents.forEach((b) =>
groupButtonManager.add(b, name),
);
const groupButtonManager =
vcsApp.toolboxManager.get('bim-functions').buttonManager;
buttonComponents.forEach((b) => groupButtonManager.add(b, name));
},
/**
* @param {boolean} forUrl
......@@ -655,10 +758,9 @@ export default function smartVillagesPlugin(config, baseUrl) {
const options = {};
return options;
},
i18n: {
},
i18n: {},
destroy() {
// empty
},
};
}
\ No newline at end of file
}
......@@ -7,26 +7,22 @@
</template>
<template #default>
<v-expansion-panels multiple>
<VcsExpansionPanel v-for = "(subcats, categoryName) in objLib" :key="categoryName" :heading="categoryName">
<v-container v-for="(entries, subCategory) in subcats" :key = "subCategory" class=" py-1 px-1 border-b-md">
<v-col>
<VcsLabel class="mb-4 text-h2">{{subCategory}}</VcsLabel>
<v-list>
<v-row>
<vcs-button
v-for="entry in entries"
:key="entry.title"
@click="placeCatalogModel(entry.model)"
<VcsExpansionPanel v-for="(subcats, categoryName) in objLib" :key="categoryName" :heading="categoryName">
<v-container v-for="(entries, subCategory) in subcats" :key="subCategory" class=" py-1 px-1 border-b-md">
<v-col class="list-col">
<VcsLabel class="mb-4 text-h2">{{ subCategory }}</VcsLabel>
<v-list class="no-scroll">
<v-row>
<vcs-button v-for="entry in entries" :key="entry.title" @click="placeCatalogModel(entry.model)"
class="p-0 mx-1 my-0 border gc-2"
style="width: auto; height: auto; margin-left: 1rem; margin-bottom: 1px;"
variant="filled"
>
<img :src="entry.icon" :alt="entry.icon" style="display: block; max-width: 100%; max-height: 100%;" />
</vcs-button>
</v-row>
</v-list>
style="width: auto; height: auto; margin-left: 1rem; margin-bottom: 1px;" variant="filled">
<img :src="entry.icon" :alt="entry.icon"
style="display: block; max-width: 100%; max-height: 100%;" />
</vcs-button>
</v-row>
</v-list>
</v-col>
<v-divider/>
<v-divider />
</v-container>
</VcsExpansionPanel>
</v-expansion-panels>
......@@ -35,38 +31,28 @@
</VcsFormSection>
<VcsFormSection heading="Objektliste">
<template #help>
<p>Objektliste:</p>
<span>Hier werden alle geladenen Objekte aufgelistet.
Über das Dreipunktemenü kann das geladene Objekt platziert werden.
</span>
<p>Hinweis: Die Objektinformationsabfrage wird für die Platzierung deaktiviert. Sie kann in der
Toolbar mit dem i wieder aktiviert werden.
</p>
<p>Objektliste:</p>
<span>Hier werden alle geladenen Objekte aufgelistet.
Über das Dreipunktemenü kann das geladene Objekt platziert werden.
</span>
<p>Hinweis: Die Objektinformationsabfrage wird für die Platzierung deaktiviert. Sie kann in der
Toolbar mit dem i wieder aktiviert werden.
</p>
</template>
<v-container class="py-1 px-1">
<v-row no-gutters>
<vcs-list
:items="items"
:draggable="draggable"
:selectable="selectable"
:single-select="selectSingle"
:searchable="searchable"
:show-title="showTitle"
:icon="titleIconSrc"
:actions="titleActionsArray"
:title="title"
v-model="selected"
@item-moved="move"
@item-renamed="({ item, newTitle }) => (item.title = newTitle)"
/>
<vcs-list :items="items" :draggable="draggable" :selectable="selectable" :single-select="selectSingle"
:searchable="searchable" :show-title="showTitle" :icon="titleIconSrc" :actions="titleActionsArray"
:title="title" v-model="selected" @item-moved="move"
@item-renamed="({ item, newTitle }) => (item.title = newTitle)" />
</v-row>
</v-container>
</VcsFormSection>
<VcsFormSection heading="Objekte Hinzufügen">
<template #help>
<p>Objekte laden:</p>
<span>Über den Objekte laden Knopf können glTF/glb Modelle der Objektliste hinzugefügt werden.</span>
<p>Objekte laden:</p>
<span>Über den Objekte laden Knopf können glTF/glb Modelle der Objektliste hinzugefügt werden.</span>
</template>
<v-container class="py-1 px-1">
<v-row no-gutters>
......@@ -76,11 +62,12 @@
</VcsFormSection>
<VcsFormSection heading="Objekt Positionierung">
<template #help>
<p>Positionierung speichern/laden:</p>
<span>Hier können versteckte Gebäude und neu platzierte Objekte an ihrer jetzigen Position gespeichert werden
und wieder geladen werden.
</span>
<p><b>Wichtig:</b> Vor dem Laden einer Positionsdatei müssen die in der Datei beschriebenen Gebäude vorher in die Objektliste geladen werden.
<p>Positionierung speichern/laden:</p>
<span>Hier können versteckte Gebäude und neu platzierte Objekte an ihrer jetzigen Position gespeichert werden
und wieder geladen werden.
</span>
<p><b>Wichtig:</b> Vor dem Laden einer Positionsdatei müssen die in der Datei beschriebenen Gebäude vorher in
die Objektliste geladen werden.
Dies geschieht nicht automatisch mit dem Laden der Positionsdatei.</p>
</template>
<v-container class="py-1 px-1">
......@@ -96,7 +83,7 @@
</v-row>
</v-container>
</VcsFormSection>
</v-sheet>
</v-sheet>
</template>
<script>
......@@ -112,13 +99,13 @@ import {
VcsTreeview,
VcsTreeviewTitle,
} from '@vcmap/ui';
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 { VCard, VCol, VContainer, VDialog, VForm, VRow, VSheet, VSwitch, VExpansionPanels, VList } from 'vuetify/components';
import { computed, inject, onMounted, ref } from 'vue';
import { name } from '../package.json';
import * as THREE from 'three';
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
import {GLTFExporter} from 'three/addons/exporters/GLTFExporter.js';
import {Cartesian2} from '@vcmap-cesium/engine';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
import { Cartesian2 } from '@vcmap-cesium/engine';
const objLib = ref({});
const preloadedModels = ref({});
......@@ -129,6 +116,33 @@ let setupModelFunction;
let app;
let projectStateObject;
function addModelToObjectList(name, url) {
if (projectStateObject === undefined) {
// object library wasn't opened before
return;
}
projectStateObject.objects.push({
name: name,
url: url,
});
const item = {
name: name,
title: name,
actions: [
{
name: 'Objekt platzieren',
callback() {
const x = window.innerWidth / 2;
const y = window.innerHeight / 2;
const cart = new Cartesian2(x, y);
setupModelFunction(app, cart, url, name, false);
},
}
]
};
items.value.push(item);
}
async function loadCatalogsFromConfig(config) {
const configJson = JSON.parse(JSON.stringify(config));
const catalogues = Object.values(configJson.objectCatalogues);
......@@ -165,7 +179,7 @@ async function retrieveCatalogJson(link) {
} catch (error) {
if (error.name === 'AbortError') {
console.error(`Catalog.json request timed out: ${link}`);
}else{
} else {
console.error(`Encountered error during catalog.json request: ${link}`, error);
}
return null;
......@@ -178,39 +192,39 @@ function getCatalogEntries(json, linkPrefix) {
const part = {};
if (!json || !Array.isArray(json.items)) {
console.warn('Invalid or missing `json.items`:', json);
return part;
}
if (!json || !Array.isArray(json.items)) {
console.warn('Invalid or missing `json.items`:', json);
return part;
}
for (const item of json.items) {
const subCat = item.title || 'Unnamed';
const entries = [];
for (const item of json.items) {
const subCat = item.title || 'Unnamed';
const entries = [];
if (Array.isArray(item.predefinedObjects)) {
for (const entry of item.predefinedObjects) {
if (typeof entry !== 'object' || entry === null) {
console.warn('Invalid entry:', entry);
continue;
}
if (Array.isArray(item.predefinedObjects)) {
for (const entry of item.predefinedObjects) {
if (typeof entry !== 'object' || entry === null) {
console.warn('Invalid entry:', entry);
continue;
}
const title = entry.title || 'Untitled';
const icon = linkPrefix + (entry.icon || '');
const model = linkPrefix + (entry.properties?.olcs_modelUrl || '');
const title = entry.title || 'Untitled';
const icon = linkPrefix + (entry.icon || '');
const model = linkPrefix + (entry.properties?.olcs_modelUrl || '');
entries.push({ title, icon, model });
}
} else {
console.warn('Expected array for predefinedObjects, got:', item.predefinedObjects);
entries.push({ title, icon, model });
}
part[subCat] = entries;
} else {
console.warn('Expected array for predefinedObjects, got:', item.predefinedObjects);
}
return part;
part[subCat] = entries;
}
async function placeCatalogModel(modelLink){
return part;
}
async function placeCatalogModel(modelLink) {
loadCatalogModel(modelLink).then(model => {
const catModel = model;
const x = window.innerWidth / 2;
......@@ -222,65 +236,104 @@ async function placeCatalogModel(modelLink){
app.notifier.add({
type: NotificationType.ERROR,
message:
'Ein Fehler ist beim Laden des Katalogobjektes aufgetreten.',
'Ein Fehler ist beim Laden des Katalogobjektes aufgetreten.',
timeout: 20000,
});
});
}
export async function loadCatalogModel(modelLink){
return new Promise( (resolve, reject) => {
if(preloadedModels.value[modelLink] != null){
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{
} 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);
}
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);
}
);
}
})
}
export function loadModel(url, name) {
const loader = new GLTFLoader();
loader.load(url, function (gltf) {
let 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,
// called when the gltf has been generated
function (output) {
const blob = new Blob([output]);
url = URL.createObjectURL(blob);
addModelToObjectList(name, url);
},
// called when there is an error in the generation
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,
});
}, 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,
});
});
}
function uploadModel() {
// Create an input element
const inputElement = document.createElement("input");
......@@ -300,70 +353,13 @@ function uploadModel() {
for (const file of fileInput.files) {
const reader = new FileReader();
reader.onload = async readerEvent => {
const content = readerEvent.target.result;
let blob = new Blob([content], {
type: "application/octet-stream",
});
let url = URL.createObjectURL(blob);
const loader = new GLTFLoader();
loader.load(url, function(gltf) {
let 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,
// called when the gltf has been generated
function (output) {
blob = new Blob([output]);
url = URL.createObjectURL(blob);
const name = file.name;
projectStateObject.objects.push({
name: name,
url: url,
});
const item = {
name: name,
title: name,
actions: [
{
name: 'Objekt platzieren',
callback() {
const x = window.innerWidth / 2;
const y = window.innerHeight / 2;
const cart = new Cartesian2(x, y);
setupModelFunction(app, cart, url, name, false);
},
}
]
};
items.value.push(item);
},
// called when there is an error in the generation
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,
});
}, 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,
});
});
}
const content = readerEvent.target.result;
let blob = new Blob([content], {
type: "application/octet-stream",
});
let url = URL.createObjectURL(blob);
loadModel(url, file.name);
}
reader.readAsArrayBuffer(file);
}
});
......@@ -374,175 +370,187 @@ function uploadModel() {
}
function getRandomIcon() {
const keys = Object.keys(Icons);
const index = Math.floor(keys.length * Math.random());
return `$${keys[index]}`;
}
function getRandomIcon() {
const keys = Object.keys(Icons);
const index = Math.floor(keys.length * Math.random());
return `$${keys[index]}`;
}
export const objectListId = "object_list_id";
export default {
name: 'objectList',
components: {
VcsListItemComponent,
VcsButton,
VcsLabel,
VcsExpansionPanel,
VcsTreeview,
VcsTreeviewTitle,
VcsList,
VcsFormButton,
VcsTextField,
VcsFormSection,
VSwitch,
VSheet,
VDialog,
VCard,
VForm,
VContainer,
VRow,
VCol,
VExpansionPanels,
},
setup() {
app = inject('vcsApp');
const { pluginState, setupModel, saveObjects, loadObjects, config } = app.plugins.getByKey(name);
const draggable = ref(false);
const selectable = ref(false);
const searchable = ref(false);
const selectSingle = ref(false);
const showSelection = ref(false);
const showTitle = ref(false);
const title = ref('Objekte');
const titleActionsArray = ref([]);
const titleIconSrc = ref(null);
const selected = ref([]);
const newItem = ref({
name: 'foo',
title: 'foo',
disabled: false,
visible: true,
icon: 'home-outline',
action: false,
clicked: false,
selected: false,
});
const dialog = ref(false);
setupModelFunction = setupModel;
projectStateObject = pluginState;
onMounted(()=>loadCatalogsFromConfig(config).then(() => {console.log("Object library loaded")}));
return {
draggable,
selectable,
searchable,
selectSingle,
selected,
showSelection,
showTitle,
title,
titleActionsArray,
titleIconSrc,
items,
newItem,
dialog,
objLib,
required: [
(v) => !!v || 'Input may not be null',
(v) => v.length > 0 || 'Input must have a length',
],
add() {
const item = {
name: newItem.value.name,
title: newItem.value.title,
disabled: newItem.value.disabled,
visible: newItem.value.visible,
hasUpdate: newItem.value.hasUpdate,
};
export const objectListId = "object_list_id";
export default {
name: 'objectList',
components: {
VcsListItemComponent,
VcsButton,
VcsLabel,
VcsExpansionPanel,
VcsTreeview,
VcsTreeviewTitle,
VcsList,
VcsFormButton,
VcsTextField,
VcsFormSection,
VSwitch,
VSheet,
VDialog,
VCard,
VForm,
VContainer,
VRow,
VCol,
VList,
VExpansionPanels,
},
setup() {
app = inject('vcsApp');
const { pluginState, setupModel, saveObjects, loadObjects, config } = app.plugins.getByKey(name);
const draggable = ref(false);
const selectable = ref(false);
const searchable = ref(false);
const selectSingle = ref(false);
const showSelection = ref(false);
const showTitle = ref(false);
const title = ref('Objekte');
const titleActionsArray = ref([]);
const titleIconSrc = ref(null);
const selected = ref([]);
const newItem = ref({
name: 'foo',
title: 'foo',
disabled: false,
visible: true,
icon: 'home-outline',
action: false,
clicked: false,
selected: false,
});
const dialog = ref(false);
setupModelFunction = setupModel;
projectStateObject = pluginState;
onMounted(() => loadCatalogsFromConfig(config).then(() => { console.log("Object library loaded") }));
return {
draggable,
selectable,
searchable,
selectSingle,
selected,
showSelection,
showTitle,
title,
titleActionsArray,
titleIconSrc,
items,
newItem,
dialog,
objLib,
required: [
(v) => !!v || 'Input may not be null',
(v) => v.length > 0 || 'Input must have a length',
],
add() {
const item = {
name: newItem.value.name,
title: newItem.value.title,
disabled: newItem.value.disabled,
visible: newItem.value.visible,
hasUpdate: newItem.value.hasUpdate,
};
if (newItem.value.icon) {
item.icon = getRandomIcon();
}
if (newItem.value.icon) {
item.icon = getRandomIcon();
}
item.actions = [];
items.value.push(item);
newItem.value = {
name: 'foo',
title: 'foo',
disabled: false,
visible: true,
icon: false,
action: false,
clicked: false,
selected: false,
hasUpdate: false,
};
dialog.value = false;
item.actions = [];
items.value.push(item);
newItem.value = {
name: 'foo',
title: 'foo',
disabled: false,
visible: true,
icon: false,
action: false,
clicked: false,
selected: false,
hasUpdate: false,
};
dialog.value = false;
},
titleActions: computed({
get() {
return titleActionsArray.value.length > 0;
},
titleActions: computed({
get() {
return titleActionsArray.value.length > 0;
},
set(value) {
if (value) {
titleActionsArray.value = [
{
name: 'console.log foo',
callback() {
console.log('foo');
},
set(value) {
if (value) {
titleActionsArray.value = [
{
name: 'console.log foo',
callback() {
console.log('foo');
},
];
} else {
titleActionsArray.value = [];
}
},
}),
titleIcon: computed({
get() {
return !!titleIconSrc.value;
},
set(value) {
if (value) {
titleIconSrc.value = getRandomIcon();
} else {
titleIconSrc.value = null;
}
},
}),
move({ item, targetIndex }) {
let target = targetIndex;
target = target >= 0 ? target : 0;
target =
target < items.value.length ? target : items.value.length - 1;
const from = items.value.indexOf(item);
if (from !== target) {
items.value.splice(from, 1);
items.value.splice(target, 0, item);
},
];
} else {
titleActionsArray.value = [];
}
},
loadModel() {
uploadModel();
},
placeCatalogModel(modelLink) {
placeCatalogModel(modelLink);
},
loadConfiguration() {
loadObjects(app);
}),
titleIcon: computed({
get() {
return !!titleIconSrc.value;
},
saveConfiguration() {
saveObjects();
set(value) {
if (value) {
titleIconSrc.value = getRandomIcon();
} else {
titleIconSrc.value = null;
}
},
};
},
};
}),
move({ item, targetIndex }) {
let target = targetIndex;
target = target >= 0 ? target : 0;
target =
target < items.value.length ? target : items.value.length - 1;
const from = items.value.indexOf(item);
if (from !== target) {
items.value.splice(from, 1);
items.value.splice(target, 0, item);
}
},
loadModel() {
uploadModel();
},
placeCatalogModel(modelLink) {
placeCatalogModel(modelLink);
},
loadConfiguration() {
loadObjects(app);
},
saveConfiguration() {
saveObjects();
},
};
},
};
</script>
<style lang="scss" scoped>
.d-grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
.d-grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
.no-scroll {
max-height: none; /* remove height limit */
overflow: visible; /* show all content */
padding-top: 0;
padding-bottom: 0;
}
.list-col {
height: auto; /* let column grow with content */
min-height: 0; /* reset flex min-height */
padding: 0; /* remove top/bottom padding if necessary */
}
</style>
......@@ -9,7 +9,7 @@
</VcsLabel>
</v-col>
<v-col>
<VcsTextField id="fileInput" type="file" v-model="state.files" />
<VcsTextField id="fileInput" type="file" @change="onFileSelected" />
</v-col>
</v-row>
<v-row no-gutters class="justify-center">
......@@ -39,9 +39,15 @@ import {
import { VContainer, VRow, VForm, VCol } from 'vuetify/components';
import { name } from '../package.json';
import ConverterStatus from "./converterStatus.vue";
import {loadModel} from "./objectList.vue";
export const bimOptionsId = 'upload_ifc_id';
const errorMessage = "Ein Fehler ist beim konvertieren aufgetreten, die Konvertierung unterstützt " +
"Dateien im IFC Format IFC2x3 und IFC4 Add2 TC1. Bitte stellen Sie sicher, dass die Datei " +
"die korrekte Version hat und nicht korrupt ist. Falls dies der Fall ist dann ist ein " +
"unbekannter Fehler beim Konvertieren aufgetreten, der nicht behoben werden konnte."
export default {
name: 'IFC Konvertierung',
components: {
......@@ -65,19 +71,22 @@ export default {
const app = inject('vcsApp');
const { pluginState, config } = app.plugins.getByKey(name);
const disable = ref(false);
const onFileSelected = (event) => {
pluginState.file = event.target.files[0];
}
return {
closeSelf() {
emit('close');
},
onFileSelected,
convert() {
const fileInput = document.getElementById("fileInput");
const fd = new FormData();
fd.append('file', pluginState.files);
fd.append('file', pluginState.file);
const req = fetch(config.convertLink, {
method: 'post',
body: fd /* or aFile[0]*/
}); // returns a promise
let filename = pluginState.files.name.replace(/\.[^/.]+$/, "") + ".glb";
let filename = pluginState.file.name.replace(/\.[^/.]+$/, "") + ".glb";
disable.value = true;
const convertBtn = document.getElementById("convertBtn");
convertBtn.innerHTML = " Konvertierung läuft ";
......@@ -86,6 +95,11 @@ export default {
disable.value = false;
convertBtn.innerHTML = " Konvertieren ";
emit('close');
const geolocationString = res.headers.get('X-geolocation');
if (geolocationString) {
const geolocation = JSON.parse(geolocationString);
pluginState.geolocations[filename] = geolocation;
}
if (res.ok) {
// status code was 200-299
app.notifier.add({
......@@ -97,20 +111,14 @@ export default {
} else {
app.notifier.add({
type: NotificationType.ERROR,
message: "Ein Fehler ist beim konvertieren aufgetreten, die Konvertierung unterstützt " +
"Dateien im IFC Format IFC2x3 und IFC4 Add2 TC1. Bitte stellen Sie sicher, dass die Datei " +
"die korrekte Version hat und nicht korrupt ist. Falls dies der Fall ist dann ist ein " +
"unbekannter Fehler beim Konvertieren aufgetreten, der nicht behoben werden konnte.",
message: errorMessage,
});
}
}, function (error) {
app.notifier.add({
type: NotificationType.ERROR,
message: "Ein Fehler ist beim konvertieren aufgetreten, die Konvertierung unterstützt " +
"Dateien im IFC Format IFC2x3 und IFC4 Add2 TC1. Bitte stellen Sie sicher, dass die Datei " +
"die korrekte Version hat und nicht korrupt ist. Falls dies der Fall ist dann ist ein " +
"unbekannter Fehler beim Konvertieren aufgetreten, der nicht behoben werden konnte.",
message: errorMessage,
});
}).then((blob) => {
if (blob != null) {
......@@ -121,6 +129,7 @@ export default {
document.body.appendChild(a);
a.click();
a.remove();
loadModel(url, filename);
}
})
},
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment