Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iModel integration #12289

Merged
merged 25 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0f6db77
initial rough api
jjspace Nov 5, 2024
421e5dd
restructure api, create tileset from model id
jjspace Nov 5, 2024
4ea3908
small adjustments
jjspace Nov 6, 2024
d779bc1
oauth testing web app and service app
jjspace Nov 11, 2024
50dfe46
small cleanup and removing code we won't use
jjspace Nov 14, 2024
c953b6f
cleanup and adjustments from pr comments
jjspace Nov 15, 2024
dc79961
Merge remote-tracking branch 'origin/main' into itwin-integration
jjspace Nov 15, 2024
46bd2f0
rename namespaces
jjspace Nov 15, 2024
1757f77
partial cleanup, remove changeset ids
jjspace Nov 19, 2024
b8c1ac5
remove get export and creation routes
jjspace Nov 19, 2024
958756e
update sandcastle for exports still processing
jjspace Nov 19, 2024
7049dbe
re-organize, resource instead of fetch, clean up types
jjspace Nov 20, 2024
188e2bf
fix type generation
jjspace Nov 20, 2024
3726464
minor renaming and descriptions
jjspace Nov 20, 2024
d2055b2
pull out dev auth server
jjspace Nov 22, 2024
47e1642
condense code, update docs
jjspace Nov 22, 2024
990004b
streamline sandcastle with new imodels
jjspace Nov 22, 2024
c435e2b
Merge remote-tracking branch 'origin/main' into itwin-integration
jjspace Nov 22, 2024
fa1ef66
add tests
jjspace Nov 23, 2024
d478736
sandcastle photospheres
jjspace Nov 23, 2024
153020b
doc updates
jjspace Nov 25, 2024
d066248
switch to ion auth, remove enum comments
jjspace Nov 25, 2024
77a45d8
add enum values in comments
jjspace Nov 25, 2024
99eb928
sandcastle adjustments
jjspace Nov 25, 2024
0cc0603
Merge branch 'main' into itwin-integration
ggetz Nov 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions Apps/Sandcastle/gallery/iTwin Demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,32 @@
statusOutput.innerText = "Starting export";

const start = Date.now();
const delay = (ms) => new Promise((res) => setTimeout(res, ms));

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);
let tileset;
try {
tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
} catch (error) {
jjspace marked this conversation as resolved.
Show resolved Hide resolved
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);
jjspace marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand Down
117 changes: 3 additions & 114 deletions packages/engine/Source/Core/ITwinPlatform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>3DTILES</code>.
jjspace marked this conversation as resolved.
Show resolved Hide resolved
*
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
* @private
*
* @param {string} iModelId iModel id
*
Expand Down Expand Up @@ -185,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 */
Expand Down Expand Up @@ -217,67 +169,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;
56 changes: 11 additions & 45 deletions packages/engine/Source/Scene/ITwinData.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -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
Expand All @@ -62,44 +47,25 @@ 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<Cesium3DTileset>}
*
* @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
* const tileset = await Cesium.ITwinData.createTilesetFromModelId(imodelId);
jjspace marked this conversation as resolved.
Show resolved Hide resolved
* 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<Cesium3DTileset | undefined>} Will return <code>undefined</code> if there is no export for the given iModel id
*
jjspace marked this conversation as resolved.
Show resolved Hide resolved
* @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) {
jjspace marked this conversation as resolved.
Show resolved Hide resolved
const { exports } = await ITwinPlatform.getExports(iModelId);
jjspace marked this conversation as resolved.
Show resolved Hide resolved
Expand Down