From 0f6db77484718c2c706d4922a3118b02b40505f3 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Tue, 5 Nov 2024 10:14:56 -0500
Subject: [PATCH 01/22] initial rough api
---
Apps/Sandcastle/gallery/iTwin Demo.html | 295 ++++++++++++++
packages/engine/Source/Core/ITwin.js | 66 ++++
.../Source/Scene/createIModel3DTileset.js | 371 ++++++++++++++++++
3 files changed, 732 insertions(+)
create mode 100644 Apps/Sandcastle/gallery/iTwin Demo.html
create mode 100644 packages/engine/Source/Core/ITwin.js
create mode 100644 packages/engine/Source/Scene/createIModel3DTileset.js
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
new file mode 100644
index 000000000000..e1674a6b9e74
--- /dev/null
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -0,0 +1,295 @@
+
+
+
+
+
+
+
+
+ Cesium Demo
+
+
+
+
+
+
+ Loading...
+
+
+
+
diff --git a/packages/engine/Source/Core/ITwin.js b/packages/engine/Source/Core/ITwin.js
new file mode 100644
index 000000000000..23826cfc5d4b
--- /dev/null
+++ b/packages/engine/Source/Core/ITwin.js
@@ -0,0 +1,66 @@
+import Resource from "./Resource.js";
+
+/**
+ * Default settings for accessing the iTwin platform.
+ *
+ * Keys can be created using the iModels share routes {@link https://developer.bentley.com/apis/imodels-v2/operations/create-imodel-share/}
+ *
+ * An ion access token is only required if you are using any ion related APIs.
+ * A default access token is provided for evaluation purposes only.
+ * Sign up for a free ion account and get your own access token at {@link https://cesium.com}
+ *
+ * @see IonResource
+ * @see IonImageryProvider
+ * @see IonGeocoderService
+ * @see createWorldImagery
+ * @see createWorldTerrain
+ * @namespace ITwin
+ */
+const ITwin = {};
+
+/**
+ * Gets or sets the default iTwin access token.
+ *
+ * TODO: I'm not sure we can even do this kind of access token. Each route seems to need it's own scopes
+ * and we may not be able to guarantee this "top level token" has them all
+ * So far we use
+ * `mesh-export:read` for loading meshes GET /mesh-export(s)
+ * `mesh-export:modify` if we want to include a function to create an export
+ * `itwin-platform` if we want to use the iModel shares ourselves GET /imodels/{id}/shares
+ *
+ *
+ * @type {string|undefined}
+ */
+ITwin.defaultAccessToken = undefined;
+
+/**
+ * Gets or sets the default Google Map Tiles API endpoint.
+ *
+ * @type {string|Resource}
+ * @default https://api.bentley.com
+ */
+ITwin.apiEndpoint = new Resource({
+ url: "https://api.bentley.com",
+});
+
+// TODO: this should only be needed if we have a way to generate really long term access tokens
+// to sample data that is accessible to everyone
+// ITwin.getDefaultTokenCredit = function (providedKey) {
+// if (providedKey !== defaultAccessToken) {
+// return undefined;
+// }
+
+// if (!defined(defaultTokenCredit)) {
+// const defaultTokenMessage =
+// ' \
+// This application is using Cesium\'s default ion access token. Please assign Cesium.Ion.defaultAccessToken \
+// with an access token from your ion account before making any Cesium API calls. \
+// You can sign up for a free ion account at https://cesium.com.';
+
+// defaultTokenCredit = new Credit(defaultTokenMessage, true);
+// }
+
+// return defaultTokenCredit;
+// };
+
+export default ITwin;
diff --git a/packages/engine/Source/Scene/createIModel3DTileset.js b/packages/engine/Source/Scene/createIModel3DTileset.js
new file mode 100644
index 000000000000..1405aac4e5a2
--- /dev/null
+++ b/packages/engine/Source/Scene/createIModel3DTileset.js
@@ -0,0 +1,371 @@
+import Cesium3DTileset from "./Cesium3DTileset.js";
+import defined from "../Core/defined.js";
+import Resource from "../Core/Resource.js";
+import ITwin from "../Core/ITwin.js";
+import DeveloperError from "../Core/DeveloperError.js";
+
+function delay(ms) {
+ return new Promise((res) => setTimeout(res, ms));
+}
+
+/**
+ * @enum {string}
+ */
+const ExportStatus = Object.freeze({
+ NotStarted: "NotStarted",
+ InProgress: "InProgress",
+ Complete: "Complete",
+ Invalid: "Invalid",
+});
+
+/**
+ * Type of an export currently, only GLTF and 3DFT are documented
+ * The CESIUM option is what we were told to use with Sandcastle
+ * I've also seen the IMODEL one but don't know where it's from
+ * @enum {string}
+ */
+const ExportType = Object.freeze({
+ "3DFT": "3DFT",
+ GLFT: "GLTF",
+ IMODEL: "IMODEL",
+ CESIUM: "CESIUM",
+});
+
+/**
+ * @typedef {Object} GeometryOptions
+ * @property {boolean} includeLines
+ * @property {number} chordTol
+ * @property {number} angleTol
+ * @property {number} decimationTol
+ * @property {number} maxEdgeLength
+ * @property {number} minBRepFeatureSize
+ * @property {number} minLineStyleComponentSize
+ */
+
+/**
+ * @typedef {Object} ViewDefinitionFilter
+ * @property {string[]} models Array of included model IDs.
+ * @property {string[]} categories Array of included category IDs.
+ * @property {string[]} neverDrawn Array of element IDs to filter out.
+ */
+
+/**
+ * @typedef {Object} StartExport
+ * @property {string} iModelId
+ * @property {string} changesetId
+ * @property {ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
+ * @property {GeometryOptions} geometryOptions
+ * @property {ViewDefinitionFilter} viewDefinitionFilter
+ */
+
+/**
+ * @typedef {Object} Link
+ * @property {string} href
+ */
+
+/**
+ * @typedef {Object} Export
+ * @property {string} id
+ * @property {string} displayName
+ * @property {ExportStatus} status
+ * @property {StartExport} request
+ * @property {{mesh: Link}} _links
+ */
+
+/**
+ * @typedef {Object} ExportResponse
+ * @property {Export} export
+ */
+
+/**
+ * Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D Tiles tileset.
+ *
+ * @function
+ *
+ * @param {string} exportId
+ * @param {Cesium3DTileset.ConstructorOptions} [options] An object describing initialization options.
+ * @returns {Promise}
+ *
+ * @see ITwin
+ *
+ * @example
+ * // Use your own iTwin API key for mesh export
+ * Cesium.ITwin.defaultApiKey = "your-api-key";
+ *
+ * const viewer = new Cesium.Viewer("cesiumContainer");
+ *
+ * try {
+ * const tileset = await Cesium.createIModel3DTileset();
+ * viewer.scene.primitives.add(tileset));
+ * } catch (error) {
+ * console.log(`Error creating tileset: ${error}`);
+ * }
+ */
+async function createIModel3DTileset(exportId, options) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+
+ options = options ?? {};
+
+ const timeoutAfter = 300000;
+ const start = Date.now();
+ let result = await createIModel3DTileset.getExport(exportId);
+ let status = result.export.status;
+
+ if (result.export.request.exportType !== ExportType.CESIUM) {
+ // This is an undocumented value but I think it's the only one we want to load
+ // TODO: should we even be checking this?
+ throw new Error(`Wrong export type ${result.export.request.exportType}`);
+ }
+
+ // wait until the export is complete
+ while (status !== ExportStatus.Complete) {
+ await delay(5000);
+ result = await createIModel3DTileset.getExport(exportId);
+ status = result.export.status;
+ console.log(`Export is ${status}`);
+
+ if (Date.now() - start > timeoutAfter) {
+ throw new Error("Export did not complete in time.");
+ }
+ }
+
+ // This link is only valid 1 hour
+ let tilesetUrl = result.export._links.mesh.href;
+ const splitStr = tilesetUrl.split("?");
+ // is there a cleaner way to do this?
+ tilesetUrl = `${splitStr[0]}/tileset.json?${splitStr[1]}`;
+
+ const resource = new Resource({
+ url: tilesetUrl,
+ });
+
+ return Cesium3DTileset.fromUrl(resource, options);
+}
+
+/**
+ * @param {string} exportId
+ */
+createIModel3DTileset.getExport = async function (exportId) {
+ const headers = {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ };
+
+ // obtain export for specified export id
+ const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
+
+ // TODO: this request is _really_ slow, like 7 whole second alone for me
+ // Arun said this was kinda normal but to keep track of the `x-correlation-id` of any that take EXTRA long
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ throw new Error(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (response.status === 404) {
+ throw new Error(`Requested export is not available ${exportId}`);
+ } else if (response.status === 429) {
+ throw new Error("Too many requests");
+ }
+ throw new Error(`Unknown request failure ${response.status}`);
+ }
+
+ /** @type {ExportResponse} */
+ const result = await response.json();
+ return result;
+};
+
+/**
+ * Get the list of exports for the given iModel + changeset
+ *
+ * @param {string} iModelId
+ * @param {string} changesetId
+ */
+createIModel3DTileset.getExports = async function (iModelId, changesetId) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+ const headers = {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ Prefer: "return=representation", // or return=minimal (the default)
+ };
+
+ // obtain export for specified export id
+ let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}`;
+ if (defined(changesetId) && changesetId !== "") {
+ url += `&changesetId=${changesetId}`;
+ }
+
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ throw new Error(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (response.status === 422) {
+ throw new Error(
+ `Unprocessable Entity:${result.error.code} ${result.error.message}`,
+ );
+ } else if (response.status === 429) {
+ throw new Error("Too many requests");
+ }
+ throw new Error(`Unknown request failure ${response.status}`);
+ }
+
+ /** @type {{exports: Export[]}} */
+ const result = await response.json();
+ return result;
+};
+
+/**
+ * Check the exports for the given iModel + changeset combination for any that
+ * have the desired CESIUM type and return that one
+ *
+ * @param {string} iModelId
+ * @param {string} changesetId
+ */
+createIModel3DTileset.checkForCesiumExport = async function (
+ iModelId,
+ changesetId,
+) {
+ const { exports } = await createIModel3DTileset.getExports(
+ iModelId,
+ changesetId,
+ );
+ const cesiumExport = exports.find(
+ (e) => e.request.exportType === ExportType.CESIUM,
+ );
+ return cesiumExport;
+};
+
+/**
+ * Start the export process for the given iModel + changeset.
+ *
+ * Pair this with the {@link checkForCesiumExport} function to avoid creating extra exports
+ *
+ * @example
+ * const cesiumExport = await Cesium.createIModel3DTileset.checkForCesiumExport(imodelId, changesetId);
+ * let exportId = cesiumExport?.id;
+ * if (!Cesium.defined(cesiumExport)) {
+ * exportId = await Cesium.createIModel3DTileset.createExportForModelId(
+ * imodelId,
+ * changesetId,
+ * accessToken,
+ * );
+ * }
+ *
+ * @param {string} iModelId
+ * @param {string} changesetId
+ */
+createIModel3DTileset.createExportForModelId = async function (
+ iModelId,
+ changesetId,
+) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+
+ console.log("Start Export");
+
+ changesetId = changesetId ?? "";
+
+ const requestOptions = {
+ method: "POST",
+ headers: {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ iModelId,
+ changesetId,
+ exportType: "CESIUM",
+ }),
+ };
+
+ // initiate mesh export
+ const response = await fetch(
+ `https://api.bentley.com/mesh-export/`,
+ requestOptions,
+ );
+
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ console.error("Unauthorized, bad token, wrong scopes or headers bad");
+ console.error(
+ result.error.code,
+ result.error.message,
+ result.error.details,
+ );
+ } else if (response.status === 403) {
+ console.error("Not allowed, forbidden");
+ console.error(result.error.code, result.error.message);
+ } else if (response.status === 422) {
+ console.error("Unprocessable: Cannot create export job");
+ console.error(result.error.code, result.error.message);
+ console.error(result.error.details);
+ } else if (response.status === 429) {
+ console.log(
+ "Too many requests, retry after:",
+ response.headers.get("retry-after"),
+ );
+ console.error(result.error.code, result.error.message);
+ } else {
+ console.error("Bad request, unknown error", response);
+ }
+ return undefined;
+ }
+
+ /** @type {ExportResponse} */
+ const result = await response.json();
+ return result.export.id;
+};
+
+/**
+ * Delete the specified export
+ *
+ * TODO: I'm not sure if we want this or not. Might belong better as an APP level function
+ * I just started creating helpers for all the routes under the `mesh-export` API
+ * for ease of access during testing
+ *
+ * @param {string} exportId
+ */
+createIModel3DTileset.deleteExport = async function (exportId) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+ const headers = {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ };
+
+ // obtain export for specified export id
+ const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
+
+ const response = await fetch(url, { method: "DELETE", headers });
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ throw new Error(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (response.status === 404) {
+ throw new Error("Export not found");
+ } else if (response.status === 422) {
+ throw new Error(
+ `Unprocessable Entity:${result.error.code} ${result.error.message}`,
+ );
+ } else if (response.status === 429) {
+ throw new Error("Too many requests");
+ }
+ throw new Error(`Unknown request failure ${response.status}`);
+ }
+};
+
+export default createIModel3DTileset;
From 421e5dda69820dd7e9a947f5644567325368d2d7 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Tue, 5 Nov 2024 16:33:11 -0500
Subject: [PATCH 02/22] restructure api, create tileset from model id
---
Apps/Sandcastle/gallery/iTwin Demo.html | 158 ++------
packages/engine/Source/Core/ITwin.js | 274 +++++++++++++-
.../Source/Scene/createIModel3DTileset.js | 358 +++---------------
3 files changed, 351 insertions(+), 439 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index e1674a6b9e74..d8d4865927e2 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -33,85 +33,18 @@
// must be created for the Start export route if you want to create new exports
// Needs to have the mesh-export:modify scope not just mesh-export:read
const accessToken =
- "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkJlbnRsZXlJTVNfMjAyNCIsInBpLmF0bSI6ImE4bWUifQ.eyJzY29wZSI6WyJtZXNoLWV4cG9ydDpyZWFkIiwibWVzaC1leHBvcnQ6bW9kaWZ5Il0sImNsaWVudF9pZCI6Iml0d2luLWRldmVsb3Blci1jb25zb2xlIiwiYXVkIjpbImh0dHBzOi8vaW1zLmJlbnRsZXkuY29tL2FzL3Rva2VuLm9hdXRoMiIsImh0dHBzOi8vaW1zb2lkYy5iZW50bGV5LmNvbS9hcy90b2tlbi5vYXV0aDIiLCJodHRwczovL2ltc29pZGMuYmVudGxleS5jb20vcmVzb3VyY2VzIiwiYmVudGxleS1hcGktbWFuYWdlbWVudCJdLCJzdWIiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJyb2xlIjoiQkVOVExFWV9FTVBMT1lFRSIsIm9yZyI6ImZhYjk3NzRiLWIzMzgtNGNjMi1hNmM5LTQ1OGJkZjdmOTY2YSIsInN1YmplY3QiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJpc3MiOiJodHRwczovL2ltcy5iZW50bGV5LmNvbSIsImVudGl0bGVtZW50IjpbIkJFTlRMRVlfTEVBUk4iLCJJTlRFUk5BTCIsIlNFTEVDVF8yMDA2IiwiQkVOIiwiQkROIl0sInByZWZlcnJlZF91c2VybmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZ2l2ZW5fbmFtZSI6Ikpvc2giLCJzaWQiOiJHMGZTamlOXzBMRjE1Vy1IYnRTaWtoeDNuSXMuU1UxVExVSmxiblJzWlhrdFZWTS5LUDVRLjZCakNoYXptMldBMjBIdXVWMUxwNFB6RVAiLCJuYmYiOjE3MzA3NDc3NjUsInVsdGltYXRlX3NpdGUiOiIxMDAxMzg5MTE3IiwidXNhZ2VfY291bnRyeV9pc28iOiJVUyIsImF1dGhfdGltZSI6MTczMDc0ODA2NSwibmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwib3JnX25hbWUiOiJCZW50bGV5IFN5c3RlbXMgSW5jIiwiZmFtaWx5X25hbWUiOiJSb3V6ZXIiLCJlbWFpbCI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZXhwIjoxNzMwNzUxNjY2fQ.0yEQZAyKKAdwNDiwHSD6f_Qzq0M8cbHJcMfT6JidBldw9qiyU4jx6ZdqILddrL-seWCkf9sRtWuoHm7Fw-j_wtaLASaOpHMMwC7IVdh25pbRB-D3mN8_rmQiDbXUadJ1MwH8-pNCubrER1lZLYEPrQ4zJcRtAblbJGNjFdoOi3FXB-y3JLleH4qYykLceDkbW3l2lZRfdIW2pytCIuZs7XZ9Hr6F_cLsYIrs9iRfBFVxxHxxfwgRLQoRuPmDuzKl9-ylLZUFN6CQZUfv7vzL9feoXJXGdZeGgqlyFuMOCiVbYU_elx7P7fFm8G13HvTti5hz98mV1r2bXYvfNpuklg";
+ "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkJlbnRsZXlJTVNfMjAyNCIsInBpLmF0bSI6ImE4bWUifQ.eyJzY29wZSI6WyJtZXNoLWV4cG9ydDpyZWFkIiwibWVzaC1leHBvcnQ6bW9kaWZ5Il0sImNsaWVudF9pZCI6Iml0d2luLWRldmVsb3Blci1jb25zb2xlIiwiYXVkIjpbImh0dHBzOi8vaW1zLmJlbnRsZXkuY29tL2FzL3Rva2VuLm9hdXRoMiIsImh0dHBzOi8vaW1zb2lkYy5iZW50bGV5LmNvbS9hcy90b2tlbi5vYXV0aDIiLCJodHRwczovL2ltc29pZGMuYmVudGxleS5jb20vcmVzb3VyY2VzIiwiYmVudGxleS1hcGktbWFuYWdlbWVudCJdLCJzdWIiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJyb2xlIjoiQkVOVExFWV9FTVBMT1lFRSIsIm9yZyI6ImZhYjk3NzRiLWIzMzgtNGNjMi1hNmM5LTQ1OGJkZjdmOTY2YSIsInN1YmplY3QiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJpc3MiOiJodHRwczovL2ltcy5iZW50bGV5LmNvbSIsImVudGl0bGVtZW50IjpbIkJFTlRMRVlfTEVBUk4iLCJJTlRFUk5BTCIsIlNFTEVDVF8yMDA2IiwiQkVOIiwiQkROIl0sInByZWZlcnJlZF91c2VybmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZ2l2ZW5fbmFtZSI6Ikpvc2giLCJzaWQiOiJHMGZTamlOXzBMRjE1Vy1IYnRTaWtoeDNuSXMuU1UxVExVSmxiblJzWlhrdFZWTS5LbWlWLlF2UExVcGJyc2R3engwWGxPbkF3eTFTT0IiLCJuYmYiOjE3MzA4MzgyNDIsInVsdGltYXRlX3NpdGUiOiIxMDAxMzg5MTE3IiwidXNhZ2VfY291bnRyeV9pc28iOiJVUyIsImF1dGhfdGltZSI6MTczMDgzODU0MiwibmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwib3JnX25hbWUiOiJCZW50bGV5IFN5c3RlbXMgSW5jIiwiZmFtaWx5X25hbWUiOiJSb3V6ZXIiLCJlbWFpbCI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZXhwIjoxNzMwODQyMTQyfQ.N_WrgjL2bqxdNLEM5nHh4Fg-FzeA-qxxpryaoaMKz8onnpgzmp-X8dyQ1TyMRqKQY99iLypE9bU45DwRJs7vBgNQ73d53SkC9TKGYn8AAOTJz8c_oEHgkoTEDaaLsMPCw88tqZ34pY0e0oIHIofVDTCvwlzwaJPADfkIxz-8GzhIt2WrXR7f6LWBFSlWNrtNhIr9Tz7UNaLOh97_3fS9KVU1I084CpBga9cj_mjGBeki7mIEQvpqMj8x2bJPae_c6WrPEjKLayrOzq4q0X0Kvle0ZFAm-Se9MFCusTFS51bYrI3IsjagGeIP2U06zzcMJ22NylomE60hz6GK_4uB3g";
Cesium.ITwin.defaultAccessToken = accessToken;
// this is the iModel in the "Hello iTwinCesium" iTwin that we should all have access to
// https://developer.bentley.com/my-itwins/b4a30036-0456-49ea-a439-3fcd9365e24e/home/
const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
+ // const imodelId = "88673c1d-12b8-48f1-8beb-5000d0edbd0b";
+
+ const changesetId = "";
// const knownExportId = undefined;
// const knownExportId = "ab9953b2-bc8e-48ac-a5b0-5d43d68593e8";
- // async function startExport(iModelId, changesetId, accessToken) {
- // console.log("Start Export");
-
- // const requestOptions = {
- // method: "POST",
- // headers: {
- // Authorization: accessToken,
- // Accept: "application/vnd.bentley.itwin-platform.v1+json",
- // "Content-Type": "application/json",
- // },
- // body: JSON.stringify({
- // iModelId,
- // changesetId,
- // exportType: "CESIUM",
- // }),
- // };
-
- // // initiate mesh export
- // const response = await fetch(
- // `https://api.bentley.com/mesh-export/`,
- // requestOptions,
- // );
- // if (!response.ok) {
- // if (response.status === 401) {
- // console.error("Unauthorized, bad token, wrong scopes or headers bad");
- // } else if (response.status === 403) {
- // console.error("Not allowed, forbidden");
- // } else if (response.status === 422) {
- // console.error("Unprocessable: Cannot create export job");
- // } else if (response.status === 429) {
- // console.log("Too many requests");
- // } else {
- // console.error("Bad request, unknown error", response);
- // }
- // return undefined;
- // }
- // const result = JSON.parse(JSON.stringify(await response.json()));
- // return result?.export?.id;
- // }
-
- // async function getExport(exportId, accessToken) {
- // const headers = {
- // Authorization: accessToken,
- // Accept: "application/vnd.bentley.itwin-platform.v1+json",
- // };
-
- // // obtain export for specified export id
- // const url = `https://api.bentley.com/mesh-export/${exportId}`;
- // try {
- // // TODO: this request is _really_ slow, like 7 whole second alone for me
- // const response = await fetch(url, { headers });
- // if (!response.ok) {
- // if (response.status === 401) {
- // console.error("Unauthorized, bad token, wrong scopes or headers bad");
- // } else if (response.status === 404) {
- // console.error("Requested export is not available", exportId);
- // } else if (response.status === 429) {
- // console.error("Too many requests");
- // } else {
- // console.log("Unknown request failure", response);
- // }
- // return undefined;
- // }
- // const result = JSON.parse(JSON.stringify(await response.json()));
- // return result;
- // } catch (err) {
- // return undefined;
- // }
- // }
-
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
// Grabbed mapping from the iTwin Viewer
@@ -192,69 +125,44 @@
const statusOutput = document.querySelector("#status");
async function init() {
// TODO: just for testing
- // await Cesium.createIModel3DTileset.deleteExport(
- // "3a627319-cf6e-4535-9499-da5320a69791",
- // );
- // console.log(await Cesium.createIModel3DTileset.checkForCesiumExport(imodelId));
- // console.log(await Cesium.createIModel3DTileset.getExports(imodelId));
-
- const changesetId = "";
- const cesiumExport = await Cesium.createIModel3DTileset.checkForCesiumExport(
- imodelId,
- changesetId,
- );
- let exportId = cesiumExport?.id;
- if (!Cesium.defined(cesiumExport)) {
- exportId = await Cesium.createIModel3DTileset.createExportForModelId(
- imodelId,
- changesetId,
- accessToken,
- );
- }
+ // await Cesium.ITwin.deleteExport("8bd9c52e-b379-4f7d-bf39-c45954030b26");
+ // console.log(await Cesium.ITwin.getExports(imodelId));
statusOutput.innerText = "Starting export";
// const exportId =
// knownExportId ??
- // (await Cesium.createIModel3DTileset.createExportForModelId(
+ // (await Cesium.ITwin.createExportForModelId(
// imodelId,
// "",
// accessToken,
// ));
- if (!exportId) {
- console.error("No export id returned");
- return;
- }
- console.log("Using export id", exportId);
+ // if (!exportId) {
+ // console.error("No export id returned");
+ // return;
+ // }
+ // console.log("Using export id", exportId);
const start = Date.now();
- // let result = await getExport(exportId, accessToken);
- // let status = result.export.status;
- // while (status !== "Complete") {
- // await delay(5000);
- // result = await getExport(exportId, accessToken);
- // status = result.export.status;
- // console.log(`Export is ${status}`);
-
- // if (Date.now() - start > 300000) {
- // throw new Error("Export did not complete in time.");
- // }
- // }
- // if (result.export.request.exportType !== "CESIUM") {
- // console.error("Wrong export type", result.export.request.exportType);
- // throw new Error(`Wrong export type ${result.export.request.exportType}`);
- // }
+ statusOutput.innerText = "Creating Tileset";
- // // This link is only valid 1 hour
- // let tilesetUrl = result.export._links.mesh.href;
- // const splitStr = tilesetUrl.split("?");
- // // is there a cleaner way to do this?
- // tilesetUrl = `${splitStr[0]}/tileset.json?${splitStr[1]}`;
+ let tileset = await Cesium.createIModel3DTileset.fromModelId(
+ imodelId,
+ changesetId,
+ );
+ if (!Cesium.defined(tileset)) {
+ statusOutput.innerText = "Starting export";
+ const exportId = await Cesium.ITwin.createExportForModelId(
+ imodelId,
+ changesetId,
+ accessToken,
+ );
+ statusOutput.innerText = "Creating Tileset from export";
+ tileset = await Cesium.createIModel3DTileset.fromExportId(exportId);
+ }
- // const tileset = await Cesium.Cesium3DTileset.fromUrl(tilesetUrl);
- statusOutput.innerText = "Creating Tileset";
- const tileset = await Cesium.createIModel3DTileset(exportId);
+ // const tileset = await Cesium.createIModel3DTileset(exportId);
scene.primitives.add(tileset);
tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE;
@@ -278,7 +186,15 @@
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
- init();
+ init().catch((error) => {
+ statusOutput.style.color = "red";
+ if (error.message.includes("Unauthorized")) {
+ statusOutput.innerText = "Error: Unauthorized";
+ } else {
+ statusOutput.innerText = "Error";
+ }
+ console.error(error);
+ });
//Sandcastle_End
Sandcastle.finishedLoading();
diff --git a/packages/engine/Source/Core/ITwin.js b/packages/engine/Source/Core/ITwin.js
index 23826cfc5d4b..ab74ad0c5536 100644
--- a/packages/engine/Source/Core/ITwin.js
+++ b/packages/engine/Source/Core/ITwin.js
@@ -1,4 +1,76 @@
+import defined from "./defined.js";
+import DeveloperError from "./DeveloperError.js";
import Resource from "./Resource.js";
+import RuntimeError from "./RuntimeError.js";
+
+/**
+ * @enum {string}
+ */
+const ExportStatus = Object.freeze({
+ NotStarted: "NotStarted",
+ InProgress: "InProgress",
+ Complete: "Complete",
+ Invalid: "Invalid",
+});
+
+/**
+ * Type of an export currently, only GLTF and 3DFT are documented
+ * The CESIUM option is what we were told to use with Sandcastle
+ * I've also seen the IMODEL one but don't know where it's from
+ * @enum {string}
+ */
+const ExportType = Object.freeze({
+ "3DFT": "3DFT",
+ GLFT: "GLTF",
+ IMODEL: "IMODEL",
+ CESIUM: "CESIUM",
+});
+
+/**
+ * @typedef {Object} GeometryOptions
+ * @property {boolean} includeLines
+ * @property {number} chordTol
+ * @property {number} angleTol
+ * @property {number} decimationTol
+ * @property {number} maxEdgeLength
+ * @property {number} minBRepFeatureSize
+ * @property {number} minLineStyleComponentSize
+ */
+
+/**
+ * @typedef {Object} ViewDefinitionFilter
+ * @property {string[]} models Array of included model IDs.
+ * @property {string[]} categories Array of included category IDs.
+ * @property {string[]} neverDrawn Array of element IDs to filter out.
+ */
+
+/**
+ * @typedef {Object} StartExport
+ * @property {string} iModelId
+ * @property {string} changesetId
+ * @property {ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
+ * @property {GeometryOptions} geometryOptions
+ * @property {ViewDefinitionFilter} viewDefinitionFilter
+ */
+
+/**
+ * @typedef {Object} Link
+ * @property {string} href
+ */
+
+/**
+ * @typedef {Object} Export
+ * @property {string} id
+ * @property {string} displayName
+ * @property {ExportStatus} status
+ * @property {StartExport} request
+ * @property {{mesh: Link}} _links
+ */
+
+/**
+ * @typedef {Object} ExportResponse
+ * @property {Export} export
+ */
/**
* Default settings for accessing the iTwin platform.
@@ -34,7 +106,7 @@ const ITwin = {};
ITwin.defaultAccessToken = undefined;
/**
- * Gets or sets the default Google Map Tiles API endpoint.
+ * Gets or sets the default iTwin API endpoint.
*
* @type {string|Resource}
* @default https://api.bentley.com
@@ -43,24 +115,192 @@ ITwin.apiEndpoint = new Resource({
url: "https://api.bentley.com",
});
-// TODO: this should only be needed if we have a way to generate really long term access tokens
-// to sample data that is accessible to everyone
-// ITwin.getDefaultTokenCredit = function (providedKey) {
-// if (providedKey !== defaultAccessToken) {
-// return undefined;
-// }
+/**
+ * @param {string} exportId
+ */
+ITwin.getExport = async function (exportId) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+
+ const headers = {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ };
+
+ // obtain export for specified export id
+ const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
+
+ // TODO: this request is _really_ slow, like 7 whole second alone for me
+ // Arun said this was kinda normal but to keep track of the `x-correlation-id` of any that take EXTRA long
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ throw new RuntimeError(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (response.status === 404) {
+ throw new RuntimeError(`Requested export is not available ${exportId}`);
+ } else if (response.status === 429) {
+ throw new RuntimeError("Too many requests");
+ }
+ throw new RuntimeError(`Unknown request failure ${response.status}`);
+ }
+
+ /** @type {ExportResponse} */
+ const result = await response.json();
+ return result;
+};
+
+/**
+ * Get the list of exports for the given iModel + changeset
+ *
+ * @param {string} iModelId
+ * @param {string} changesetId
+ */
+ITwin.getExports = async function (iModelId, changesetId) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+
+ const headers = {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ Prefer: "return=representation", // or return=minimal (the default)
+ };
+
+ // obtain export for specified export id
+ let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}`;
+ if (defined(changesetId) && changesetId !== "") {
+ url += `&changesetId=${changesetId}`;
+ }
-// if (!defined(defaultTokenCredit)) {
-// const defaultTokenMessage =
-// ' \
-// This application is using Cesium\'s default ion access token. Please assign Cesium.Ion.defaultAccessToken \
-// with an access token from your ion account before making any Cesium API calls. \
-// You can sign up for a free ion account at https://cesium.com.';
+ const response = await fetch(url, { headers });
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ throw new RuntimeError(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (response.status === 422) {
+ throw new RuntimeError(
+ `Unprocessable Entity:${result.error.code} ${result.error.message}`,
+ );
+ } else if (response.status === 429) {
+ throw new RuntimeError("Too many requests");
+ }
+ throw new RuntimeError(`Unknown request failure ${response.status}`);
+ }
+
+ /** @type {{exports: Export[]}} */
+ const result = await response.json();
+ return result;
+};
+
+/**
+ * Start the export process for the given iModel + changeset.
+ *
+ * @param {string} iModelId
+ * @param {string} changesetId
+ */
+ITwin.createExportForModelId = async function (iModelId, changesetId) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+
+ changesetId = changesetId ?? "";
+
+ const requestOptions = {
+ method: "POST",
+ headers: {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ iModelId,
+ changesetId,
+ exportType: "CESIUM",
+ }),
+ };
+
+ // initiate mesh export
+ const response = await fetch(
+ `https://api.bentley.com/mesh-export/`,
+ requestOptions,
+ );
+
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ console.error(
+ result.error.code,
+ result.error.message,
+ result.error.details,
+ );
+ throw new RuntimeError(
+ "Unauthorized, bad token, wrong scopes or headers bad",
+ );
+ } else if (response.status === 403) {
+ console.error(result.error.code, result.error.message);
+ throw new RuntimeError("Not allowed, forbidden");
+ } else if (response.status === 422) {
+ console.error(result.error.code, result.error.message);
+ console.error(result.error.details);
+ throw new RuntimeError("Unprocessable: Cannot create export job");
+ } else if (response.status === 429) {
+ throw new RuntimeError("Too many requests");
+ }
+
+ throw new RuntimeError(`Unknown request failure ${response.status}`);
+ }
+
+ /** @type {ExportResponse} */
+ const result = await response.json();
+ return result.export.id;
+};
+
+/**
+ * Delete the specified export
+ *
+ * TODO: I'm not sure if we want this or not. Might belong better as an APP level function
+ * I just started creating helpers for all the routes under the `mesh-export` API
+ * for ease of access during testing
+ *
+ * @param {string} exportId
+ */
+ITwin.deleteExport = async function (exportId) {
+ if (!defined(ITwin.defaultAccessToken)) {
+ throw new DeveloperError("Must set ITwin.defaultAccessToken first");
+ }
+ const headers = {
+ Authorization: ITwin.defaultAccessToken,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ };
-// defaultTokenCredit = new Credit(defaultTokenMessage, true);
-// }
+ // obtain export for specified export id
+ const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
-// return defaultTokenCredit;
-// };
+ const response = await fetch(url, { method: "DELETE", headers });
+ if (!response.ok) {
+ const result = await response.json();
+ if (response.status === 401) {
+ throw new RuntimeError(
+ `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
+ );
+ } else if (response.status === 404) {
+ throw new RuntimeError("Export not found");
+ } else if (response.status === 422) {
+ throw new RuntimeError(
+ `Unprocessable Entity: ${result.error.code} ${result.error.message}`,
+ );
+ } else if (response.status === 429) {
+ throw new RuntimeError("Too many requests");
+ }
+ throw new RuntimeError(`Unknown request failure ${response.status}`);
+ }
+};
export default ITwin;
+export { ExportStatus, ExportType };
diff --git a/packages/engine/Source/Scene/createIModel3DTileset.js b/packages/engine/Source/Scene/createIModel3DTileset.js
index 1405aac4e5a2..7bbefa6e0e0f 100644
--- a/packages/engine/Source/Scene/createIModel3DTileset.js
+++ b/packages/engine/Source/Scene/createIModel3DTileset.js
@@ -1,81 +1,55 @@
import Cesium3DTileset from "./Cesium3DTileset.js";
import defined from "../Core/defined.js";
import Resource from "../Core/Resource.js";
-import ITwin from "../Core/ITwin.js";
+import ITwin, { ExportStatus, ExportType } from "../Core/ITwin.js";
import DeveloperError from "../Core/DeveloperError.js";
+import RuntimeError from "../Core/RuntimeError.js";
function delay(ms) {
return new Promise((res) => setTimeout(res, ms));
}
/**
- * @enum {string}
+ * @param {Export} exportObj
+ * @param {Cesium3DTileset.ConstructorOptions} [options]
*/
-const ExportStatus = Object.freeze({
- NotStarted: "NotStarted",
- InProgress: "InProgress",
- Complete: "Complete",
- Invalid: "Invalid",
-});
+async function loadExport(exportObj, options) {
+ let status = exportObj.status;
-/**
- * Type of an export currently, only GLTF and 3DFT are documented
- * The CESIUM option is what we were told to use with Sandcastle
- * I've also seen the IMODEL one but don't know where it's from
- * @enum {string}
- */
-const ExportType = Object.freeze({
- "3DFT": "3DFT",
- GLFT: "GLTF",
- IMODEL: "IMODEL",
- CESIUM: "CESIUM",
-});
+ if (exportObj.request.exportType !== ExportType.CESIUM) {
+ // This is an undocumented value but I think it's the only one we want to load
+ // TODO: should we even be checking this?
+ throw new Error(`Wrong export type ${exportObj.request.exportType}`);
+ }
-/**
- * @typedef {Object} GeometryOptions
- * @property {boolean} includeLines
- * @property {number} chordTol
- * @property {number} angleTol
- * @property {number} decimationTol
- * @property {number} maxEdgeLength
- * @property {number} minBRepFeatureSize
- * @property {number} minLineStyleComponentSize
- */
+ const timeoutAfter = 300000;
+ const start = Date.now();
+ // wait until the export is complete
+ while (status !== ExportStatus.Complete) {
+ await delay(5000);
+ exportObj = (await ITwin.getExport(exportObj.id)).export;
+ status = exportObj.status;
+ console.log(`Export is ${status}`);
-/**
- * @typedef {Object} ViewDefinitionFilter
- * @property {string[]} models Array of included model IDs.
- * @property {string[]} categories Array of included category IDs.
- * @property {string[]} neverDrawn Array of element IDs to filter out.
- */
+ if (Date.now() - start > timeoutAfter) {
+ throw new RuntimeError("Export did not complete in time.");
+ }
+ }
-/**
- * @typedef {Object} StartExport
- * @property {string} iModelId
- * @property {string} changesetId
- * @property {ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
- * @property {GeometryOptions} geometryOptions
- * @property {ViewDefinitionFilter} viewDefinitionFilter
- */
+ // This link is only valid 1 hour
+ let tilesetUrl = exportObj._links.mesh.href;
+ const splitStr = tilesetUrl.split("?");
+ // is there a cleaner way to do this?
+ tilesetUrl = `${splitStr[0]}/tileset.json?${splitStr[1]}`;
-/**
- * @typedef {Object} Link
- * @property {string} href
- */
+ const resource = new Resource({
+ url: tilesetUrl,
+ });
-/**
- * @typedef {Object} Export
- * @property {string} id
- * @property {string} displayName
- * @property {ExportStatus} status
- * @property {StartExport} request
- * @property {{mesh: Link}} _links
- */
+ return Cesium3DTileset.fromUrl(resource, options);
+}
-/**
- * @typedef {Object} ExportResponse
- * @property {Export} export
- */
+const createIModel3DTileset = {};
/**
* Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D Tiles tileset.
@@ -101,271 +75,53 @@ const ExportType = Object.freeze({
* console.log(`Error creating tileset: ${error}`);
* }
*/
-async function createIModel3DTileset(exportId, options) {
+createIModel3DTileset.fromExportId = async function (exportId, options) {
if (!defined(ITwin.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
options = options ?? {};
- const timeoutAfter = 300000;
- const start = Date.now();
- let result = await createIModel3DTileset.getExport(exportId);
- let status = result.export.status;
-
- if (result.export.request.exportType !== ExportType.CESIUM) {
- // This is an undocumented value but I think it's the only one we want to load
- // TODO: should we even be checking this?
- throw new Error(`Wrong export type ${result.export.request.exportType}`);
- }
-
- // wait until the export is complete
- while (status !== ExportStatus.Complete) {
- await delay(5000);
- result = await createIModel3DTileset.getExport(exportId);
- status = result.export.status;
- console.log(`Export is ${status}`);
-
- if (Date.now() - start > timeoutAfter) {
- throw new Error("Export did not complete in time.");
- }
- }
-
- // This link is only valid 1 hour
- let tilesetUrl = result.export._links.mesh.href;
- const splitStr = tilesetUrl.split("?");
- // is there a cleaner way to do this?
- tilesetUrl = `${splitStr[0]}/tileset.json?${splitStr[1]}`;
-
- const resource = new Resource({
- url: tilesetUrl,
- });
-
- return Cesium3DTileset.fromUrl(resource, options);
-}
-
-/**
- * @param {string} exportId
- */
-createIModel3DTileset.getExport = async function (exportId) {
- const headers = {
- Authorization: ITwin.defaultAccessToken,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- };
-
- // obtain export for specified export id
- const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
-
- // TODO: this request is _really_ slow, like 7 whole second alone for me
- // Arun said this was kinda normal but to keep track of the `x-correlation-id` of any that take EXTRA long
- const response = await fetch(url, { headers });
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
- throw new Error(
- `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
- );
- } else if (response.status === 404) {
- throw new Error(`Requested export is not available ${exportId}`);
- } else if (response.status === 429) {
- throw new Error("Too many requests");
- }
- throw new Error(`Unknown request failure ${response.status}`);
- }
-
- /** @type {ExportResponse} */
- const result = await response.json();
- return result;
-};
-
-/**
- * Get the list of exports for the given iModel + changeset
- *
- * @param {string} iModelId
- * @param {string} changesetId
- */
-createIModel3DTileset.getExports = async function (iModelId, changesetId) {
- if (!defined(ITwin.defaultAccessToken)) {
- throw new DeveloperError("Must set ITwin.defaultAccessToken first");
- }
- const headers = {
- Authorization: ITwin.defaultAccessToken,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- Prefer: "return=representation", // or return=minimal (the default)
- };
-
- // obtain export for specified export id
- let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}`;
- if (defined(changesetId) && changesetId !== "") {
- url += `&changesetId=${changesetId}`;
- }
-
- const response = await fetch(url, { headers });
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
- throw new Error(
- `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
- );
- } else if (response.status === 422) {
- throw new Error(
- `Unprocessable Entity:${result.error.code} ${result.error.message}`,
- );
- } else if (response.status === 429) {
- throw new Error("Too many requests");
- }
- throw new Error(`Unknown request failure ${response.status}`);
- }
-
- /** @type {{exports: Export[]}} */
- const result = await response.json();
- return result;
+ const result = await ITwin.getExport(exportId);
+ const tileset = await loadExport(result.export, options);
+ return tileset;
};
/**
* Check the exports for the given iModel + changeset combination for any that
- * have the desired CESIUM type and return that one
+ * have the desired CESIUM type and returns the first one that matches as a new tileset.
*
- * @param {string} iModelId
- * @param {string} changesetId
- */
-createIModel3DTileset.checkForCesiumExport = async function (
- iModelId,
- changesetId,
-) {
- const { exports } = await createIModel3DTileset.getExports(
- iModelId,
- changesetId,
- );
- const cesiumExport = exports.find(
- (e) => e.request.exportType === ExportType.CESIUM,
- );
- return cesiumExport;
-};
-
-/**
- * Start the export process for the given iModel + changeset.
+ * If there is not a CESIUM export you can create it using {@link ITwin.createExportForModelId}
*
- * Pair this with the {@link checkForCesiumExport} function to avoid creating extra exports
+ * This function assumes one export per type per "iModel id + changeset id". If you need to create
+ * multiple exports per "iModel id + changeset id" you should switch to using {@link createIModel3DTileset}
+ * with the export id directly
*
* @example
- * const cesiumExport = await Cesium.createIModel3DTileset.checkForCesiumExport(imodelId, changesetId);
- * let exportId = cesiumExport?.id;
- * if (!Cesium.defined(cesiumExport)) {
- * exportId = await Cesium.createIModel3DTileset.createExportForModelId(
- * imodelId,
- * changesetId,
- * accessToken,
- * );
+ * // Try to load the corresponding tileset export or create it if it doesn't exist
+ * let tileset = await Cesium.createIModel3DTileset.fromModelIdimodelId, changesetId);
+ * if (!Cesium.defined(tileset)) {
+ * const exportId = await Cesium.ITwin.createExportForModelId(imodelId, changesetId, accessToken);
+ * tileset = await Cesium.createIModel3DTileset(exportId);
* }
*
* @param {string} iModelId
* @param {string} changesetId
+ * @param {Cesium3DTileset.ConstructorOptions} options
*/
-createIModel3DTileset.createExportForModelId = async function (
+createIModel3DTileset.fromModelId = async function (
iModelId,
changesetId,
+ options,
) {
- if (!defined(ITwin.defaultAccessToken)) {
- throw new DeveloperError("Must set ITwin.defaultAccessToken first");
- }
-
- console.log("Start Export");
-
- changesetId = changesetId ?? "";
-
- const requestOptions = {
- method: "POST",
- headers: {
- Authorization: ITwin.defaultAccessToken,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- iModelId,
- changesetId,
- exportType: "CESIUM",
- }),
- };
-
- // initiate mesh export
- const response = await fetch(
- `https://api.bentley.com/mesh-export/`,
- requestOptions,
+ const { exports } = await ITwin.getExports(iModelId, changesetId);
+ const cesiumExport = exports.find(
+ (exportObj) => exportObj.request?.exportType === ExportType.CESIUM,
);
-
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
- console.error("Unauthorized, bad token, wrong scopes or headers bad");
- console.error(
- result.error.code,
- result.error.message,
- result.error.details,
- );
- } else if (response.status === 403) {
- console.error("Not allowed, forbidden");
- console.error(result.error.code, result.error.message);
- } else if (response.status === 422) {
- console.error("Unprocessable: Cannot create export job");
- console.error(result.error.code, result.error.message);
- console.error(result.error.details);
- } else if (response.status === 429) {
- console.log(
- "Too many requests, retry after:",
- response.headers.get("retry-after"),
- );
- console.error(result.error.code, result.error.message);
- } else {
- console.error("Bad request, unknown error", response);
- }
- return undefined;
- }
-
- /** @type {ExportResponse} */
- const result = await response.json();
- return result.export.id;
-};
-
-/**
- * Delete the specified export
- *
- * TODO: I'm not sure if we want this or not. Might belong better as an APP level function
- * I just started creating helpers for all the routes under the `mesh-export` API
- * for ease of access during testing
- *
- * @param {string} exportId
- */
-createIModel3DTileset.deleteExport = async function (exportId) {
- if (!defined(ITwin.defaultAccessToken)) {
- throw new DeveloperError("Must set ITwin.defaultAccessToken first");
- }
- const headers = {
- Authorization: ITwin.defaultAccessToken,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- };
-
- // obtain export for specified export id
- const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
-
- const response = await fetch(url, { method: "DELETE", headers });
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
- throw new Error(
- `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
- );
- } else if (response.status === 404) {
- throw new Error("Export not found");
- } else if (response.status === 422) {
- throw new Error(
- `Unprocessable Entity:${result.error.code} ${result.error.message}`,
- );
- } else if (response.status === 429) {
- throw new Error("Too many requests");
- }
- throw new Error(`Unknown request failure ${response.status}`);
+ if (!defined(cesiumExport)) {
+ return;
}
+ return loadExport(cesiumExport, options);
};
export default createIModel3DTileset;
From 4ea390891d356d89f47639bebd07e65dff34644a Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Wed, 6 Nov 2024 14:09:02 -0500
Subject: [PATCH 03/22] small adjustments
---
packages/engine/Source/Core/ITwin.js | 22 ++++++----------------
1 file changed, 6 insertions(+), 16 deletions(-)
diff --git a/packages/engine/Source/Core/ITwin.js b/packages/engine/Source/Core/ITwin.js
index ab74ad0c5536..e7d49d3c59c1 100644
--- a/packages/engine/Source/Core/ITwin.js
+++ b/packages/engine/Source/Core/ITwin.js
@@ -21,7 +21,7 @@ const ExportStatus = Object.freeze({
*/
const ExportType = Object.freeze({
"3DFT": "3DFT",
- GLFT: "GLTF",
+ GLTF: "GLTF",
IMODEL: "IMODEL",
CESIUM: "CESIUM",
});
@@ -75,17 +75,7 @@ const ExportType = Object.freeze({
/**
* Default settings for accessing the iTwin platform.
*
- * Keys can be created using the iModels share routes {@link https://developer.bentley.com/apis/imodels-v2/operations/create-imodel-share/}
- *
- * An ion access token is only required if you are using any ion related APIs.
- * A default access token is provided for evaluation purposes only.
- * Sign up for a free ion account and get your own access token at {@link https://cesium.com}
- *
- * @see IonResource
- * @see IonImageryProvider
- * @see IonGeocoderService
- * @see createWorldImagery
- * @see createWorldTerrain
+ * @see createIModel3DTileset
* @namespace ITwin
*/
const ITwin = {};
@@ -99,7 +89,7 @@ const ITwin = {};
* `mesh-export:read` for loading meshes GET /mesh-export(s)
* `mesh-export:modify` if we want to include a function to create an export
* `itwin-platform` if we want to use the iModel shares ourselves GET /imodels/{id}/shares
- *
+ * Seems the `itwin-platform` scope should apply to everything but the docs are a little unclear
*
* @type {string|undefined}
*/
@@ -157,7 +147,7 @@ ITwin.getExport = async function (exportId) {
* Get the list of exports for the given iModel + changeset
*
* @param {string} iModelId
- * @param {string} changesetId
+ * @param {string} [changesetId]
*/
ITwin.getExports = async function (iModelId, changesetId) {
if (!defined(ITwin.defaultAccessToken)) {
@@ -202,7 +192,7 @@ ITwin.getExports = async function (iModelId, changesetId) {
* Start the export process for the given iModel + changeset.
*
* @param {string} iModelId
- * @param {string} changesetId
+ * @param {string} [changesetId]
*/
ITwin.createExportForModelId = async function (iModelId, changesetId) {
if (!defined(ITwin.defaultAccessToken)) {
@@ -227,7 +217,7 @@ ITwin.createExportForModelId = async function (iModelId, changesetId) {
// initiate mesh export
const response = await fetch(
- `https://api.bentley.com/mesh-export/`,
+ `${ITwin.apiEndpoint}mesh-export/`,
requestOptions,
);
From d779bc186441d9661a1e64fa4e08c81bd7abab45 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Mon, 11 Nov 2024 14:25:55 -0500
Subject: [PATCH 04/22] oauth testing web app and service app
---
.prettierignore | 1 +
Apps/Sandcastle/gallery/iTwin Demo.html | 80 ++++++++++++++++-
eslint.config.js | 8 ++
itwin-oauth-demo/.gitignore | 1 +
itwin-oauth-demo/index.html | 34 +++++++
itwin-oauth-demo/server.js | 114 ++++++++++++++++++++++++
packages/engine/Source/Core/ITwin.js | 5 +-
7 files changed, 238 insertions(+), 5 deletions(-)
create mode 100644 itwin-oauth-demo/.gitignore
create mode 100644 itwin-oauth-demo/index.html
create mode 100644 itwin-oauth-demo/server.js
diff --git a/.prettierignore b/.prettierignore
index c46eee96cb50..e0d51a3709d0 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -11,6 +11,7 @@
!packages/**/
!Specs/**/
!Tools/**/
+!itwin-oauth-demo/**/
!**/*.js
!**/*.cjs
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index d8d4865927e2..ef4991471367 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -30,15 +30,87 @@
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
+ const popup = (url) => {
+ // based on https://gist.github.com/gauravtiwari/2ae9f44aee281c759fe5a66d5c2721a2
+ const windowArea = {
+ width: 500,
+ height: 600,
+ };
+
+ windowArea.left = Math.floor(
+ window.screenX + (window.outerWidth - windowArea.width) / 2,
+ );
+ windowArea.top = Math.floor(
+ window.screenY + (window.outerHeight - windowArea.height) / 8,
+ );
+
+ const sep = url.indexOf("?") !== -1 ? "&" : "?";
+ const fullUrl = `${url}${sep}`;
+ const windowOpts = `toolbar=0,scrollbars=1,status=1,resizable=1,location=1,menuBar=0,
+ width=${windowArea.width},height=${windowArea.height},
+ left=${windowArea.left},top=${windowArea.top}`;
+
+ const authWindow = window.open(fullUrl, "popup", windowOpts);
+
+ // Listen to message from child window
+ const authPromise = new Promise((resolve, reject) => {
+ window.addEventListener(
+ "message",
+ (e) => {
+ console.log("message", e);
+ // TODO: if we go this route we may want this back to make sure
+ // it's coming from wherever we host this
+ // if (e.origin !== window.SITE_DOMAIN) {
+ // authWindow.close();
+ // reject("Not allowed");
+ // }
+
+ if (e.data.auth) {
+ resolve(e.data.auth);
+ authWindow.close();
+ } else {
+ authWindow.close();
+ reject("Unauthorised");
+ }
+ },
+ false,
+ );
+ });
+ return authPromise;
+ };
+
+ // ===== Web app oauth flow =====
+
+ // const clientId = "webapp-0MsbOTn56gKXMoI1WsJ0SoVEn";
+ // const redirectUri = "http://localhost:3000";
+
+ // const result = await popup(
+ // `https://ims.bentley.com/connect/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=itwin-platform`,
+ // ).catch((error) => {
+ // console.error(error);
+ // throw new Error("OAuth failed");
+ // });
+ // const accessCode = result.code;
+ // console.log("popup returned", result);
+
+ // ===== Service app request =====
+
+ const serviceResponse = await fetch("http://localhost:3000/service");
+ const serviceResult = await serviceResponse.json();
+ const { token: accessCode } = serviceResult;
+
// must be created for the Start export route if you want to create new exports
// Needs to have the mesh-export:modify scope not just mesh-export:read
- const accessToken =
+ let accessToken =
"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkJlbnRsZXlJTVNfMjAyNCIsInBpLmF0bSI6ImE4bWUifQ.eyJzY29wZSI6WyJtZXNoLWV4cG9ydDpyZWFkIiwibWVzaC1leHBvcnQ6bW9kaWZ5Il0sImNsaWVudF9pZCI6Iml0d2luLWRldmVsb3Blci1jb25zb2xlIiwiYXVkIjpbImh0dHBzOi8vaW1zLmJlbnRsZXkuY29tL2FzL3Rva2VuLm9hdXRoMiIsImh0dHBzOi8vaW1zb2lkYy5iZW50bGV5LmNvbS9hcy90b2tlbi5vYXV0aDIiLCJodHRwczovL2ltc29pZGMuYmVudGxleS5jb20vcmVzb3VyY2VzIiwiYmVudGxleS1hcGktbWFuYWdlbWVudCJdLCJzdWIiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJyb2xlIjoiQkVOVExFWV9FTVBMT1lFRSIsIm9yZyI6ImZhYjk3NzRiLWIzMzgtNGNjMi1hNmM5LTQ1OGJkZjdmOTY2YSIsInN1YmplY3QiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJpc3MiOiJodHRwczovL2ltcy5iZW50bGV5LmNvbSIsImVudGl0bGVtZW50IjpbIkJFTlRMRVlfTEVBUk4iLCJJTlRFUk5BTCIsIlNFTEVDVF8yMDA2IiwiQkVOIiwiQkROIl0sInByZWZlcnJlZF91c2VybmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZ2l2ZW5fbmFtZSI6Ikpvc2giLCJzaWQiOiJHMGZTamlOXzBMRjE1Vy1IYnRTaWtoeDNuSXMuU1UxVExVSmxiblJzWlhrdFZWTS5LbWlWLlF2UExVcGJyc2R3engwWGxPbkF3eTFTT0IiLCJuYmYiOjE3MzA4MzgyNDIsInVsdGltYXRlX3NpdGUiOiIxMDAxMzg5MTE3IiwidXNhZ2VfY291bnRyeV9pc28iOiJVUyIsImF1dGhfdGltZSI6MTczMDgzODU0MiwibmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwib3JnX25hbWUiOiJCZW50bGV5IFN5c3RlbXMgSW5jIiwiZmFtaWx5X25hbWUiOiJSb3V6ZXIiLCJlbWFpbCI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZXhwIjoxNzMwODQyMTQyfQ.N_WrgjL2bqxdNLEM5nHh4Fg-FzeA-qxxpryaoaMKz8onnpgzmp-X8dyQ1TyMRqKQY99iLypE9bU45DwRJs7vBgNQ73d53SkC9TKGYn8AAOTJz8c_oEHgkoTEDaaLsMPCw88tqZ34pY0e0oIHIofVDTCvwlzwaJPADfkIxz-8GzhIt2WrXR7f6LWBFSlWNrtNhIr9Tz7UNaLOh97_3fS9KVU1I084CpBga9cj_mjGBeki7mIEQvpqMj8x2bJPae_c6WrPEjKLayrOzq4q0X0Kvle0ZFAm-Se9MFCusTFS51bYrI3IsjagGeIP2U06zzcMJ22NylomE60hz6GK_4uB3g";
+
+ accessToken = `Bearer ${accessCode}`;
+
Cesium.ITwin.defaultAccessToken = accessToken;
// this is the iModel in the "Hello iTwinCesium" iTwin that we should all have access to
// https://developer.bentley.com/my-itwins/b4a30036-0456-49ea-a439-3fcd9365e24e/home/
- const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
- // const imodelId = "88673c1d-12b8-48f1-8beb-5000d0edbd0b";
+ // const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
+ const imodelId = "88673c1d-12b8-48f1-8beb-5000d0edbd0b";
const changesetId = "";
@@ -107,7 +179,7 @@
const subcategory = feature.getProperty("subcategory");
const message = `
Element ID: ${element}
- Subcategory: ${classes[subcategory] ?? subcategory}
+ Subcategory: ${classes[subcategory] || subcategory}
Feature ID: ${feature.featureId}`;
nameOverlay.textContent = message;
}
diff --git a/eslint.config.js b/eslint.config.js
index c8ec3d9bd70c..84dd74b032c4 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -103,4 +103,12 @@ export default [
"n/no-missing-import": "off",
},
},
+ {
+ files: ["itwin-oauth-demo/*"],
+ languageOptions: {
+ ...configCesium.configs.node.languageOptions,
+ sourceType: "module",
+ ecmaVersion: 2022,
+ },
+ },
];
diff --git a/itwin-oauth-demo/.gitignore b/itwin-oauth-demo/.gitignore
new file mode 100644
index 000000000000..d344ba6b06cb
--- /dev/null
+++ b/itwin-oauth-demo/.gitignore
@@ -0,0 +1 @@
+config.json
diff --git a/itwin-oauth-demo/index.html b/itwin-oauth-demo/index.html
new file mode 100644
index 000000000000..af6bc98d6499
--- /dev/null
+++ b/itwin-oauth-demo/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+ Auth!
+
+
+
+
+
diff --git a/itwin-oauth-demo/server.js b/itwin-oauth-demo/server.js
new file mode 100644
index 000000000000..790b96c9958a
--- /dev/null
+++ b/itwin-oauth-demo/server.js
@@ -0,0 +1,114 @@
+import express from "express";
+import { readFileSync, writeFileSync } from "fs";
+import { dirname, join } from "path";
+import { exit } from "process";
+import { fileURLToPath } from "url";
+
+let config = {
+ webapp: {
+ clientId: "",
+ clientSecret: "",
+ },
+ serviceapp: {
+ clientId: "",
+ clientSecret: "",
+ },
+ port: 3000,
+ redirectUri: "http://localhost:3000",
+};
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const configPath = join(__dirname, "./config.json");
+try {
+ const configFile = readFileSync(configPath, { encoding: "utf-8" });
+ config = JSON.parse(configFile);
+} catch {
+ console.log("config file missing, default written to", configPath);
+ console.log("Please update the config with the desired values");
+ writeFileSync(configPath, JSON.stringify(config, undefined, 2));
+ exit(1);
+}
+
+const app = express();
+const port = config.port ?? 3000;
+const redirectUri = config.redirectUri ?? "http://localhost:3000";
+
+// eslint-disable-next-line no-unused-vars
+app.get("/", async (req, res) => {
+ res.sendFile(join(__dirname, "./index.html"));
+});
+
+app.get("/token", async (req, res) => {
+ console.log("/token request received");
+ const { code } = req.query;
+
+ if (!code) {
+ res.status(404).send("Code missing");
+ }
+
+ const body = new URLSearchParams();
+ body.set("grant_type", "authorization_code");
+ body.set("client_id", config.webapp.clientId);
+ body.set("client_secret", config.webapp.clientSecret);
+ body.set("code", code);
+ body.set("redirect_uri", redirectUri);
+
+ const response = await fetch("https://ims.bentley.com/connect/token", {
+ method: "POST",
+ body,
+ });
+
+ const result = await response.json();
+
+ if (!response.ok || !result) {
+ console.log(" bad response/no result");
+ res.status(response.status).send();
+ return;
+ }
+ const { access_token } = result;
+ if (access_token) {
+ console.log(" token acquired, returned");
+ res.status(200).send({ token: access_token });
+ return;
+ }
+ console.log(" token not found");
+ res.status(404).send("token not found");
+});
+
+// eslint-disable-next-line no-unused-vars
+app.get("/service", async (req, res) => {
+ console.log("/service request received");
+
+ const body = new URLSearchParams();
+ body.set("grant_type", "client_credentials");
+ body.set("client_id", config.serviceapp.clientId);
+ body.set("client_secret", config.serviceapp.clientSecret);
+ body.set("scope", "itwin-platform");
+
+ const response = await fetch("https://ims.bentley.com/connect/token", {
+ method: "POST",
+ body,
+ });
+
+ const result = await response.json();
+
+ res.setHeader("Access-Control-Allow-Origin", "*");
+
+ if (!response.ok || !result) {
+ console.log(" bad response/no result");
+ res.status(response.status).send();
+ return;
+ }
+ const { access_token } = result;
+ if (access_token) {
+ console.log(" token acquired, returned");
+ res.status(200).send({ token: access_token });
+ return;
+ }
+ console.log(" token not found");
+ res.status(404).send("token not found");
+});
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`);
+});
diff --git a/packages/engine/Source/Core/ITwin.js b/packages/engine/Source/Core/ITwin.js
index e7d49d3c59c1..e3ed43b1ee68 100644
--- a/packages/engine/Source/Core/ITwin.js
+++ b/packages/engine/Source/Core/ITwin.js
@@ -161,7 +161,7 @@ ITwin.getExports = async function (iModelId, changesetId) {
};
// obtain export for specified export id
- let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}`;
+ let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}&exportType=CESIUM&$top=1`;
if (defined(changesetId) && changesetId !== "") {
url += `&changesetId=${changesetId}`;
}
@@ -173,6 +173,9 @@ ITwin.getExports = async function (iModelId, changesetId) {
throw new RuntimeError(
`Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
);
+ } else if (response.status === 403) {
+ console.error(result.error.code, result.error.message);
+ throw new RuntimeError("Not allowed, forbidden");
} else if (response.status === 422) {
throw new RuntimeError(
`Unprocessable Entity:${result.error.code} ${result.error.message}`,
From 50dfe46a90342611a1fc5c5d52ceb50fcfd54496 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Thu, 14 Nov 2024 13:18:25 -0500
Subject: [PATCH 05/22] small cleanup and removing code we won't use
---
Apps/Sandcastle/gallery/iTwin Demo.html | 103 +-----------------
itwin-oauth-demo/server.js | 48 --------
packages/engine/Source/Core/ITwin.js | 67 ++++--------
.../Source/Scene/createIModel3DTileset.js | 26 ++---
4 files changed, 33 insertions(+), 211 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index ef4991471367..eeea23b17f51 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -30,95 +30,17 @@
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
- const popup = (url) => {
- // based on https://gist.github.com/gauravtiwari/2ae9f44aee281c759fe5a66d5c2721a2
- const windowArea = {
- width: 500,
- height: 600,
- };
-
- windowArea.left = Math.floor(
- window.screenX + (window.outerWidth - windowArea.width) / 2,
- );
- windowArea.top = Math.floor(
- window.screenY + (window.outerHeight - windowArea.height) / 8,
- );
-
- const sep = url.indexOf("?") !== -1 ? "&" : "?";
- const fullUrl = `${url}${sep}`;
- const windowOpts = `toolbar=0,scrollbars=1,status=1,resizable=1,location=1,menuBar=0,
- width=${windowArea.width},height=${windowArea.height},
- left=${windowArea.left},top=${windowArea.top}`;
-
- const authWindow = window.open(fullUrl, "popup", windowOpts);
-
- // Listen to message from child window
- const authPromise = new Promise((resolve, reject) => {
- window.addEventListener(
- "message",
- (e) => {
- console.log("message", e);
- // TODO: if we go this route we may want this back to make sure
- // it's coming from wherever we host this
- // if (e.origin !== window.SITE_DOMAIN) {
- // authWindow.close();
- // reject("Not allowed");
- // }
-
- if (e.data.auth) {
- resolve(e.data.auth);
- authWindow.close();
- } else {
- authWindow.close();
- reject("Unauthorised");
- }
- },
- false,
- );
- });
- return authPromise;
- };
-
- // ===== Web app oauth flow =====
-
- // const clientId = "webapp-0MsbOTn56gKXMoI1WsJ0SoVEn";
- // const redirectUri = "http://localhost:3000";
-
- // const result = await popup(
- // `https://ims.bentley.com/connect/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=itwin-platform`,
- // ).catch((error) => {
- // console.error(error);
- // throw new Error("OAuth failed");
- // });
- // const accessCode = result.code;
- // console.log("popup returned", result);
-
- // ===== Service app request =====
-
const serviceResponse = await fetch("http://localhost:3000/service");
- const serviceResult = await serviceResponse.json();
- const { token: accessCode } = serviceResult;
-
- // must be created for the Start export route if you want to create new exports
- // Needs to have the mesh-export:modify scope not just mesh-export:read
- let accessToken =
- "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkJlbnRsZXlJTVNfMjAyNCIsInBpLmF0bSI6ImE4bWUifQ.eyJzY29wZSI6WyJtZXNoLWV4cG9ydDpyZWFkIiwibWVzaC1leHBvcnQ6bW9kaWZ5Il0sImNsaWVudF9pZCI6Iml0d2luLWRldmVsb3Blci1jb25zb2xlIiwiYXVkIjpbImh0dHBzOi8vaW1zLmJlbnRsZXkuY29tL2FzL3Rva2VuLm9hdXRoMiIsImh0dHBzOi8vaW1zb2lkYy5iZW50bGV5LmNvbS9hcy90b2tlbi5vYXV0aDIiLCJodHRwczovL2ltc29pZGMuYmVudGxleS5jb20vcmVzb3VyY2VzIiwiYmVudGxleS1hcGktbWFuYWdlbWVudCJdLCJzdWIiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJyb2xlIjoiQkVOVExFWV9FTVBMT1lFRSIsIm9yZyI6ImZhYjk3NzRiLWIzMzgtNGNjMi1hNmM5LTQ1OGJkZjdmOTY2YSIsInN1YmplY3QiOiJjMWM1MzRhNy0zZDk2LTQ2MzMtYjY5ZC1jMGEzNzE5OWQwZGUiLCJpc3MiOiJodHRwczovL2ltcy5iZW50bGV5LmNvbSIsImVudGl0bGVtZW50IjpbIkJFTlRMRVlfTEVBUk4iLCJJTlRFUk5BTCIsIlNFTEVDVF8yMDA2IiwiQkVOIiwiQkROIl0sInByZWZlcnJlZF91c2VybmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZ2l2ZW5fbmFtZSI6Ikpvc2giLCJzaWQiOiJHMGZTamlOXzBMRjE1Vy1IYnRTaWtoeDNuSXMuU1UxVExVSmxiblJzWlhrdFZWTS5LbWlWLlF2UExVcGJyc2R3engwWGxPbkF3eTFTT0IiLCJuYmYiOjE3MzA4MzgyNDIsInVsdGltYXRlX3NpdGUiOiIxMDAxMzg5MTE3IiwidXNhZ2VfY291bnRyeV9pc28iOiJVUyIsImF1dGhfdGltZSI6MTczMDgzODU0MiwibmFtZSI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwib3JnX25hbWUiOiJCZW50bGV5IFN5c3RlbXMgSW5jIiwiZmFtaWx5X25hbWUiOiJSb3V6ZXIiLCJlbWFpbCI6Ikpvc2guUm91emVyQGJlbnRsZXkuY29tIiwiZXhwIjoxNzMwODQyMTQyfQ.N_WrgjL2bqxdNLEM5nHh4Fg-FzeA-qxxpryaoaMKz8onnpgzmp-X8dyQ1TyMRqKQY99iLypE9bU45DwRJs7vBgNQ73d53SkC9TKGYn8AAOTJz8c_oEHgkoTEDaaLsMPCw88tqZ34pY0e0oIHIofVDTCvwlzwaJPADfkIxz-8GzhIt2WrXR7f6LWBFSlWNrtNhIr9Tz7UNaLOh97_3fS9KVU1I084CpBga9cj_mjGBeki7mIEQvpqMj8x2bJPae_c6WrPEjKLayrOzq4q0X0Kvle0ZFAm-Se9MFCusTFS51bYrI3IsjagGeIP2U06zzcMJ22NylomE60hz6GK_4uB3g";
-
- accessToken = `Bearer ${accessCode}`;
+ const { token } = await serviceResponse.json();
+ const accessToken = `Bearer ${token}`;
Cesium.ITwin.defaultAccessToken = accessToken;
// this is the iModel in the "Hello iTwinCesium" iTwin that we should all have access to
// https://developer.bentley.com/my-itwins/b4a30036-0456-49ea-a439-3fcd9365e24e/home/
// const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
const imodelId = "88673c1d-12b8-48f1-8beb-5000d0edbd0b";
-
const changesetId = "";
- // const knownExportId = undefined;
- // const knownExportId = "ab9953b2-bc8e-48ac-a5b0-5d43d68593e8";
-
- const delay = (ms) => new Promise((res) => setTimeout(res, ms));
-
// Grabbed mapping from the iTwin Viewer
const classes = {
2199023255632: "Building Roof",
@@ -196,24 +118,7 @@
const statusOutput = document.querySelector("#status");
async function init() {
- // TODO: just for testing
- // await Cesium.ITwin.deleteExport("8bd9c52e-b379-4f7d-bf39-c45954030b26");
- // console.log(await Cesium.ITwin.getExports(imodelId));
-
statusOutput.innerText = "Starting export";
- // const exportId =
- // knownExportId ??
- // (await Cesium.ITwin.createExportForModelId(
- // imodelId,
- // "",
- // accessToken,
- // ));
-
- // if (!exportId) {
- // console.error("No export id returned");
- // return;
- // }
- // console.log("Using export id", exportId);
const start = Date.now();
@@ -224,6 +129,8 @@
changesetId,
);
if (!Cesium.defined(tileset)) {
+ // TODO: this is temporary, we should not have to call the Start Export route ever after
+ // auto generation is set up
statusOutput.innerText = "Starting export";
const exportId = await Cesium.ITwin.createExportForModelId(
imodelId,
@@ -234,7 +141,6 @@
tileset = await Cesium.createIModel3DTileset.fromExportId(exportId);
}
- // const tileset = await Cesium.createIModel3DTileset(exportId);
scene.primitives.add(tileset);
tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE;
@@ -267,7 +173,6 @@
}
console.error(error);
});
-
//Sandcastle_End
Sandcastle.finishedLoading();
};
diff --git a/itwin-oauth-demo/server.js b/itwin-oauth-demo/server.js
index 790b96c9958a..8a935343622e 100644
--- a/itwin-oauth-demo/server.js
+++ b/itwin-oauth-demo/server.js
@@ -5,16 +5,11 @@ import { exit } from "process";
import { fileURLToPath } from "url";
let config = {
- webapp: {
- clientId: "",
- clientSecret: "",
- },
serviceapp: {
clientId: "",
clientSecret: "",
},
port: 3000,
- redirectUri: "http://localhost:3000",
};
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -31,49 +26,6 @@ try {
const app = express();
const port = config.port ?? 3000;
-const redirectUri = config.redirectUri ?? "http://localhost:3000";
-
-// eslint-disable-next-line no-unused-vars
-app.get("/", async (req, res) => {
- res.sendFile(join(__dirname, "./index.html"));
-});
-
-app.get("/token", async (req, res) => {
- console.log("/token request received");
- const { code } = req.query;
-
- if (!code) {
- res.status(404).send("Code missing");
- }
-
- const body = new URLSearchParams();
- body.set("grant_type", "authorization_code");
- body.set("client_id", config.webapp.clientId);
- body.set("client_secret", config.webapp.clientSecret);
- body.set("code", code);
- body.set("redirect_uri", redirectUri);
-
- const response = await fetch("https://ims.bentley.com/connect/token", {
- method: "POST",
- body,
- });
-
- const result = await response.json();
-
- if (!response.ok || !result) {
- console.log(" bad response/no result");
- res.status(response.status).send();
- return;
- }
- const { access_token } = result;
- if (access_token) {
- console.log(" token acquired, returned");
- res.status(200).send({ token: access_token });
- return;
- }
- console.log(" token not found");
- res.status(404).send("token not found");
-});
// eslint-disable-next-line no-unused-vars
app.get("/service", async (req, res) => {
diff --git a/packages/engine/Source/Core/ITwin.js b/packages/engine/Source/Core/ITwin.js
index e3ed43b1ee68..51a89e15668d 100644
--- a/packages/engine/Source/Core/ITwin.js
+++ b/packages/engine/Source/Core/ITwin.js
@@ -20,10 +20,9 @@ const ExportStatus = Object.freeze({
* @enum {string}
*/
const ExportType = Object.freeze({
- "3DFT": "3DFT",
- GLTF: "GLTF",
IMODEL: "IMODEL",
CESIUM: "CESIUM",
+ "3DTILES": "3DTILES",
});
/**
@@ -75,6 +74,8 @@ const ExportType = Object.freeze({
/**
* Default settings for accessing the iTwin platform.
*
+ * @experimental
+ *
* @see createIModel3DTileset
* @namespace ITwin
*/
@@ -91,6 +92,8 @@ const ITwin = {};
* `itwin-platform` if we want to use the iModel shares ourselves GET /imodels/{id}/shares
* Seems the `itwin-platform` scope should apply to everything but the docs are a little unclear
*
+ * @experimental
+ *
* @type {string|undefined}
*/
ITwin.defaultAccessToken = undefined;
@@ -98,6 +101,8 @@ ITwin.defaultAccessToken = undefined;
/**
* Gets or sets the default iTwin API endpoint.
*
+ * @experimental
+ *
* @type {string|Resource}
* @default https://api.bentley.com
*/
@@ -106,6 +111,10 @@ ITwin.apiEndpoint = new Resource({
});
/**
+ * Get the export object for the specified export id
+ *
+ * @experimental
+ *
* @param {string} exportId
*/
ITwin.getExport = async function (exportId) {
@@ -146,6 +155,8 @@ ITwin.getExport = async function (exportId) {
/**
* Get the list of exports for the given iModel + changeset
*
+ * @experimental
+ *
* @param {string} iModelId
* @param {string} [changesetId]
*/
@@ -161,7 +172,8 @@ ITwin.getExports = async function (iModelId, changesetId) {
};
// obtain export for specified export id
- let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}&exportType=CESIUM&$top=1`;
+ // TODO: if we do include the clientVersion what should it be set to? can we sync it with the package.json?
+ let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}&exportType=${ExportType["3DTILES"]}&$top=1&client=CesiumJS&clientVersion=1.123`;
if (defined(changesetId) && changesetId !== "") {
url += `&changesetId=${changesetId}`;
}
@@ -194,6 +206,12 @@ ITwin.getExports = async function (iModelId, changesetId) {
/**
* Start the export process for the given iModel + changeset.
*
+ * TODO: REMOVE THIS FUNCTION! Auto generation of exports for the 3DTILES type is planned very soon
+ * and will be the desired way of interacting with iModels through exports. This function is here
+ * just while we continue testing during the PR process.
+ *
+ * @experimental
+ *
* @param {string} iModelId
* @param {string} [changesetId]
*/
@@ -214,7 +232,7 @@ ITwin.createExportForModelId = async function (iModelId, changesetId) {
body: JSON.stringify({
iModelId,
changesetId,
- exportType: "CESIUM",
+ exportType: ExportType["3DTILES"],
}),
};
@@ -254,46 +272,5 @@ ITwin.createExportForModelId = async function (iModelId, changesetId) {
return result.export.id;
};
-/**
- * Delete the specified export
- *
- * TODO: I'm not sure if we want this or not. Might belong better as an APP level function
- * I just started creating helpers for all the routes under the `mesh-export` API
- * for ease of access during testing
- *
- * @param {string} exportId
- */
-ITwin.deleteExport = async function (exportId) {
- if (!defined(ITwin.defaultAccessToken)) {
- throw new DeveloperError("Must set ITwin.defaultAccessToken first");
- }
- const headers = {
- Authorization: ITwin.defaultAccessToken,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- };
-
- // obtain export for specified export id
- const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
-
- const response = await fetch(url, { method: "DELETE", headers });
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
- throw new RuntimeError(
- `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
- );
- } else if (response.status === 404) {
- throw new RuntimeError("Export not found");
- } else if (response.status === 422) {
- throw new RuntimeError(
- `Unprocessable Entity: ${result.error.code} ${result.error.message}`,
- );
- } else if (response.status === 429) {
- throw new RuntimeError("Too many requests");
- }
- throw new RuntimeError(`Unknown request failure ${response.status}`);
- }
-};
-
export default ITwin;
export { ExportStatus, ExportType };
diff --git a/packages/engine/Source/Scene/createIModel3DTileset.js b/packages/engine/Source/Scene/createIModel3DTileset.js
index 7bbefa6e0e0f..9e5908bee402 100644
--- a/packages/engine/Source/Scene/createIModel3DTileset.js
+++ b/packages/engine/Source/Scene/createIModel3DTileset.js
@@ -16,7 +16,7 @@ function delay(ms) {
async function loadExport(exportObj, options) {
let status = exportObj.status;
- if (exportObj.request.exportType !== ExportType.CESIUM) {
+ if (exportObj.request.exportType !== ExportType["3DTILES"]) {
// This is an undocumented value but I think it's the only one we want to load
// TODO: should we even be checking this?
throw new Error(`Wrong export type ${exportObj.request.exportType}`);
@@ -55,6 +55,7 @@ const createIModel3DTileset = {};
* Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D Tiles tileset.
*
* @function
+ * @experimental
*
* @param {string} exportId
* @param {Cesium3DTileset.ConstructorOptions} [options] An object describing initialization options.
@@ -63,17 +64,7 @@ const createIModel3DTileset = {};
* @see ITwin
*
* @example
- * // Use your own iTwin API key for mesh export
- * Cesium.ITwin.defaultApiKey = "your-api-key";
- *
- * const viewer = new Cesium.Viewer("cesiumContainer");
- *
- * try {
- * const tileset = await Cesium.createIModel3DTileset();
- * viewer.scene.primitives.add(tileset));
- * } catch (error) {
- * console.log(`Error creating tileset: ${error}`);
- * }
+ * TODO: example after API finalized
*/
createIModel3DTileset.fromExportId = async function (exportId, options) {
if (!defined(ITwin.defaultAccessToken)) {
@@ -98,12 +89,9 @@ createIModel3DTileset.fromExportId = async function (exportId, options) {
* with the export id directly
*
* @example
- * // Try to load the corresponding tileset export or create it if it doesn't exist
- * let tileset = await Cesium.createIModel3DTileset.fromModelIdimodelId, changesetId);
- * if (!Cesium.defined(tileset)) {
- * const exportId = await Cesium.ITwin.createExportForModelId(imodelId, changesetId, accessToken);
- * tileset = await Cesium.createIModel3DTileset(exportId);
- * }
+ * TODO: example after API finalized
+ *
+ * @experimental
*
* @param {string} iModelId
* @param {string} changesetId
@@ -116,7 +104,7 @@ createIModel3DTileset.fromModelId = async function (
) {
const { exports } = await ITwin.getExports(iModelId, changesetId);
const cesiumExport = exports.find(
- (exportObj) => exportObj.request?.exportType === ExportType.CESIUM,
+ (exportObj) => exportObj.request?.exportType === ExportType["3DTILES"],
);
if (!defined(cesiumExport)) {
return;
From c953b6f91ba08999cbef0a979df82d031955d4fe Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Fri, 15 Nov 2024 15:58:10 -0500
Subject: [PATCH 06/22] cleanup and adjustments from pr comments
---
Apps/Sandcastle/gallery/iTwin Demo.html | 4 +-
packages/engine/Source/Core/ITwin.js | 111 ++++++++++++------
.../Source/Scene/createIModel3DTileset.js | 41 ++++---
3 files changed, 97 insertions(+), 59 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index eeea23b17f51..a26bc50f5dbb 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -32,9 +32,8 @@
//Sandcastle_Begin
const serviceResponse = await fetch("http://localhost:3000/service");
const { token } = await serviceResponse.json();
- const accessToken = `Bearer ${token}`;
- Cesium.ITwin.defaultAccessToken = accessToken;
+ Cesium.ITwin.defaultAccessToken = token;
// this is the iModel in the "Hello iTwinCesium" iTwin that we should all have access to
// https://developer.bentley.com/my-itwins/b4a30036-0456-49ea-a439-3fcd9365e24e/home/
// const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
@@ -135,7 +134,6 @@
const exportId = await Cesium.ITwin.createExportForModelId(
imodelId,
changesetId,
- accessToken,
);
statusOutput.innerText = "Creating Tileset from export";
tileset = await Cesium.createIModel3DTileset.fromExportId(exportId);
diff --git a/packages/engine/Source/Core/ITwin.js b/packages/engine/Source/Core/ITwin.js
index 51a89e15668d..249acd76ef88 100644
--- a/packages/engine/Source/Core/ITwin.js
+++ b/packages/engine/Source/Core/ITwin.js
@@ -1,30 +1,9 @@
+import Check from "./Check.js";
import defined from "./defined.js";
import DeveloperError from "./DeveloperError.js";
import Resource from "./Resource.js";
import RuntimeError from "./RuntimeError.js";
-/**
- * @enum {string}
- */
-const ExportStatus = Object.freeze({
- NotStarted: "NotStarted",
- InProgress: "InProgress",
- Complete: "Complete",
- Invalid: "Invalid",
-});
-
-/**
- * Type of an export currently, only GLTF and 3DFT are documented
- * The CESIUM option is what we were told to use with Sandcastle
- * I've also seen the IMODEL one but don't know where it's from
- * @enum {string}
- */
-const ExportType = Object.freeze({
- IMODEL: "IMODEL",
- CESIUM: "CESIUM",
- "3DTILES": "3DTILES",
-});
-
/**
* @typedef {Object} GeometryOptions
* @property {boolean} includeLines
@@ -47,7 +26,7 @@ const ExportType = Object.freeze({
* @typedef {Object} StartExport
* @property {string} iModelId
* @property {string} changesetId
- * @property {ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
+ * @property {ITwin.ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
* @property {GeometryOptions} geometryOptions
* @property {ViewDefinitionFilter} viewDefinitionFilter
*/
@@ -61,7 +40,7 @@ const ExportType = Object.freeze({
* @typedef {Object} Export
* @property {string} id
* @property {string} displayName
- * @property {ExportStatus} status
+ * @property {ITwin.ExportStatus} status
* @property {StartExport} request
* @property {{mesh: Link}} _links
*/
@@ -74,13 +53,32 @@ const ExportType = Object.freeze({
/**
* Default settings for accessing the iTwin platform.
*
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @see createIModel3DTileset
* @namespace ITwin
*/
const ITwin = {};
+/**
+ * @enum {string}
+ */
+ITwin.ExportStatus = Object.freeze({
+ NotStarted: "NotStarted",
+ InProgress: "InProgress",
+ Complete: "Complete",
+ Invalid: "Invalid",
+});
+
+/**
+ * @enum {string}
+ */
+ITwin.ExportType = Object.freeze({
+ IMODEL: "IMODEL",
+ CESIUM: "CESIUM",
+ "3DTILES": "3DTILES",
+});
+
/**
* Gets or sets the default iTwin access token.
*
@@ -92,7 +90,7 @@ const ITwin = {};
* `itwin-platform` if we want to use the iModel shares ourselves GET /imodels/{id}/shares
* Seems the `itwin-platform` scope should apply to everything but the docs are a little unclear
*
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @type {string|undefined}
*/
@@ -101,7 +99,7 @@ ITwin.defaultAccessToken = undefined;
/**
* Gets or sets the default iTwin API endpoint.
*
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @type {string|Resource}
* @default https://api.bentley.com
@@ -113,17 +111,25 @@ ITwin.apiEndpoint = new Resource({
/**
* Get the export object for the specified export id
*
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @param {string} exportId
+ *
+ * @throws {RuntimeError} Unauthorized, bad token, wrong scopes or headers bad.
+ * @throws {RuntimeError} Requested export is not available
+ * @throws {RuntimeError} Too many requests
+ * @throws {RuntimeError} Unknown request failure
*/
ITwin.getExport = async function (exportId) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string("exportId", exportId);
if (!defined(ITwin.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
+ //>>includeEnd('debug')
const headers = {
- Authorization: ITwin.defaultAccessToken,
+ Authorization: `Bearer ${ITwin.defaultAccessToken}`,
Accept: "application/vnd.bentley.itwin-platform.v1+json",
};
@@ -155,27 +161,47 @@ ITwin.getExport = async function (exportId) {
/**
* Get the list of exports for the given iModel + changeset
*
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @param {string} iModelId
* @param {string} [changesetId]
+ *
+ * @throws {RuntimeError} Unauthorized, bad token, wrong scopes or headers bad.
+ * @throws {RuntimeError} Not allowed, forbidden
+ * @throws {RuntimeError} Unprocessable Entity
+ * @throws {RuntimeError} Too many requests
+ * @throws {RuntimeError} Unknown request failure
*/
ITwin.getExports = async function (iModelId, changesetId) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string("iModelId", iModelId);
+ if (defined(changesetId)) {
+ Check.typeOf.string("changesetId", changesetId);
+ }
if (!defined(ITwin.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
+ //>>includeEnd('debug')
const headers = {
- Authorization: ITwin.defaultAccessToken,
+ Authorization: `Bearer ${ITwin.defaultAccessToken}`,
Accept: "application/vnd.bentley.itwin-platform.v1+json",
Prefer: "return=representation", // or return=minimal (the default)
};
// obtain export for specified export id
// TODO: if we do include the clientVersion what should it be set to? can we sync it with the package.json?
- let url = `${ITwin.apiEndpoint}mesh-export/?iModelId=${iModelId}&exportType=${ExportType["3DTILES"]}&$top=1&client=CesiumJS&clientVersion=1.123`;
+ const url = new URL(`${ITwin.apiEndpoint}mesh-export`);
+ url.searchParams.set("iModelId", iModelId);
if (defined(changesetId) && changesetId !== "") {
- url += `&changesetId=${changesetId}`;
+ url.searchParams.set("changesetId", changesetId);
+ }
+ url.searchParams.set("exportType", ITwin.ExportType["3DTILES"]);
+ url.searchParams.set("$top", "1");
+ url.searchParams.set("client", "CesiumJS");
+ /* global CESIUM_VERSION */
+ if (typeof CESIUM_VERSION !== "undefined") {
+ url.searchParams.set("clientVersion", CESIUM_VERSION);
}
const response = await fetch(url, { headers });
@@ -210,29 +236,41 @@ ITwin.getExports = async function (iModelId, changesetId) {
* and will be the desired way of interacting with iModels through exports. This function is here
* just while we continue testing during the PR process.
*
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @param {string} iModelId
* @param {string} [changesetId]
+ *
+ * @throws {RuntimeError} Unauthorized, bad token, wrong scopes or headers bad.
+ * @throws {RuntimeError} Not allowed, forbidden
+ * @throws {RuntimeError} Unprocessable: Cannot create export job
+ * @throws {RuntimeError} Too many requests
+ * @throws {RuntimeError} Unknown request failure
*/
ITwin.createExportForModelId = async function (iModelId, changesetId) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string("iModelId", iModelId);
+ if (defined(changesetId)) {
+ Check.typeOf.string("changesetId", changesetId);
+ }
if (!defined(ITwin.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
+ //>>includeEnd('debug')
changesetId = changesetId ?? "";
const requestOptions = {
method: "POST",
headers: {
- Authorization: ITwin.defaultAccessToken,
+ Authorization: `Bearer ${ITwin.defaultAccessToken}`,
Accept: "application/vnd.bentley.itwin-platform.v1+json",
"Content-Type": "application/json",
},
body: JSON.stringify({
iModelId,
changesetId,
- exportType: ExportType["3DTILES"],
+ exportType: ITwin.ExportType["3DTILES"],
}),
};
@@ -273,4 +311,3 @@ ITwin.createExportForModelId = async function (iModelId, changesetId) {
};
export default ITwin;
-export { ExportStatus, ExportType };
diff --git a/packages/engine/Source/Scene/createIModel3DTileset.js b/packages/engine/Source/Scene/createIModel3DTileset.js
index 9e5908bee402..f4ebb904318b 100644
--- a/packages/engine/Source/Scene/createIModel3DTileset.js
+++ b/packages/engine/Source/Scene/createIModel3DTileset.js
@@ -1,9 +1,9 @@
import Cesium3DTileset from "./Cesium3DTileset.js";
import defined from "../Core/defined.js";
import Resource from "../Core/Resource.js";
-import ITwin, { ExportStatus, ExportType } from "../Core/ITwin.js";
-import DeveloperError from "../Core/DeveloperError.js";
+import ITwin from "../Core/ITwin.js";
import RuntimeError from "../Core/RuntimeError.js";
+import Check from "../Core/Check.js";
function delay(ms) {
return new Promise((res) => setTimeout(res, ms));
@@ -14,18 +14,20 @@ function delay(ms) {
* @param {Cesium3DTileset.ConstructorOptions} [options]
*/
async function loadExport(exportObj, options) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("exportObj", exportObj);
+ //>>includeEnd('debug')
+
let status = exportObj.status;
- if (exportObj.request.exportType !== ExportType["3DTILES"]) {
- // This is an undocumented value but I think it's the only one we want to load
- // TODO: should we even be checking this?
- throw new Error(`Wrong export type ${exportObj.request.exportType}`);
+ if (exportObj.request.exportType !== ITwin.ExportType["3DTILES"]) {
+ throw new RuntimeError(`Wrong export type ${exportObj.request.exportType}`);
}
const timeoutAfter = 300000;
const start = Date.now();
// wait until the export is complete
- while (status !== ExportStatus.Complete) {
+ while (status !== ITwin.ExportStatus.Complete) {
await delay(5000);
exportObj = (await ITwin.getExport(exportObj.id)).export;
status = exportObj.status;
@@ -36,11 +38,11 @@ async function loadExport(exportObj, options) {
}
}
+ // Convert the link to the tileset url while preserving the search paramaters
// This link is only valid 1 hour
- let tilesetUrl = exportObj._links.mesh.href;
- const splitStr = tilesetUrl.split("?");
- // is there a cleaner way to do this?
- tilesetUrl = `${splitStr[0]}/tileset.json?${splitStr[1]}`;
+ const baseUrl = new URL(exportObj._links.mesh.href);
+ baseUrl.pathname = `${baseUrl.pathname}/tileset.json`;
+ const tilesetUrl = baseUrl.toString();
const resource = new Resource({
url: tilesetUrl,
@@ -55,22 +57,19 @@ const createIModel3DTileset = {};
* Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D Tiles tileset.
*
* @function
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @param {string} exportId
* @param {Cesium3DTileset.ConstructorOptions} [options] An object describing initialization options.
* @returns {Promise}
*
- * @see ITwin
+ * @throws {RuntimeError} Wrong export type
+ * @throws {RuntimeError} Export did not complete in time.
*
* @example
* TODO: example after API finalized
*/
createIModel3DTileset.fromExportId = async function (exportId, options) {
- if (!defined(ITwin.defaultAccessToken)) {
- throw new DeveloperError("Must set ITwin.defaultAccessToken first");
- }
-
options = options ?? {};
const result = await ITwin.getExport(exportId);
@@ -91,11 +90,14 @@ createIModel3DTileset.fromExportId = async function (exportId, options) {
* @example
* TODO: example after API finalized
*
- * @experimental
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @param {string} iModelId
* @param {string} changesetId
* @param {Cesium3DTileset.ConstructorOptions} options
+ *
+ * @throws {RuntimeError} Wrong export type
+ * @throws {RuntimeError} Export did not complete in time.
*/
createIModel3DTileset.fromModelId = async function (
iModelId,
@@ -104,7 +106,8 @@ createIModel3DTileset.fromModelId = async function (
) {
const { exports } = await ITwin.getExports(iModelId, changesetId);
const cesiumExport = exports.find(
- (exportObj) => exportObj.request?.exportType === ExportType["3DTILES"],
+ (exportObj) =>
+ exportObj.request?.exportType === ITwin.ExportType["3DTILES"],
);
if (!defined(cesiumExport)) {
return;
From 46bd2f004681ff700989c309477e8dfff4852d19 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Fri, 15 Nov 2024 17:03:00 -0500
Subject: [PATCH 07/22] rename namespaces
---
Apps/Sandcastle/gallery/iTwin Demo.html | 8 ++--
.../Core/{ITwin.js => ITwinPlatform.js} | 48 +++++++++----------
...{createIModel3DTileset.js => ITwinData.js} | 26 +++++-----
3 files changed, 41 insertions(+), 41 deletions(-)
rename packages/engine/Source/Core/{ITwin.js => ITwinPlatform.js} (86%)
rename packages/engine/Source/Scene/{createIModel3DTileset.js => ITwinData.js} (81%)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index a26bc50f5dbb..dc87286bcd24 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -33,7 +33,7 @@
const serviceResponse = await fetch("http://localhost:3000/service");
const { token } = await serviceResponse.json();
- Cesium.ITwin.defaultAccessToken = token;
+ Cesium.ITwinPlatform.defaultAccessToken = token;
// this is the iModel in the "Hello iTwinCesium" iTwin that we should all have access to
// https://developer.bentley.com/my-itwins/b4a30036-0456-49ea-a439-3fcd9365e24e/home/
// const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
@@ -123,7 +123,7 @@
statusOutput.innerText = "Creating Tileset";
- let tileset = await Cesium.createIModel3DTileset.fromModelId(
+ let tileset = await Cesium.ITwinData.createTilesetFromModelId(
imodelId,
changesetId,
);
@@ -131,12 +131,12 @@
// TODO: this is temporary, we should not have to call the Start Export route ever after
// auto generation is set up
statusOutput.innerText = "Starting export";
- const exportId = await Cesium.ITwin.createExportForModelId(
+ const exportId = await Cesium.ITwinPlatform.createExportForModelId(
imodelId,
changesetId,
);
statusOutput.innerText = "Creating Tileset from export";
- tileset = await Cesium.createIModel3DTileset.fromExportId(exportId);
+ tileset = await Cesium.ITwinData.createTilesetFromExportId(exportId);
}
scene.primitives.add(tileset);
diff --git a/packages/engine/Source/Core/ITwin.js b/packages/engine/Source/Core/ITwinPlatform.js
similarity index 86%
rename from packages/engine/Source/Core/ITwin.js
rename to packages/engine/Source/Core/ITwinPlatform.js
index 249acd76ef88..0131e8efd5ee 100644
--- a/packages/engine/Source/Core/ITwin.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -26,7 +26,7 @@ import RuntimeError from "./RuntimeError.js";
* @typedef {Object} StartExport
* @property {string} iModelId
* @property {string} changesetId
- * @property {ITwin.ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
+ * @property {ITwinPlatform.ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
* @property {GeometryOptions} geometryOptions
* @property {ViewDefinitionFilter} viewDefinitionFilter
*/
@@ -40,7 +40,7 @@ import RuntimeError from "./RuntimeError.js";
* @typedef {Object} Export
* @property {string} id
* @property {string} displayName
- * @property {ITwin.ExportStatus} status
+ * @property {ITwinPlatform.ExportStatus} status
* @property {StartExport} request
* @property {{mesh: Link}} _links
*/
@@ -55,15 +55,15 @@ import RuntimeError from "./RuntimeError.js";
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
- * @see createIModel3DTileset
- * @namespace ITwin
+ * @see ITwinData for ways to import data
+ * @namespace ITwinPlatform
*/
-const ITwin = {};
+const ITwinPlatform = {};
/**
* @enum {string}
*/
-ITwin.ExportStatus = Object.freeze({
+ITwinPlatform.ExportStatus = Object.freeze({
NotStarted: "NotStarted",
InProgress: "InProgress",
Complete: "Complete",
@@ -73,7 +73,7 @@ ITwin.ExportStatus = Object.freeze({
/**
* @enum {string}
*/
-ITwin.ExportType = Object.freeze({
+ITwinPlatform.ExportType = Object.freeze({
IMODEL: "IMODEL",
CESIUM: "CESIUM",
"3DTILES": "3DTILES",
@@ -94,7 +94,7 @@ ITwin.ExportType = Object.freeze({
*
* @type {string|undefined}
*/
-ITwin.defaultAccessToken = undefined;
+ITwinPlatform.defaultAccessToken = undefined;
/**
* Gets or sets the default iTwin API endpoint.
@@ -104,7 +104,7 @@ ITwin.defaultAccessToken = undefined;
* @type {string|Resource}
* @default https://api.bentley.com
*/
-ITwin.apiEndpoint = new Resource({
+ITwinPlatform.apiEndpoint = new Resource({
url: "https://api.bentley.com",
});
@@ -120,21 +120,21 @@ ITwin.apiEndpoint = new Resource({
* @throws {RuntimeError} Too many requests
* @throws {RuntimeError} Unknown request failure
*/
-ITwin.getExport = async function (exportId) {
+ITwinPlatform.getExport = async function (exportId) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("exportId", exportId);
- if (!defined(ITwin.defaultAccessToken)) {
+ if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
//>>includeEnd('debug')
const headers = {
- Authorization: `Bearer ${ITwin.defaultAccessToken}`,
+ Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Accept: "application/vnd.bentley.itwin-platform.v1+json",
};
// obtain export for specified export id
- const url = `${ITwin.apiEndpoint}mesh-export/${exportId}`;
+ const url = `${ITwinPlatform.apiEndpoint}mesh-export/${exportId}`;
// TODO: this request is _really_ slow, like 7 whole second alone for me
// Arun said this was kinda normal but to keep track of the `x-correlation-id` of any that take EXTRA long
@@ -172,31 +172,31 @@ ITwin.getExport = async function (exportId) {
* @throws {RuntimeError} Too many requests
* @throws {RuntimeError} Unknown request failure
*/
-ITwin.getExports = async function (iModelId, changesetId) {
+ITwinPlatform.getExports = async function (iModelId, changesetId) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iModelId", iModelId);
if (defined(changesetId)) {
Check.typeOf.string("changesetId", changesetId);
}
- if (!defined(ITwin.defaultAccessToken)) {
+ if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
//>>includeEnd('debug')
const headers = {
- Authorization: `Bearer ${ITwin.defaultAccessToken}`,
+ Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Accept: "application/vnd.bentley.itwin-platform.v1+json",
Prefer: "return=representation", // or return=minimal (the default)
};
// obtain export for specified export id
// TODO: if we do include the clientVersion what should it be set to? can we sync it with the package.json?
- const url = new URL(`${ITwin.apiEndpoint}mesh-export`);
+ const url = new URL(`${ITwinPlatform.apiEndpoint}mesh-export`);
url.searchParams.set("iModelId", iModelId);
if (defined(changesetId) && changesetId !== "") {
url.searchParams.set("changesetId", changesetId);
}
- url.searchParams.set("exportType", ITwin.ExportType["3DTILES"]);
+ url.searchParams.set("exportType", ITwinPlatform.ExportType["3DTILES"]);
url.searchParams.set("$top", "1");
url.searchParams.set("client", "CesiumJS");
/* global CESIUM_VERSION */
@@ -247,13 +247,13 @@ ITwin.getExports = async function (iModelId, changesetId) {
* @throws {RuntimeError} Too many requests
* @throws {RuntimeError} Unknown request failure
*/
-ITwin.createExportForModelId = async function (iModelId, changesetId) {
+ITwinPlatform.createExportForModelId = async function (iModelId, changesetId) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iModelId", iModelId);
if (defined(changesetId)) {
Check.typeOf.string("changesetId", changesetId);
}
- if (!defined(ITwin.defaultAccessToken)) {
+ if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
//>>includeEnd('debug')
@@ -263,20 +263,20 @@ ITwin.createExportForModelId = async function (iModelId, changesetId) {
const requestOptions = {
method: "POST",
headers: {
- Authorization: `Bearer ${ITwin.defaultAccessToken}`,
+ Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
Accept: "application/vnd.bentley.itwin-platform.v1+json",
"Content-Type": "application/json",
},
body: JSON.stringify({
iModelId,
changesetId,
- exportType: ITwin.ExportType["3DTILES"],
+ exportType: ITwinPlatform.ExportType["3DTILES"],
}),
};
// initiate mesh export
const response = await fetch(
- `${ITwin.apiEndpoint}mesh-export/`,
+ `${ITwinPlatform.apiEndpoint}mesh-export/`,
requestOptions,
);
@@ -310,4 +310,4 @@ ITwin.createExportForModelId = async function (iModelId, changesetId) {
return result.export.id;
};
-export default ITwin;
+export default ITwinPlatform;
diff --git a/packages/engine/Source/Scene/createIModel3DTileset.js b/packages/engine/Source/Scene/ITwinData.js
similarity index 81%
rename from packages/engine/Source/Scene/createIModel3DTileset.js
rename to packages/engine/Source/Scene/ITwinData.js
index f4ebb904318b..e64307798e99 100644
--- a/packages/engine/Source/Scene/createIModel3DTileset.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -1,7 +1,7 @@
import Cesium3DTileset from "./Cesium3DTileset.js";
import defined from "../Core/defined.js";
import Resource from "../Core/Resource.js";
-import ITwin from "../Core/ITwin.js";
+import ITwinPlatform from "../Core/ITwinPlatform.js";
import RuntimeError from "../Core/RuntimeError.js";
import Check from "../Core/Check.js";
@@ -20,16 +20,16 @@ async function loadExport(exportObj, options) {
let status = exportObj.status;
- if (exportObj.request.exportType !== ITwin.ExportType["3DTILES"]) {
+ if (exportObj.request.exportType !== ITwinPlatform.ExportType["3DTILES"]) {
throw new RuntimeError(`Wrong export type ${exportObj.request.exportType}`);
}
const timeoutAfter = 300000;
const start = Date.now();
// wait until the export is complete
- while (status !== ITwin.ExportStatus.Complete) {
+ while (status !== ITwinPlatform.ExportStatus.Complete) {
await delay(5000);
- exportObj = (await ITwin.getExport(exportObj.id)).export;
+ exportObj = (await ITwinPlatform.getExport(exportObj.id)).export;
status = exportObj.status;
console.log(`Export is ${status}`);
@@ -51,7 +51,7 @@ async function loadExport(exportObj, options) {
return Cesium3DTileset.fromUrl(resource, options);
}
-const createIModel3DTileset = {};
+const ITwinData = {};
/**
* Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D Tiles tileset.
@@ -69,10 +69,10 @@ const createIModel3DTileset = {};
* @example
* TODO: example after API finalized
*/
-createIModel3DTileset.fromExportId = async function (exportId, options) {
+ITwinData.createTilesetFromExportId = async function (exportId, options) {
options = options ?? {};
- const result = await ITwin.getExport(exportId);
+ const result = await ITwinPlatform.getExport(exportId);
const tileset = await loadExport(result.export, options);
return tileset;
};
@@ -81,10 +81,10 @@ createIModel3DTileset.fromExportId = async function (exportId, options) {
* Check the exports for the given iModel + changeset combination for any that
* have the desired CESIUM type and returns the first one that matches as a new tileset.
*
- * If there is not a CESIUM export you can create it using {@link ITwin.createExportForModelId}
+ * If there is not a CESIUM export you can create it using {@link ITwinPlatform.createExportForModelId}
*
* This function assumes one export per type per "iModel id + changeset id". If you need to create
- * multiple exports per "iModel id + changeset id" you should switch to using {@link createIModel3DTileset}
+ * multiple exports per "iModel id + changeset id" you should switch to using {@link ITwinData}
* with the export id directly
*
* @example
@@ -99,15 +99,15 @@ createIModel3DTileset.fromExportId = async function (exportId, options) {
* @throws {RuntimeError} Wrong export type
* @throws {RuntimeError} Export did not complete in time.
*/
-createIModel3DTileset.fromModelId = async function (
+ITwinData.createTilesetFromModelId = async function (
iModelId,
changesetId,
options,
) {
- const { exports } = await ITwin.getExports(iModelId, changesetId);
+ const { exports } = await ITwinPlatform.getExports(iModelId, changesetId);
const cesiumExport = exports.find(
(exportObj) =>
- exportObj.request?.exportType === ITwin.ExportType["3DTILES"],
+ exportObj.request?.exportType === ITwinPlatform.ExportType["3DTILES"],
);
if (!defined(cesiumExport)) {
return;
@@ -115,4 +115,4 @@ createIModel3DTileset.fromModelId = async function (
return loadExport(cesiumExport, options);
};
-export default createIModel3DTileset;
+export default ITwinData;
From 1757f77a0878d695d80f4f4c2d24172f994adf55 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Tue, 19 Nov 2024 16:32:03 -0500
Subject: [PATCH 08/22] partial cleanup, remove changeset ids
---
Apps/Sandcastle/gallery/iTwin Demo.html | 11 +----
packages/engine/Source/Core/ITwinPlatform.js | 50 ++++----------------
packages/engine/Source/Scene/ITwinData.js | 47 ++++++++----------
3 files changed, 33 insertions(+), 75 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index dc87286bcd24..27047ba7d14d 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -38,7 +38,6 @@
// https://developer.bentley.com/my-itwins/b4a30036-0456-49ea-a439-3fcd9365e24e/home/
// const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
const imodelId = "88673c1d-12b8-48f1-8beb-5000d0edbd0b";
- const changesetId = "";
// Grabbed mapping from the iTwin Viewer
const classes = {
@@ -123,18 +122,12 @@
statusOutput.innerText = "Creating Tileset";
- let tileset = await Cesium.ITwinData.createTilesetFromModelId(
- imodelId,
- changesetId,
- );
+ let tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
if (!Cesium.defined(tileset)) {
// TODO: this is temporary, we should not have to call the Start Export route ever after
// auto generation is set up
statusOutput.innerText = "Starting export";
- const exportId = await Cesium.ITwinPlatform.createExportForModelId(
- imodelId,
- changesetId,
- );
+ const exportId = await Cesium.ITwinPlatform.createExportForModelId(imodelId);
statusOutput.innerText = "Creating Tileset from export";
tileset = await Cesium.ITwinData.createTilesetFromExportId(exportId);
}
diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js
index 0131e8efd5ee..c968ed48dd29 100644
--- a/packages/engine/Source/Core/ITwinPlatform.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -61,6 +61,7 @@ import RuntimeError from "./RuntimeError.js";
const ITwinPlatform = {};
/**
+ * Status states for a mesh-export export
* @enum {string}
*/
ITwinPlatform.ExportStatus = Object.freeze({
@@ -71,6 +72,7 @@ ITwinPlatform.ExportStatus = Object.freeze({
});
/**
+ * Types of mesh-export exports. CesiumJS only supports loading 3DTILES
type exports
* @enum {string}
*/
ITwinPlatform.ExportType = Object.freeze({
@@ -80,15 +82,7 @@ ITwinPlatform.ExportType = Object.freeze({
});
/**
- * Gets or sets the default iTwin access token.
- *
- * TODO: I'm not sure we can even do this kind of access token. Each route seems to need it's own scopes
- * and we may not be able to guarantee this "top level token" has them all
- * So far we use
- * `mesh-export:read` for loading meshes GET /mesh-export(s)
- * `mesh-export:modify` if we want to include a function to create an export
- * `itwin-platform` if we want to use the iModel shares ourselves GET /imodels/{id}/shares
- * Seems the `itwin-platform` scope should apply to everything but the docs are a little unclear
+ * Gets or sets the default iTwin access token. This token should have the itwin-platform
scope.
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
@@ -119,6 +113,7 @@ ITwinPlatform.apiEndpoint = new Resource({
* @throws {RuntimeError} Requested export is not available
* @throws {RuntimeError} Too many requests
* @throws {RuntimeError} Unknown request failure
+ * TODO: remove? this is used when we're looping to wait for jobs to finish
*/
ITwinPlatform.getExport = async function (exportId) {
//>>includeStart('debug', pragmas.debug);
@@ -159,25 +154,22 @@ ITwinPlatform.getExport = async function (exportId) {
};
/**
- * Get the list of exports for the given iModel + changeset
+ * Get the list of exports for the specified iModel at it's most current version. This will only return exports with {@link ITwinPlatform.ExportType} of 3DTILES
.
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
- * @param {string} iModelId
- * @param {string} [changesetId]
+ * @param {string} iModelId iModel id
*
* @throws {RuntimeError} Unauthorized, bad token, wrong scopes or headers bad.
* @throws {RuntimeError} Not allowed, forbidden
* @throws {RuntimeError} Unprocessable Entity
* @throws {RuntimeError} Too many requests
* @throws {RuntimeError} Unknown request failure
+ * @returns {Promise<{exports: Export[]}>}
*/
-ITwinPlatform.getExports = async function (iModelId, changesetId) {
+ITwinPlatform.getExports = async function (iModelId) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iModelId", iModelId);
- if (defined(changesetId)) {
- Check.typeOf.string("changesetId", changesetId);
- }
if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
@@ -190,12 +182,8 @@ ITwinPlatform.getExports = async function (iModelId, changesetId) {
};
// obtain export for specified export id
- // TODO: if we do include the clientVersion what should it be set to? can we sync it with the package.json?
const url = new URL(`${ITwinPlatform.apiEndpoint}mesh-export`);
url.searchParams.set("iModelId", iModelId);
- if (defined(changesetId) && changesetId !== "") {
- url.searchParams.set("changesetId", changesetId);
- }
url.searchParams.set("exportType", ITwinPlatform.ExportType["3DTILES"]);
url.searchParams.set("$top", "1");
url.searchParams.set("client", "CesiumJS");
@@ -230,36 +218,19 @@ ITwinPlatform.getExports = async function (iModelId, changesetId) {
};
/**
- * Start the export process for the given iModel + changeset.
- *
* TODO: REMOVE THIS FUNCTION! Auto generation of exports for the 3DTILES type is planned very soon
* and will be the desired way of interacting with iModels through exports. This function is here
* just while we continue testing during the PR process.
- *
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
- * @param {string} iModelId
- * @param {string} [changesetId]
- *
- * @throws {RuntimeError} Unauthorized, bad token, wrong scopes or headers bad.
- * @throws {RuntimeError} Not allowed, forbidden
- * @throws {RuntimeError} Unprocessable: Cannot create export job
- * @throws {RuntimeError} Too many requests
- * @throws {RuntimeError} Unknown request failure
+ * @deprecated
*/
-ITwinPlatform.createExportForModelId = async function (iModelId, changesetId) {
+ITwinPlatform.createExportForModelId = async function (iModelId) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.string("iModelId", iModelId);
- if (defined(changesetId)) {
- Check.typeOf.string("changesetId", changesetId);
- }
if (!defined(ITwinPlatform.defaultAccessToken)) {
throw new DeveloperError("Must set ITwin.defaultAccessToken first");
}
//>>includeEnd('debug')
- changesetId = changesetId ?? "";
-
const requestOptions = {
method: "POST",
headers: {
@@ -269,7 +240,6 @@ ITwinPlatform.createExportForModelId = async function (iModelId, changesetId) {
},
body: JSON.stringify({
iModelId,
- changesetId,
exportType: ITwinPlatform.ExportType["3DTILES"],
}),
};
diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js
index e64307798e99..f822b4d9bc97 100644
--- a/packages/engine/Source/Scene/ITwinData.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -11,7 +11,8 @@ function delay(ms) {
/**
* @param {Export} exportObj
- * @param {Cesium3DTileset.ConstructorOptions} [options]
+ * @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
+ * @returns {Promise}
*/
async function loadExport(exportObj, options) {
//>>includeStart('debug', pragmas.debug);
@@ -51,16 +52,24 @@ async function loadExport(exportObj, options) {
return Cesium3DTileset.fromUrl(resource, options);
}
+/**
+ * Methods for loading iTwin platform data into CesiumJS
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @see ITwinPlatform to set the API token and access api functions
+ * @namespace ITwinData
+ */
const ITwinData = {};
/**
- * Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D Tiles tileset.
+ * Creates a {@link Cesium3DTileset} instance for the given export id.
*
* @function
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
* @param {string} exportId
- * @param {Cesium3DTileset.ConstructorOptions} [options] An object describing initialization options.
+ * @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
* @returns {Promise}
*
* @throws {RuntimeError} Wrong export type
@@ -68,47 +77,33 @@ const ITwinData = {};
*
* @example
* TODO: example after API finalized
+ * @deprecated
*/
ITwinData.createTilesetFromExportId = async function (exportId, options) {
- options = options ?? {};
-
const result = await ITwinPlatform.getExport(exportId);
const tileset = await loadExport(result.export, options);
return tileset;
};
/**
- * Check the exports for the given iModel + changeset combination for any that
- * have the desired CESIUM type and returns the first one that matches as a new tileset.
- *
- * If there is not a CESIUM export you can create it using {@link ITwinPlatform.createExportForModelId}
- *
- * This function assumes one export per type per "iModel id + changeset id". If you need to create
- * multiple exports per "iModel id + changeset id" you should switch to using {@link ITwinData}
- * with the export id directly
+ * Loads the export for the specified iModel with the export type that CesiumJS can load and returns
+ * a tileset created from that export.
*
* @example
* TODO: example after API finalized
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
- * @param {string} iModelId
- * @param {string} changesetId
- * @param {Cesium3DTileset.ConstructorOptions} options
+ * @param {string} iModelId The id of the iModel to load
+ * @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
+ * @returns {Promise} Will return undefined
if there is no export for the given iModel id
*
* @throws {RuntimeError} Wrong export type
* @throws {RuntimeError} Export did not complete in time.
*/
-ITwinData.createTilesetFromModelId = async function (
- iModelId,
- changesetId,
- options,
-) {
- const { exports } = await ITwinPlatform.getExports(iModelId, changesetId);
- const cesiumExport = exports.find(
- (exportObj) =>
- exportObj.request?.exportType === ITwinPlatform.ExportType["3DTILES"],
- );
+ITwinData.createTilesetFromModelId = async function (iModelId, options) {
+ const { exports } = await ITwinPlatform.getExports(iModelId);
+ const cesiumExport = exports[0];
if (!defined(cesiumExport)) {
return;
}
From b8c1ac54bb7914451ae43806bc544114ef190197 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Tue, 19 Nov 2024 16:40:03 -0500
Subject: [PATCH 09/22] remove get export and creation routes
---
Apps/Sandcastle/gallery/iTwin Demo.html | 10 +-
packages/engine/Source/Core/ITwinPlatform.js | 115 +------------------
packages/engine/Source/Scene/ITwinData.js | 53 ++-------
3 files changed, 11 insertions(+), 167 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index 27047ba7d14d..de045f350659 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -122,15 +122,7 @@
statusOutput.innerText = "Creating Tileset";
- let tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
- if (!Cesium.defined(tileset)) {
- // TODO: this is temporary, we should not have to call the Start Export route ever after
- // auto generation is set up
- statusOutput.innerText = "Starting export";
- const exportId = await Cesium.ITwinPlatform.createExportForModelId(imodelId);
- statusOutput.innerText = "Creating Tileset from export";
- tileset = await Cesium.ITwinData.createTilesetFromExportId(exportId);
- }
+ const tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
scene.primitives.add(tileset);
tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE;
diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js
index c968ed48dd29..134c5a553f83 100644
--- a/packages/engine/Source/Core/ITwinPlatform.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -102,61 +102,11 @@ ITwinPlatform.apiEndpoint = new Resource({
url: "https://api.bentley.com",
});
-/**
- * Get the export object for the specified export id
- *
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
- * @param {string} exportId
- *
- * @throws {RuntimeError} Unauthorized, bad token, wrong scopes or headers bad.
- * @throws {RuntimeError} Requested export is not available
- * @throws {RuntimeError} Too many requests
- * @throws {RuntimeError} Unknown request failure
- * TODO: remove? this is used when we're looping to wait for jobs to finish
- */
-ITwinPlatform.getExport = async function (exportId) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("exportId", exportId);
- if (!defined(ITwinPlatform.defaultAccessToken)) {
- throw new DeveloperError("Must set ITwin.defaultAccessToken first");
- }
- //>>includeEnd('debug')
-
- const headers = {
- Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- };
-
- // obtain export for specified export id
- const url = `${ITwinPlatform.apiEndpoint}mesh-export/${exportId}`;
-
- // TODO: this request is _really_ slow, like 7 whole second alone for me
- // Arun said this was kinda normal but to keep track of the `x-correlation-id` of any that take EXTRA long
- const response = await fetch(url, { headers });
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
- throw new RuntimeError(
- `Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
- );
- } else if (response.status === 404) {
- throw new RuntimeError(`Requested export is not available ${exportId}`);
- } else if (response.status === 429) {
- throw new RuntimeError("Too many requests");
- }
- throw new RuntimeError(`Unknown request failure ${response.status}`);
- }
-
- /** @type {ExportResponse} */
- const result = await response.json();
- return result;
-};
-
/**
* Get the list of exports for the specified iModel at it's most current version. This will only return exports with {@link ITwinPlatform.ExportType} of 3DTILES
.
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ * @private
*
* @param {string} iModelId iModel id
*
@@ -217,67 +167,4 @@ ITwinPlatform.getExports = async function (iModelId) {
return result;
};
-/**
- * TODO: REMOVE THIS FUNCTION! Auto generation of exports for the 3DTILES type is planned very soon
- * and will be the desired way of interacting with iModels through exports. This function is here
- * just while we continue testing during the PR process.
- * @deprecated
- */
-ITwinPlatform.createExportForModelId = async function (iModelId) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.string("iModelId", iModelId);
- if (!defined(ITwinPlatform.defaultAccessToken)) {
- throw new DeveloperError("Must set ITwin.defaultAccessToken first");
- }
- //>>includeEnd('debug')
-
- const requestOptions = {
- method: "POST",
- headers: {
- Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- iModelId,
- exportType: ITwinPlatform.ExportType["3DTILES"],
- }),
- };
-
- // initiate mesh export
- const response = await fetch(
- `${ITwinPlatform.apiEndpoint}mesh-export/`,
- requestOptions,
- );
-
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
- console.error(
- result.error.code,
- result.error.message,
- result.error.details,
- );
- throw new RuntimeError(
- "Unauthorized, bad token, wrong scopes or headers bad",
- );
- } else if (response.status === 403) {
- console.error(result.error.code, result.error.message);
- throw new RuntimeError("Not allowed, forbidden");
- } else if (response.status === 422) {
- console.error(result.error.code, result.error.message);
- console.error(result.error.details);
- throw new RuntimeError("Unprocessable: Cannot create export job");
- } else if (response.status === 429) {
- throw new RuntimeError("Too many requests");
- }
-
- throw new RuntimeError(`Unknown request failure ${response.status}`);
- }
-
- /** @type {ExportResponse} */
- const result = await response.json();
- return result.export.id;
-};
-
export default ITwinPlatform;
diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js
index f822b4d9bc97..ef5769431748 100644
--- a/packages/engine/Source/Scene/ITwinData.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -5,10 +5,6 @@ import ITwinPlatform from "../Core/ITwinPlatform.js";
import RuntimeError from "../Core/RuntimeError.js";
import Check from "../Core/Check.js";
-function delay(ms) {
- return new Promise((res) => setTimeout(res, ms));
-}
-
/**
* @param {Export} exportObj
* @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
@@ -19,24 +15,13 @@ async function loadExport(exportObj, options) {
Check.defined("exportObj", exportObj);
//>>includeEnd('debug')
- let status = exportObj.status;
-
if (exportObj.request.exportType !== ITwinPlatform.ExportType["3DTILES"]) {
throw new RuntimeError(`Wrong export type ${exportObj.request.exportType}`);
}
-
- const timeoutAfter = 300000;
- const start = Date.now();
- // wait until the export is complete
- while (status !== ITwinPlatform.ExportStatus.Complete) {
- await delay(5000);
- exportObj = (await ITwinPlatform.getExport(exportObj.id)).export;
- status = exportObj.status;
- console.log(`Export is ${status}`);
-
- if (Date.now() - start > timeoutAfter) {
- throw new RuntimeError("Export did not complete in time.");
- }
+ if (exportObj.status !== ITwinPlatform.ExportStatus.Complete) {
+ throw new RuntimeError(
+ `Export is not completed. ${exportObj.id} - ${exportObj.status}`,
+ );
}
// Convert the link to the tileset url while preserving the search paramaters
@@ -62,33 +47,13 @@ async function loadExport(exportObj, options) {
*/
const ITwinData = {};
-/**
- * Creates a {@link Cesium3DTileset} instance for the given export id.
- *
- * @function
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
- * @param {string} exportId
- * @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
- * @returns {Promise}
- *
- * @throws {RuntimeError} Wrong export type
- * @throws {RuntimeError} Export did not complete in time.
- *
- * @example
- * TODO: example after API finalized
- * @deprecated
- */
-ITwinData.createTilesetFromExportId = async function (exportId, options) {
- const result = await ITwinPlatform.getExport(exportId);
- const tileset = await loadExport(result.export, options);
- return tileset;
-};
-
/**
* Loads the export for the specified iModel with the export type that CesiumJS can load and returns
* a tileset created from that export.
*
+ * If the export is not finished processing this will throw an error. It is up to the caller
+ * to re-attempt loading at a later time
+ *
* @example
* TODO: example after API finalized
*
@@ -98,8 +63,8 @@ ITwinData.createTilesetFromExportId = async function (exportId, options) {
* @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
* @returns {Promise} Will return undefined
if there is no export for the given iModel id
*
- * @throws {RuntimeError} Wrong export type
- * @throws {RuntimeError} Export did not complete in time.
+ * @throws {RuntimeError} Wrong export type [type]
+ * @throws {RuntimeError} Export is not completed. [id] - [status]
*/
ITwinData.createTilesetFromModelId = async function (iModelId, options) {
const { exports } = await ITwinPlatform.getExports(iModelId);
From 958756eea714c26998405fb6956532e2b4a86170 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Tue, 19 Nov 2024 16:53:29 -0500
Subject: [PATCH 10/22] update sandcastle for exports still processing
---
Apps/Sandcastle/gallery/iTwin Demo.html | 27 ++++++++++++++++++--
packages/engine/Source/Core/ITwinPlatform.js | 2 ++
packages/engine/Source/Scene/ITwinData.js | 3 ++-
3 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index de045f350659..7bee11033cad 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -119,10 +119,33 @@
statusOutput.innerText = "Starting export";
const start = Date.now();
+ const delay = (ms) => new Promise((res) => setTimeout(res, ms));
statusOutput.innerText = "Creating Tileset";
-
- const tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
+ let tileset;
+ try {
+ tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
+ } catch (error) {
+ if (!error.message.includes("not completed")) {
+ throw error;
+ }
+ statusOutput.innerText = "Tileset not completed, retrying...";
+
+ const timeoutAfter = 300000;
+ // wait until the export is complete
+ while (Date.now() - start > timeoutAfter) {
+ await delay(5000);
+ try {
+ tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
+ break;
+ } catch (error) {
+ if (!error.message.includes("not completed")) {
+ throw error;
+ }
+ }
+ }
+ throw new Cesium.RuntimeError("Export did not complete in time.");
+ }
scene.primitives.add(tileset);
tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE;
diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js
index 134c5a553f83..defe07107bbb 100644
--- a/packages/engine/Source/Core/ITwinPlatform.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -135,6 +135,8 @@ ITwinPlatform.getExports = async function (iModelId) {
const url = new URL(`${ITwinPlatform.apiEndpoint}mesh-export`);
url.searchParams.set("iModelId", iModelId);
url.searchParams.set("exportType", ITwinPlatform.ExportType["3DTILES"]);
+ // TODO: If we're only requesting the top 1 is there a chance it's `Invalid` instead of `Complete`
+ // and never possible to load it?
url.searchParams.set("$top", "1");
url.searchParams.set("client", "CesiumJS");
/* global CESIUM_VERSION */
diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js
index ef5769431748..bfe560ec8440 100644
--- a/packages/engine/Source/Scene/ITwinData.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -55,7 +55,8 @@ const ITwinData = {};
* to re-attempt loading at a later time
*
* @example
- * TODO: example after API finalized
+ * const tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
+ * viewer.scene.primitives.add(tileset);
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
From 7049dbefa28b93f1898192d29eaefc9c0806ee84 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Wed, 20 Nov 2024 13:28:31 -0500
Subject: [PATCH 11/22] re-organize, resource instead of fetch, clean up types
---
Apps/Sandcastle/gallery/iTwin Demo.html | 2 +-
packages/engine/Source/Core/ITwinPlatform.js | 133 ++++++++-----------
packages/engine/Source/Scene/ITwinData.js | 22 +--
3 files changed, 71 insertions(+), 86 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index 7bee11033cad..17e54c4f2294 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -134,7 +134,7 @@
const timeoutAfter = 300000;
// wait until the export is complete
while (Date.now() - start > timeoutAfter) {
- await delay(5000);
+ await delay(15000);
try {
tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
break;
diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js
index defe07107bbb..734d2b150749 100644
--- a/packages/engine/Source/Core/ITwinPlatform.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -4,52 +4,6 @@ import DeveloperError from "./DeveloperError.js";
import Resource from "./Resource.js";
import RuntimeError from "./RuntimeError.js";
-/**
- * @typedef {Object} GeometryOptions
- * @property {boolean} includeLines
- * @property {number} chordTol
- * @property {number} angleTol
- * @property {number} decimationTol
- * @property {number} maxEdgeLength
- * @property {number} minBRepFeatureSize
- * @property {number} minLineStyleComponentSize
- */
-
-/**
- * @typedef {Object} ViewDefinitionFilter
- * @property {string[]} models Array of included model IDs.
- * @property {string[]} categories Array of included category IDs.
- * @property {string[]} neverDrawn Array of element IDs to filter out.
- */
-
-/**
- * @typedef {Object} StartExport
- * @property {string} iModelId
- * @property {string} changesetId
- * @property {ITwinPlatform.ExportType} exportType Type of mesh to create. Currently, only GLTF and 3DFT are supported and undocumented CESIUM option
- * @property {GeometryOptions} geometryOptions
- * @property {ViewDefinitionFilter} viewDefinitionFilter
- */
-
-/**
- * @typedef {Object} Link
- * @property {string} href
- */
-
-/**
- * @typedef {Object} Export
- * @property {string} id
- * @property {string} displayName
- * @property {ITwinPlatform.ExportStatus} status
- * @property {StartExport} request
- * @property {{mesh: Link}} _links
- */
-
-/**
- * @typedef {Object} ExportResponse
- * @property {Export} export
- */
-
/**
* Default settings for accessing the iTwin platform.
*
@@ -63,6 +17,7 @@ const ITwinPlatform = {};
/**
* Status states for a mesh-export export
* @enum {string}
+ * @private
*/
ITwinPlatform.ExportStatus = Object.freeze({
NotStarted: "NotStarted",
@@ -74,6 +29,7 @@ ITwinPlatform.ExportStatus = Object.freeze({
/**
* Types of mesh-export exports. CesiumJS only supports loading 3DTILES
type exports
* @enum {string}
+ * @private
*/
ITwinPlatform.ExportType = Object.freeze({
IMODEL: "IMODEL",
@@ -102,6 +58,35 @@ ITwinPlatform.apiEndpoint = new Resource({
url: "https://api.bentley.com",
});
+/**
+ * @typedef {Object} ExportRequest
+ * @property {string} iModelId
+ * @property {string} changesetId
+ * @property {ITwinPlatform.ExportType} exportType Type of the export. CesiumJS only supports the 3DTILES type
+ */
+
+/**
+ * @typedef {Object} Link
+ * @property {string} href
+ */
+
+/**
+ * @typedef {Object} ExportRepresentation
+ * The export objects from get-exports when using return=representation
+ * @property {string} id Export id
+ * @property {string} displayName Name of the iModel
+ * @property {ITwinPlatform.ExportStatus} status Status of this export
+ * @property {string} lastModified
+ * @property {ExportRequest} request Object containing info about the export itself
+ * @property {{mesh: Link}} _links Object containing relevant links. For Exports this includes the access url for the mesh itself
+ */
+
+/**
+ * @typedef {Object} GetExportsResponse
+ * @property {ExportRepresentation[]} exports The list of exports for the current page
+ * @property {{self: Link, next: Link | undefined, prev: Link | undefined}} _links Pagination links
+ */
+
/**
* Get the list of exports for the specified iModel at it's most current version. This will only return exports with {@link ITwinPlatform.ExportType} of 3DTILES
.
*
@@ -115,7 +100,7 @@ ITwinPlatform.apiEndpoint = new Resource({
* @throws {RuntimeError} Unprocessable Entity
* @throws {RuntimeError} Too many requests
* @throws {RuntimeError} Unknown request failure
- * @returns {Promise<{exports: Export[]}>}
+ * @returns {Promise}
*/
ITwinPlatform.getExports = async function (iModelId) {
//>>includeStart('debug', pragmas.debug);
@@ -125,48 +110,48 @@ ITwinPlatform.getExports = async function (iModelId) {
}
//>>includeEnd('debug')
- const headers = {
- Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
- Accept: "application/vnd.bentley.itwin-platform.v1+json",
- Prefer: "return=representation", // or return=minimal (the default)
- };
-
- // obtain export for specified export id
- const url = new URL(`${ITwinPlatform.apiEndpoint}mesh-export`);
- url.searchParams.set("iModelId", iModelId);
- url.searchParams.set("exportType", ITwinPlatform.ExportType["3DTILES"]);
- // TODO: If we're only requesting the top 1 is there a chance it's `Invalid` instead of `Complete`
- // and never possible to load it?
- url.searchParams.set("$top", "1");
- url.searchParams.set("client", "CesiumJS");
+ const resource = new Resource({
+ url: `${ITwinPlatform.apiEndpoint}mesh-export`,
+ headers: {
+ Authorization: `Bearer ${ITwinPlatform.defaultAccessToken}`,
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ Prefer: "return=representation",
+ },
+ queryParameters: {
+ iModelId: iModelId,
+ exportType: ITwinPlatform.ExportType["3DTILES"],
+ // TODO: If we're only requesting the top 1 is there a chance it's `Invalid` instead of `Complete`
+ // and never possible to load it?
+ $top: "1",
+ client: "CesiumJS",
+ },
+ });
/* global CESIUM_VERSION */
if (typeof CESIUM_VERSION !== "undefined") {
- url.searchParams.set("clientVersion", CESIUM_VERSION);
+ resource.appendQueryParameters({ clientVersion: CESIUM_VERSION });
}
- const response = await fetch(url, { headers });
- if (!response.ok) {
- const result = await response.json();
- if (response.status === 401) {
+ try {
+ const response = await resource.fetchJson();
+ return response;
+ } catch (error) {
+ const result = JSON.parse(error.response);
+ if (error.statusCode === 401) {
throw new RuntimeError(
`Unauthorized, bad token, wrong scopes or headers bad. ${result.error.details[0].code}`,
);
- } else if (response.status === 403) {
+ } else if (error.statusCode === 403) {
console.error(result.error.code, result.error.message);
throw new RuntimeError("Not allowed, forbidden");
- } else if (response.status === 422) {
+ } else if (error.statusCode === 422) {
throw new RuntimeError(
`Unprocessable Entity:${result.error.code} ${result.error.message}`,
);
- } else if (response.status === 429) {
+ } else if (error.statusCode === 429) {
throw new RuntimeError("Too many requests");
}
- throw new RuntimeError(`Unknown request failure ${response.status}`);
+ throw new RuntimeError(`Unknown request failure ${error.statusCode}`);
}
-
- /** @type {{exports: Export[]}} */
- const result = await response.json();
- return result;
};
export default ITwinPlatform;
diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js
index bfe560ec8440..0659620bdb13 100644
--- a/packages/engine/Source/Scene/ITwinData.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -6,7 +6,17 @@ import RuntimeError from "../Core/RuntimeError.js";
import Check from "../Core/Check.js";
/**
- * @param {Export} exportObj
+ * Methods for loading iTwin platform data into CesiumJS
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @see ITwinPlatform to set the API token and base api url
+ * @namespace ITwinData
+ */
+const ITwinData = {};
+
+/**
+ * @param {ExportRepresentation} exportObj
* @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
* @returns {Promise}
*/
@@ -37,16 +47,6 @@ async function loadExport(exportObj, options) {
return Cesium3DTileset.fromUrl(resource, options);
}
-/**
- * Methods for loading iTwin platform data into CesiumJS
- *
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
- * @see ITwinPlatform to set the API token and access api functions
- * @namespace ITwinData
- */
-const ITwinData = {};
-
/**
* Loads the export for the specified iModel with the export type that CesiumJS can load and returns
* a tileset created from that export.
From 188e2bf3948fe95479f64fa6b5c6d287f01a4f18 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Wed, 20 Nov 2024 13:40:33 -0500
Subject: [PATCH 12/22] fix type generation
---
packages/engine/Source/Core/ITwinPlatform.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js
index 734d2b150749..cbb8499aac0a 100644
--- a/packages/engine/Source/Core/ITwinPlatform.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -17,7 +17,6 @@ const ITwinPlatform = {};
/**
* Status states for a mesh-export export
* @enum {string}
- * @private
*/
ITwinPlatform.ExportStatus = Object.freeze({
NotStarted: "NotStarted",
@@ -29,7 +28,6 @@ ITwinPlatform.ExportStatus = Object.freeze({
/**
* Types of mesh-export exports. CesiumJS only supports loading 3DTILES
type exports
* @enum {string}
- * @private
*/
ITwinPlatform.ExportType = Object.freeze({
IMODEL: "IMODEL",
From 3726464a6294ae1ddb2dcd486670f8b3aa10fbd0 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Wed, 20 Nov 2024 14:46:51 -0500
Subject: [PATCH 13/22] minor renaming and descriptions
---
Apps/Sandcastle/gallery/iTwin Demo.html | 38 +++++------------------
packages/engine/Source/Scene/ITwinData.js | 6 ++--
2 files changed, 10 insertions(+), 34 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index 17e54c4f2294..29a95fe0747a 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -9,10 +9,10 @@
/>
-
- Cesium Demo
+
+ iTwin iModel demo
@@ -34,10 +34,11 @@
const { token } = await serviceResponse.json();
Cesium.ITwinPlatform.defaultAccessToken = token;
+ // TODO: remove/clean up when we pick the final sample iTwin we want to use
// this is the iModel in the "Hello iTwinCesium" iTwin that we should all have access to
// https://developer.bentley.com/my-itwins/b4a30036-0456-49ea-a439-3fcd9365e24e/home/
- // const imodelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
- const imodelId = "88673c1d-12b8-48f1-8beb-5000d0edbd0b";
+ // const iModelId = "2852c3d7-00c3-4b5d-a0ce-82bbde4f061e";
+ const iModelId = "88673c1d-12b8-48f1-8beb-5000d0edbd0b";
// Grabbed mapping from the iTwin Viewer
const classes = {
@@ -117,36 +118,11 @@
const statusOutput = document.querySelector("#status");
async function init() {
statusOutput.innerText = "Starting export";
-
const start = Date.now();
- const delay = (ms) => new Promise((res) => setTimeout(res, ms));
statusOutput.innerText = "Creating Tileset";
- let tileset;
- try {
- tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
- } catch (error) {
- if (!error.message.includes("not completed")) {
- throw error;
- }
- statusOutput.innerText = "Tileset not completed, retrying...";
-
- const timeoutAfter = 300000;
- // wait until the export is complete
- while (Date.now() - start > timeoutAfter) {
- await delay(15000);
- try {
- tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
- break;
- } catch (error) {
- if (!error.message.includes("not completed")) {
- throw error;
- }
- }
- }
- throw new Cesium.RuntimeError("Export did not complete in time.");
- }
+ const tileset = await Cesium.ITwinData.createTilesetFromIModelId(iModelId);
scene.primitives.add(tileset);
tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.REPLACE;
diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js
index 0659620bdb13..70de32fe5e9f 100644
--- a/packages/engine/Source/Scene/ITwinData.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -10,7 +10,7 @@ import Check from "../Core/Check.js";
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*
- * @see ITwinPlatform to set the API token and base api url
+ * @see ITwinPlatform to set the API token and base API URL
* @namespace ITwinData
*/
const ITwinData = {};
@@ -55,7 +55,7 @@ async function loadExport(exportObj, options) {
* to re-attempt loading at a later time
*
* @example
- * const tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
+ * const tileset = await Cesium.ITwinData.createTilesetFromIModelId(iModelId);
* viewer.scene.primitives.add(tileset);
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
@@ -67,7 +67,7 @@ async function loadExport(exportObj, options) {
* @throws {RuntimeError} Wrong export type [type]
* @throws {RuntimeError} Export is not completed. [id] - [status]
*/
-ITwinData.createTilesetFromModelId = async function (iModelId, options) {
+ITwinData.createTilesetFromIModelId = async function (iModelId, options) {
const { exports } = await ITwinPlatform.getExports(iModelId);
const cesiumExport = exports[0];
if (!defined(cesiumExport)) {
From d2055b277c72665b001fd08003d03913d0bd5b75 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Fri, 22 Nov 2024 13:53:23 -0500
Subject: [PATCH 14/22] pull out dev auth server
---
.prettierignore | 1 -
itwin-oauth-demo/.gitignore | 1 -
itwin-oauth-demo/index.html | 34 -------------------
itwin-oauth-demo/server.js | 66 -------------------------------------
4 files changed, 102 deletions(-)
delete mode 100644 itwin-oauth-demo/.gitignore
delete mode 100644 itwin-oauth-demo/index.html
delete mode 100644 itwin-oauth-demo/server.js
diff --git a/.prettierignore b/.prettierignore
index e0d51a3709d0..c46eee96cb50 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -11,7 +11,6 @@
!packages/**/
!Specs/**/
!Tools/**/
-!itwin-oauth-demo/**/
!**/*.js
!**/*.cjs
diff --git a/itwin-oauth-demo/.gitignore b/itwin-oauth-demo/.gitignore
deleted file mode 100644
index d344ba6b06cb..000000000000
--- a/itwin-oauth-demo/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-config.json
diff --git a/itwin-oauth-demo/index.html b/itwin-oauth-demo/index.html
deleted file mode 100644
index af6bc98d6499..000000000000
--- a/itwin-oauth-demo/index.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
- Auth!
-
-
-
-
-
diff --git a/itwin-oauth-demo/server.js b/itwin-oauth-demo/server.js
deleted file mode 100644
index 8a935343622e..000000000000
--- a/itwin-oauth-demo/server.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import express from "express";
-import { readFileSync, writeFileSync } from "fs";
-import { dirname, join } from "path";
-import { exit } from "process";
-import { fileURLToPath } from "url";
-
-let config = {
- serviceapp: {
- clientId: "",
- clientSecret: "",
- },
- port: 3000,
-};
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-const configPath = join(__dirname, "./config.json");
-try {
- const configFile = readFileSync(configPath, { encoding: "utf-8" });
- config = JSON.parse(configFile);
-} catch {
- console.log("config file missing, default written to", configPath);
- console.log("Please update the config with the desired values");
- writeFileSync(configPath, JSON.stringify(config, undefined, 2));
- exit(1);
-}
-
-const app = express();
-const port = config.port ?? 3000;
-
-// eslint-disable-next-line no-unused-vars
-app.get("/service", async (req, res) => {
- console.log("/service request received");
-
- const body = new URLSearchParams();
- body.set("grant_type", "client_credentials");
- body.set("client_id", config.serviceapp.clientId);
- body.set("client_secret", config.serviceapp.clientSecret);
- body.set("scope", "itwin-platform");
-
- const response = await fetch("https://ims.bentley.com/connect/token", {
- method: "POST",
- body,
- });
-
- const result = await response.json();
-
- res.setHeader("Access-Control-Allow-Origin", "*");
-
- if (!response.ok || !result) {
- console.log(" bad response/no result");
- res.status(response.status).send();
- return;
- }
- const { access_token } = result;
- if (access_token) {
- console.log(" token acquired, returned");
- res.status(200).send({ token: access_token });
- return;
- }
- console.log(" token not found");
- res.status(404).send("token not found");
-});
-
-app.listen(port, () => {
- console.log(`Server listening on port ${port}`);
-});
From 47e1642eabffb26f301661f1ea1815a4f90bb38e Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Fri, 22 Nov 2024 15:17:31 -0500
Subject: [PATCH 15/22] condense code, update docs
---
CHANGES.md | 1 +
packages/engine/Source/Core/ITwinPlatform.js | 23 +++---
packages/engine/Source/Scene/ITwinData.js | 80 +++++++++-----------
3 files changed, 50 insertions(+), 54 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 49e2c0056ac6..f6b0782a351a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,6 +7,7 @@
##### Additions :tada:
- Added `Entity.trackingReferenceFrame` property to allow tracking entities in their own inertial reference frame. [#12194](https://github.com/CesiumGS/cesium/pull/12194)
+- Added a new integration with the iTwin Platform to easily load iModels directly in the viewer. Use `ITwinPlatform.defaultAccessToken` to set the access token then use `ITwinData.createTilesetFromIModelId(iModelId)` to load the iModel as a `Cesium3DTileset`. [#12289](https://github.com/CesiumGS/cesium/pull/12289)
##### Fixes :wrench:
diff --git a/packages/engine/Source/Core/ITwinPlatform.js b/packages/engine/Source/Core/ITwinPlatform.js
index cbb8499aac0a..a21a2f6e0276 100644
--- a/packages/engine/Source/Core/ITwinPlatform.js
+++ b/packages/engine/Source/Core/ITwinPlatform.js
@@ -58,6 +58,7 @@ ITwinPlatform.apiEndpoint = new Resource({
/**
* @typedef {Object} ExportRequest
+ * @private
* @property {string} iModelId
* @property {string} changesetId
* @property {ITwinPlatform.ExportType} exportType Type of the export. CesiumJS only supports the 3DTILES type
@@ -65,12 +66,14 @@ ITwinPlatform.apiEndpoint = new Resource({
/**
* @typedef {Object} Link
+ * @private
* @property {string} href
*/
/**
* @typedef {Object} ExportRepresentation
* The export objects from get-exports when using return=representation
+ * @private
* @property {string} id Export id
* @property {string} displayName Name of the iModel
* @property {ITwinPlatform.ExportStatus} status Status of this export
@@ -81,24 +84,21 @@ ITwinPlatform.apiEndpoint = new Resource({
/**
* @typedef {Object} GetExportsResponse
+ * @private
* @property {ExportRepresentation[]} exports The list of exports for the current page
* @property {{self: Link, next: Link | undefined, prev: Link | undefined}} _links Pagination links
*/
/**
- * Get the list of exports for the specified iModel at it's most current version. This will only return exports with {@link ITwinPlatform.ExportType} of 3DTILES
.
+ * Get the list of exports for the specified iModel at it's most current version.
+ * This will only return the top 5 exports with {@link ITwinPlatform.ExportType} of 3DTILES
.
*
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
* @private
*
* @param {string} iModelId iModel id
- *
- * @throws {RuntimeError} Unauthorized, bad token, wrong scopes or headers bad.
- * @throws {RuntimeError} Not allowed, forbidden
- * @throws {RuntimeError} Unprocessable Entity
- * @throws {RuntimeError} Too many requests
- * @throws {RuntimeError} Unknown request failure
* @returns {Promise}
+ *
+ * @throws {RuntimeError} If the iTwin API request is not successful
*/
ITwinPlatform.getExports = async function (iModelId) {
//>>includeStart('debug', pragmas.debug);
@@ -118,9 +118,10 @@ ITwinPlatform.getExports = async function (iModelId) {
queryParameters: {
iModelId: iModelId,
exportType: ITwinPlatform.ExportType["3DTILES"],
- // TODO: If we're only requesting the top 1 is there a chance it's `Invalid` instead of `Complete`
- // and never possible to load it?
- $top: "1",
+ // With the export auto-generation it will auto-delete the 6th export so
+ // there should never be more than 5 results. Just request them all and parse
+ // for ones that are COMPLETE
+ $top: "5",
client: "CesiumJS",
},
});
diff --git a/packages/engine/Source/Scene/ITwinData.js b/packages/engine/Source/Scene/ITwinData.js
index 70de32fe5e9f..219480c52ba2 100644
--- a/packages/engine/Source/Scene/ITwinData.js
+++ b/packages/engine/Source/Scene/ITwinData.js
@@ -3,7 +3,6 @@ import defined from "../Core/defined.js";
import Resource from "../Core/Resource.js";
import ITwinPlatform from "../Core/ITwinPlatform.js";
import RuntimeError from "../Core/RuntimeError.js";
-import Check from "../Core/Check.js";
/**
* Methods for loading iTwin platform data into CesiumJS
@@ -16,27 +15,51 @@ import Check from "../Core/Check.js";
const ITwinData = {};
/**
- * @param {ExportRepresentation} exportObj
- * @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
- * @returns {Promise}
+ * Create a {@link Cesium3DTileset} for the given iModel id using the mesh export service.
+ *
+ * If there is not a completed export available for the given iModel id this function will return undefined
.
+ * We recommend waiting 10-20 seconds and trying to load the tileset again.
+ * If all exports are Invalid this will throw an error. In that case there's likely something wrong with the iModel itself
+ *
+ * @example
+ * const tileset = await Cesium.ITwinData.createTilesetFromIModelId(iModelId);
+ * if (Cesium.defined(tileset)) {
+ * viewer.scene.primitives.add(tileset);
+ * }
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @param {string} iModelId The id of the iModel to load
+ * @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to the internally created {@link Cesium3DTileset}.
+ * @returns {Promise} Will return undefined
if there is no completed export for the given iModel id
+ *
+ * @throws {RuntimeError} If all exports for the given iModel are Invalid
+ * @throws {RuntimeError} If the iTwin API request is not successful
*/
-async function loadExport(exportObj, options) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("exportObj", exportObj);
- //>>includeEnd('debug')
+ITwinData.createTilesetFromIModelId = async function (iModelId, options) {
+ const { exports } = await ITwinPlatform.getExports(iModelId);
- if (exportObj.request.exportType !== ITwinPlatform.ExportType["3DTILES"]) {
- throw new RuntimeError(`Wrong export type ${exportObj.request.exportType}`);
- }
- if (exportObj.status !== ITwinPlatform.ExportStatus.Complete) {
+ if (
+ exports.every((exportObj) => {
+ return exportObj.status === ITwinPlatform.ExportStatus.Invalid;
+ })
+ ) {
throw new RuntimeError(
- `Export is not completed. ${exportObj.id} - ${exportObj.status}`,
+ `All exports for this iModel are Invalid: ${iModelId}`,
);
}
+ const completeExport = exports.find((exportObj) => {
+ return exportObj.status === ITwinPlatform.ExportStatus.Complete;
+ });
+
+ if (!defined(completeExport)) {
+ return;
+ }
+
// Convert the link to the tileset url while preserving the search paramaters
// This link is only valid 1 hour
- const baseUrl = new URL(exportObj._links.mesh.href);
+ const baseUrl = new URL(completeExport._links.mesh.href);
baseUrl.pathname = `${baseUrl.pathname}/tileset.json`;
const tilesetUrl = baseUrl.toString();
@@ -45,35 +68,6 @@ async function loadExport(exportObj, options) {
});
return Cesium3DTileset.fromUrl(resource, options);
-}
-
-/**
- * Loads the export for the specified iModel with the export type that CesiumJS can load and returns
- * a tileset created from that export.
- *
- * If the export is not finished processing this will throw an error. It is up to the caller
- * to re-attempt loading at a later time
- *
- * @example
- * const tileset = await Cesium.ITwinData.createTilesetFromIModelId(iModelId);
- * viewer.scene.primitives.add(tileset);
- *
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
- * @param {string} iModelId The id of the iModel to load
- * @param {Cesium3DTileset.ConstructorOptions} [options] Object containing options to pass to an internally created {@link Cesium3DTileset}.
- * @returns {Promise} Will return undefined
if there is no export for the given iModel id
- *
- * @throws {RuntimeError} Wrong export type [type]
- * @throws {RuntimeError} Export is not completed. [id] - [status]
- */
-ITwinData.createTilesetFromIModelId = async function (iModelId, options) {
- const { exports } = await ITwinPlatform.getExports(iModelId);
- const cesiumExport = exports[0];
- if (!defined(cesiumExport)) {
- return;
- }
- return loadExport(cesiumExport, options);
};
export default ITwinData;
From 990004b98526c55eacddf3ceb639680fb824ec70 Mon Sep 17 00:00:00 2001
From: jjspace <8007967+jjspace@users.noreply.github.com>
Date: Fri, 22 Nov 2024 16:52:25 -0500
Subject: [PATCH 16/22] streamline sandcastle with new imodels
---
Apps/Sandcastle/gallery/iTwin Demo.html | 138 ++++++++++++++--------
CHANGES.md | 2 +-
packages/engine/Source/Scene/ITwinData.js | 1 +
3 files changed, 94 insertions(+), 47 deletions(-)
diff --git a/Apps/Sandcastle/gallery/iTwin Demo.html b/Apps/Sandcastle/gallery/iTwin Demo.html
index 29a95fe0747a..cab4f922ad4f 100644
--- a/Apps/Sandcastle/gallery/iTwin Demo.html
+++ b/Apps/Sandcastle/gallery/iTwin Demo.html
@@ -24,24 +24,22 @@
Loading...