Commit 51717470 authored by Matthias Betz's avatar Matthias Betz
Browse files

support geolocation

parent a5904da0
export const ifcIconSvg = `
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) --> <!-- Created with Inkscape (http://www.inkscape.org/) -->
...@@ -6,14 +7,13 @@ ...@@ -6,14 +7,13 @@
version="1.1" version="1.1"
id="svg1" id="svg1"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)" inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
sodipodi:docname="IFC_Icon_J_v01.svg" sodipodi:docname="IFC_Icon_J_v02.svg"
xml:space="preserve" xml:space="preserve"
inkscape:export-filename="..\Export\IFC_Icon_I_v01.svg" inkscape:export-filename="..\Export\IFC_Icon_J_Plain_v01.svg"
inkscape:export-xdpi="96" inkscape:export-xdpi="96"
inkscape:export-ydpi="96" inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1" id="namedview1"
...@@ -27,15 +27,15 @@ ...@@ -27,15 +27,15 @@
inkscape:document-units="px" inkscape:document-units="px"
showborder="true" showborder="true"
showgrid="true" showgrid="true"
inkscape:zoom="8" inkscape:zoom="22.627417"
inkscape:cx="-39.25" inkscape:cx="10.827573"
inkscape:cy="13.3125" inkscape:cy="13.50132"
inkscape:window-width="1920" inkscape:window-width="1920"
inkscape:window-height="1129" inkscape:window-height="1129"
inkscape:window-x="-8" inkscape:window-x="-8"
inkscape:window-y="-8" inkscape:window-y="-8"
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="g7" inkscape:current-layer="layer5"
showguides="false"><inkscape:grid showguides="false"><inkscape:grid
id="grid1" id="grid1"
units="px" units="px"
...@@ -284,13 +284,11 @@ ...@@ -284,13 +284,11 @@
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke-width:3.78418;enable-background:accumulate;stop-color:#000000" style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke-width:3.78418;enable-background:accumulate;stop-color:#000000"
d="m 16.719483,5.3400135 c 0,1.0219276 0.415528,1.9976456 1.152991,2.7050956 0.737462,0.7074485 1.571251,1.0679705 2.442959,1.0587195 l 0.395171,1.9863654 C 19.14539,11.155244 17.620858,10.57823 16.490363,9.493742 15.359868,8.4092541 14.71653,6.9065791 14.71653,5.3400135 Z" d="m 16.719483,5.3400135 c 0,1.0219276 0.415528,1.9976456 1.152991,2.7050956 0.737462,0.7074485 1.571251,1.0679705 2.442959,1.0587195 l 0.395171,1.9863654 C 19.14539,11.155244 17.620858,10.57823 16.490363,9.493742 15.359868,8.4092541 14.71653,6.9065791 14.71653,5.3400135 Z"
id="use13-5" id="use13-5"
sodipodi:nodetypes="csccscc" /><use sodipodi:nodetypes="csccscc" /><path
x="0" style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke-width:3.78418;enable-background:accumulate;stop-color:#000000"
y="0" d="m 16.719483,1.6002563 c 0,1.0219286 0.415528,1.9976461 1.152991,2.7050959 0.737462,0.7074495 1.571251,1.0679715 2.44296,1.0587205 l 0.395171,1.9863596 C 19.14539,7.4154823 17.620858,6.8384683 16.490363,5.7539827 15.359868,4.6694977 14.71653,3.1668221 14.71653,1.6002563 Z"
xlink:href="#path10-4"
id="use14-1" id="use14-1"
transform="matrix(-1,0,0,1,29.433068,0)" sodipodi:nodetypes="csccscc" /></g><g
style="display:inline" /></g><g
inkscape:groupmode="layer" inkscape:groupmode="layer"
id="g7" id="g7"
inkscape:label="Haus1" inkscape:label="Haus1"
...@@ -315,3 +313,6 @@ ...@@ -315,3 +313,6 @@
stroke-width="0" stroke-width="0"
id="path1" id="path1"
style="display:inline;fill:#000000" /></g></svg> style="display:inline;fill:#000000" /></g></svg>
`;
export default ifcIconSvg;
\ No newline at end of file
import {getPluginAssetUrl, NotificationType, ToolboxType, WindowSlot} from '@vcmap/ui'; import {
getPluginAssetUrl,
NotificationType,
ToolboxType,
WindowSlot,
} from '@vcmap/ui';
import * as Cesium from '@vcmap-cesium/engine'; import * as Cesium from '@vcmap-cesium/engine';
import { import {
Cartesian3, Cartesian3,
...@@ -15,6 +20,7 @@ import { mapVersion, name, version } from '../package.json'; ...@@ -15,6 +20,7 @@ import { mapVersion, name, version } from '../package.json';
import BIMOptions, { bimOptionsId } from './upload.vue'; import BIMOptions, { bimOptionsId } from './upload.vue';
import objectList, { loadCatalogModel, objectListId } from './objectList.vue'; import objectList, { loadCatalogModel, objectListId } from './objectList.vue';
import scaleControl, { scaleControlId } from './scaleControl.vue'; import scaleControl, { scaleControlId } from './scaleControl.vue';
import ifcIconSvg from '@icon.js';
let model; let model;
let conditions = [[true, true]]; let conditions = [[true, true]];
...@@ -29,38 +35,12 @@ let hpRoll; ...@@ -29,38 +35,12 @@ let hpRoll;
let app; let app;
let currentlyScaledModel; let currentlyScaledModel;
const hidingFeatures = []; const hidingFeatures = [];
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']);
featureToHide.loops++;
}
}
}
if (allHidden) {
const primitives = app.maps.activeMap.getScene().primitives;
for (let i = 0; i < primitives.length; i++) {
const tileSet = primitives.get(i);
if (!(tileSet instanceof Cesium3DTileset)) {
continue;
}
tileSet.tileVisible.removeEventListener(hidingListener);
}
}
};
const pluginState = { const pluginState = {
files: [], files: [],
objects: [], objects: [],
geolocations: {}, geolocations: {},
movedBy: {},
}; };
function updateHideCondition(gmlId) { function updateHideCondition(gmlId) {
...@@ -118,6 +98,9 @@ function saveObjects() { ...@@ -118,6 +98,9 @@ function saveObjects() {
const wgs84X = CMath.toDegrees(ellipPos.longitude); const wgs84X = CMath.toDegrees(ellipPos.longitude);
const wgs84Y = CMath.toDegrees(ellipPos.latitude); const wgs84Y = CMath.toDegrees(ellipPos.latitude);
const height = ellipPos.height; const height = ellipPos.height;
if (m.model.fromCatalog === undefined) {
m.model.fromCatalog = false;
}
outputArray.push({ outputArray.push({
name: m.name, name: m.name,
position: [wgs84X, wgs84Y, height], position: [wgs84X, wgs84Y, height],
...@@ -148,20 +131,82 @@ function saveFile(fileName, urlFile) { ...@@ -148,20 +131,82 @@ function saveFile(fileName, urlFile) {
a.remove(); a.remove();
} }
async function placeModelAtPosition(vcMap, lon, lat, modelUrl, name) { async function placeModelAtPosition(vcMap, lon, lat, modelUrl, name, rot, ele) {
// Create Cartographic position // Create Cartographic position
const position = Cesium.Cartographic.fromDegrees(lon, lat); const position = Cesium.Cartographic.fromDegrees(lon, lat);
// Wait for terrain height // Wait for terrain height
const [updatedPosition] = await Cesium.sampleTerrainMostDetailed( let height = ele;
if (!height) {
let [updatedPosition] = await Cesium.sampleTerrainMostDetailed(
vcMap.getCesiumWidget().terrainProvider, vcMap.getCesiumWidget().terrainProvider,
[position], [position],
); );
const height = updatedPosition.height + 10; height = updatedPosition.height;
console.log("Height: " + height); }
console.log("Lon: " + lon);
console.log("Lat: " + lat); let cartesianPosition = Cartesian3.fromDegrees(lon, lat, height);
const cartesianPosition = Cartesian3.fromDegrees(lon, lat, height); let flyToPos = Cartesian3.fromDegrees(lon, lat, height + 200);
console.log(cartesianPosition);
const movedBy = pluginState.movedBy[name];
if (movedBy) {
console.log(movedBy);
// Old Cartesian position (Cesium.Cartesian3)
let oldCartesian = cartesianPosition;
// Build ENU transform at this point
let enuTransform = Cesium.Transforms.eastNorthUpToFixedFrame(oldCartesian);
// Local offset you want (meters)
let localOffset = new Cesium.Cartesian3(+movedBy.x, -movedBy.z, 0);
// Convert local ENU offset → ECEF movement
let offsetMatrix = Cesium.Matrix4.multiplyByTranslation(
enuTransform,
localOffset,
new Cesium.Matrix4(),
);
let newCartesian = Cesium.Matrix4.getTranslation(offsetMatrix, new Cesium.Cartesian3());
let newCartographic = Cesium.Cartographic.fromCartesian(newCartesian);
if (!ele) {
// update height for new location
// Wait for terrain height
let [updatedPosition] = await Cesium.sampleTerrainMostDetailed(
vcMap.getCesiumWidget().terrainProvider,
[newCartographic],
);
height = updatedPosition.height;
}
let newFlyPos = Cartesian3.fromRadians(
newCartographic.longitude,
newCartographic.latitude,
height + 200,
);
newCartesian = Cartesian3.fromRadians(
newCartographic.longitude,
newCartographic.latitude,
height,
);
enuTransform = Cesium.Transforms.eastNorthUpToFixedFrame(newCartesian);
// Local offset you want (meters)
localOffset = new Cesium.Cartesian3(0, 0, +movedBy.y);
// Convert local ENU offset → ECEF movement
offsetMatrix = Cesium.Matrix4.multiplyByTranslation(
enuTransform,
localOffset,
new Cesium.Matrix4(),
);
newCartesian = Cesium.Matrix4.getTranslation(offsetMatrix, new Cesium.Cartesian3());
flyToPos = newFlyPos;
cartesianPosition = newCartesian;
}
// Add the model to the viewer // Add the model to the viewer
model = vcMap.getEntities().add({ model = vcMap.getEntities().add({
name: name, name: name,
...@@ -177,22 +222,33 @@ async function placeModelAtPosition(vcMap, lon, lat, modelUrl, name) { ...@@ -177,22 +222,33 @@ async function placeModelAtPosition(vcMap, lon, lat, modelUrl, name) {
model: model, model: model,
name: name, name: name,
}); });
if (rot) {
const roll = new HeadingPitchRoll();
hpRollMap.set(model, roll);
roll.heading = rot;
if (roll.heading > CMath.TWO_PI) {
roll.heading -= CMath.TWO_PI;
}
if (roll.heading < 0.0) {
roll.heading += CMath.TWO_PI;
}
const modelPos = model.position.getValue();
model.orientation = new ConstantProperty(
Transforms.headingPitchRollQuaternion(modelPos, roll),
);
}
vcMap.requestRender(); vcMap.requestRender();
vcMap.getCesiumWidget().camera.flyTo({ vcMap.getCesiumWidget().camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1500), destination: flyToPos,
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) { function setupModel(vcsApp, windowPos, url, name, fromCatalog, useGeolocation) {
const vcMap = vcsApp.maps.activeMap; const vcMap = vcsApp.maps.activeMap;
const geolocation = pluginState.geolocations[name]; const geolocation = pluginState.geolocations[name];
if (geolocation === undefined) { if (!useGeolocation || geolocation === undefined) {
setupModelWithoutGeolocation( setupModelWithoutGeolocation(
vcMap, vcMap,
windowPos, windowPos,
...@@ -205,7 +261,9 @@ function setupModel(vcsApp, windowPos, url, name, fromCatalog) { ...@@ -205,7 +261,9 @@ function setupModel(vcsApp, windowPos, url, name, fromCatalog) {
} else { } else {
const lon = geolocation.lon; const lon = geolocation.lon;
const lat = geolocation.lat; const lat = geolocation.lat;
placeModelAtPosition(vcMap, lon, lat, url, name); const rot = geolocation.heading;
const ele = geolocation.ele;
placeModelAtPosition(vcMap, lon, lat, url, name, rot, ele);
} }
} }
...@@ -473,7 +531,6 @@ export default function smartVillagesPlugin(config, baseUrl) { ...@@ -473,7 +531,6 @@ export default function smartVillagesPlugin(config, baseUrl) {
loadingObject.position[1], loadingObject.position[1],
loadingObject.position[2], loadingObject.position[2],
); );
console.log(cartPos);
// Add the model to the viewer // Add the model to the viewer
model = vcMap.getEntities().add({ model = vcMap.getEntities().add({
name: loadingObject.name, name: loadingObject.name,
...@@ -667,10 +724,11 @@ export default function smartVillagesPlugin(config, baseUrl) { ...@@ -667,10 +724,11 @@ export default function smartVillagesPlugin(config, baseUrl) {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async onVcsAppMounted(vcsApp) { async onVcsAppMounted(vcsApp) {
let importedIcon = `svgString:${ifcIconSvg}`;
const bimGroup = { const bimGroup = {
id: 'bim-functions', id: 'bim-functions',
type: ToolboxType.GROUP, type: ToolboxType.GROUP,
icon: getPluginAssetUrl(app, name, 'plugin-assets/IFC_Icon_J_v01.svg'), icon: importedIcon,
disabled: false, disabled: false,
title: 'BIM- und Planungsfunktionen', title: 'BIM- und Planungsfunktionen',
}; };
......
...@@ -115,32 +115,49 @@ const items = ref(defaultItems); ...@@ -115,32 +115,49 @@ const items = ref(defaultItems);
let setupModelFunction; let setupModelFunction;
let app; let app;
let projectStateObject; let projectStateObject;
let uploadedModels = [];
function addModelToObjectList(name, url) { function addModelToObjectList(name, url, hasGeolocation) {
if (projectStateObject === undefined) { if (projectStateObject === undefined) {
// object library wasn't opened before
return; return;
} }
projectStateObject.objects.push({ projectStateObject.objects.push({
name: name, name: name,
url: url, url: url,
}); });
const item = { let actions = [
name: name,
title: name,
actions: [
{ {
name: 'Objekt platzieren', name: 'Objekt platzieren',
callback() { callback() {
const x = window.innerWidth / 2; const x = window.innerWidth / 2;
const y = window.innerHeight / 2; const y = window.innerHeight / 2;
const cart = new Cartesian2(x, y); const cart = new Cartesian2(x, y);
setupModelFunction(app, cart, url, name, false); setupModelFunction(app, cart, url, name, false, false);
},
}
];
if (hasGeolocation) {
const geolocation = projectStateObject.geolocations[name];
const lon = geolocation.lon.toFixed(1);
const lat = geolocation.lat.toFixed(1);
actions.push({
name: 'Objekt platzieren mit Georeferenz (' + lon + '°E ' + lat + '°N)',
callback() {
const x = window.innerWidth / 2;
const y = window.innerHeight / 2;
const cart = new Cartesian2(x, y);
setupModelFunction(app, cart, url, name, false, true);
}, },
});
} }
]
const placeModel = {
name: name,
title: name,
actions: actions
}; };
items.value.push(item); items.value.push(placeModel);
} }
async function loadCatalogsFromConfig(config) { async function loadCatalogsFromConfig(config) {
...@@ -301,13 +318,28 @@ export async function loadCatalogModel(modelLink) { ...@@ -301,13 +318,28 @@ export async function loadCatalogModel(modelLink) {
}) })
} }
export function loadModel(url, name) { export function loadModelFromUrl(url, name, hasGeolocation) {
if (projectStateObject === undefined) {
// object library wasn't opened before
// store them, and load them once opened
uploadedModels.push({
name: name,
url: url,
hasGeolocation: hasGeolocation,
});
return;
}
const loader = new GLTFLoader(); const loader = new GLTFLoader();
loader.load(url, function (gltf) { loader.load(url, function (gltf) {
let bounds = new THREE.Box3().setFromObject(gltf.scene); let bounds = new THREE.Box3().setFromObject(gltf.scene);
const x = bounds.min.x + (bounds.max.x - bounds.min.x) / 2; 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 z = bounds.min.z + (bounds.max.z - bounds.min.z) / 2;
const m = new THREE.Matrix4().makeTranslation(-x, -bounds.min.y, -z); const m = new THREE.Matrix4().makeTranslation(-x, -bounds.min.y, -z);
projectStateObject.movedBy[name] = {
x: x,
y: bounds.min.y,
z: z,
}
gltf.scene.applyMatrix4(m); gltf.scene.applyMatrix4(m);
const exporter = new GLTFExporter(); const exporter = new GLTFExporter();
const options = { const options = {
...@@ -319,7 +351,7 @@ export function loadModel(url, name) { ...@@ -319,7 +351,7 @@ export function loadModel(url, name) {
function (output) { function (output) {
const blob = new Blob([output]); const blob = new Blob([output]);
url = URL.createObjectURL(blob); url = URL.createObjectURL(blob);
addModelToObjectList(name, url); addModelToObjectList(name, url, hasGeolocation);
}, },
// called when there is an error in the generation // called when there is an error in the generation
function (error) { function (error) {
...@@ -364,7 +396,7 @@ function uploadModel() { ...@@ -364,7 +396,7 @@ function uploadModel() {
type: "application/octet-stream", type: "application/octet-stream",
}); });
let url = URL.createObjectURL(blob); let url = URL.createObjectURL(blob);
loadModel(url, file.name); loadModelFromUrl(url, file.name, false);
} }
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
} }
...@@ -434,7 +466,15 @@ export default { ...@@ -434,7 +466,15 @@ export default {
const dialog = ref(false); const dialog = ref(false);
setupModelFunction = setupModel; setupModelFunction = setupModel;
projectStateObject = pluginState; projectStateObject = pluginState;
onMounted(() => loadCatalogsFromConfig(config).then(() => { console.log("Object library loaded") })); onMounted(() => {
loadCatalogsFromConfig(config).then(() => { console.log("Object library loaded") });
for (const o of uploadedModels) {
loadModelFromUrl(o.url, o.name, o.hasGeolocation);
}
uploadedModels = [];
});
return { return {
draggable, draggable,
selectable, selectable,
...@@ -548,15 +588,22 @@ export default { ...@@ -548,15 +588,22 @@ export default {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
.no-scroll { .no-scroll {
max-height: none; /* remove height limit */ max-height: none;
overflow: visible; /* show all content */ /* remove height limit */
overflow: visible;
/* show all content */
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
} }
.list-col { .list-col {
height: auto; /* let column grow with content */ height: auto;
min-height: 0; /* reset flex min-height */ /* let column grow with content */
padding: 0; /* remove top/bottom padding if necessary */ min-height: 0;
/* reset flex min-height */
padding: 0;
/* remove top/bottom padding if necessary */
} }
</style> </style>
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
<v-card class="pa-2 ma-2"> <v-card class="pa-2 ma-2">
<v-container> <v-container>
<v-row no-gutters class="justify-center"> <v-row no-gutters class="justify-center">
<v-col> <v-col class="flex-grow-0">
<VcsLabel html-for="fileInput"> <VcsLabel html-for="fileInput">
IFC Datei: IFC Datei:
</VcsLabel> </VcsLabel>
</v-col> </v-col>
<v-col> <v-col>
<VcsTextField id="fileInput" type="file" @change="onFileSelected" /> <VcsFileInput id="fileInput" type="file" @change="onFileSelected" accept=".ifc" />
</v-col> </v-col>
</v-row> </v-row>
<v-row no-gutters class="justify-center"> <v-row no-gutters class="justify-center">
...@@ -29,6 +29,7 @@ import { ...@@ -29,6 +29,7 @@ import {
VcsRadio, VcsRadio,
VcsFormButton, VcsFormButton,
VcsTextField, VcsTextField,
VcsFileInput,
NotificationType, NotificationType,
VcsFormattedNumber, VcsFormattedNumber,
VcsFormSection, VcsFormSection,
...@@ -39,7 +40,7 @@ import { ...@@ -39,7 +40,7 @@ import {
import { VContainer, VRow, VForm, VCol } from 'vuetify/components'; import { VContainer, VRow, VForm, VCol } from 'vuetify/components';
import { name } from '../package.json'; import { name } from '../package.json';
import ConverterStatus from "./converterStatus.vue"; import ConverterStatus from "./converterStatus.vue";
import {loadModel} from "./objectList.vue"; import {loadModelFromUrl} from "./objectList.vue";
export const bimOptionsId = 'upload_ifc_id'; export const bimOptionsId = 'upload_ifc_id';
...@@ -55,6 +56,7 @@ export default { ...@@ -55,6 +56,7 @@ export default {
VcsSelect, VcsSelect,
VcsTextField, VcsTextField,
VcsCheckbox, VcsCheckbox,
VcsFileInput,
VcsRadio, VcsRadio,
VcsFormattedNumber, VcsFormattedNumber,
VcsFormSection, VcsFormSection,
...@@ -102,10 +104,18 @@ export default { ...@@ -102,10 +104,18 @@ export default {
} }
if (res.ok) { if (res.ok) {
// status code was 200-299 // status code was 200-299
let message = "Datei erfolgreich konvertiert.";
let timeout = 5000;
if (geolocationString) {
const geolocation = JSON.parse(geolocationString);
message = "Datei erfolgreich konvertiert. Gefundene Position: (" + geolocation.lon
+ "°E " + geolocation.lat + "°N)";
timeout = 15000;
}
app.notifier.add({ app.notifier.add({
type: NotificationType.SUCCESS, type: NotificationType.SUCCESS,
message: "Datei erfolgreich konvertiert", message: message,
timeout: 5000, timeout: timeout,
}); });
return res.blob(); return res.blob();
} else { } else {
...@@ -120,6 +130,7 @@ export default { ...@@ -120,6 +130,7 @@ export default {
type: NotificationType.ERROR, type: NotificationType.ERROR,
message: errorMessage, message: errorMessage,
}); });
disable.value = false;
}).then((blob) => { }).then((blob) => {
if (blob != null) { if (blob != null) {
var url = window.URL.createObjectURL(blob); var url = window.URL.createObjectURL(blob);
...@@ -129,7 +140,11 @@ export default { ...@@ -129,7 +140,11 @@ export default {
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
a.remove(); a.remove();
loadModel(url, filename); let hasGeolocation = pluginState.geolocations[filename] != undefined;
if (!hasGeolocation) {
hasGeolocation = false;
}
loadModelFromUrl(url, filename, hasGeolocation);
} }
}) })
}, },
......
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