From 5e87d0349737f410c5ba22599f6971befda34f56 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Tue, 18 Mar 2025 12:23:03 +0200 Subject: [PATCH 1/8] Adding share url builder to view iModel in Cesium Sandcastle --- docs/imodel/view/cesium-sandcastle.md | 24 ++ docs/imodel/view/overview.md | 7 + package-lock.json | 21 +- package.json | 4 +- src/commands/imodel/view/cesium-sandcastle.ts | 254 ++++++++++++++++++ 5 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 docs/imodel/view/cesium-sandcastle.md create mode 100644 docs/imodel/view/overview.md create mode 100644 src/commands/imodel/view/cesium-sandcastle.ts diff --git a/docs/imodel/view/cesium-sandcastle.md b/docs/imodel/view/cesium-sandcastle.md new file mode 100644 index 00000000..4044b8f4 --- /dev/null +++ b/docs/imodel/view/cesium-sandcastle.md @@ -0,0 +1,24 @@ +# itp imodel view cesium-sandcastle + +Setup iModel and get URL to view it in Cesium Sandcastle. + +## Options + +- **`--changeset-id`** + Changeset id to be viewed in Cesium Sandcastle. + **Type:** `string` **Required:** Yes + +- **`--imodel-id`** + iModel id to be viewed in Cesium Sandcastle. + **Type:** `string` **Required:** Yes + +## Examples + +```bash +# Example 1: View a specific changeset of an iModel in Cesium Sandcastle +itp imodel view cesium-sandcastle --imodel-id "12345" --changeset-id "67890" +``` + +## API Reference + +[Cesium Sandcastle](https://cesium.com/docs/sandcastle/) diff --git a/docs/imodel/view/overview.md b/docs/imodel/view/overview.md new file mode 100644 index 00000000..633c76a7 --- /dev/null +++ b/docs/imodel/view/overview.md @@ -0,0 +1,7 @@ +# itp imodel view + +Work with views for an iModel. + +## Available Commands + +- [itp imodel view cesium-sandcastle](imodel/view/cesium-sandcastle.md) diff --git a/package-lock.json b/package-lock.json index 2f5c9cc3..fd6b3a60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,8 @@ "dotenv": "^16.4.5", "fs-extra": "^8.1.0", "jwt-decode": "^4.0.0", - "open": "^10.1.0" + "open": "^10.1.0", + "pako": "^2.1.0" }, "bin": { "itp": "bin/run.js" @@ -33,6 +34,7 @@ "@types/chai": "^4", "@types/mocha": "^10", "@types/node": "^18.19.64", + "@types/pako": "^2.0.3", "chai": "^4", "eslint": "^8", "eslint-config-oclif": "^5", @@ -3504,6 +3506,13 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, + "node_modules/@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", @@ -8596,7 +8605,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.6", + "version": "7.0.3", "inBundle": true, "license": "MIT", "dependencies": { @@ -8712,7 +8721,7 @@ "inBundle": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.6", + "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" }, "engines": { @@ -10795,6 +10804,12 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", diff --git a/package.json b/package.json index bbab0e7c..0e76646f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "dotenv": "^16.4.5", "fs-extra": "^8.1.0", "jwt-decode": "^4.0.0", - "open": "^10.1.0" + "open": "^10.1.0", + "pako": "^2.1.0" }, "devDependencies": { "@oclif/prettier-config": "^0.2.1", @@ -29,6 +30,7 @@ "@types/chai": "^4", "@types/mocha": "^10", "@types/node": "^18.19.64", + "@types/pako": "^2.0.3", "chai": "^4", "eslint": "^8", "eslint-config-oclif": "^5", diff --git a/src/commands/imodel/view/cesium-sandcastle.ts b/src/commands/imodel/view/cesium-sandcastle.ts new file mode 100644 index 00000000..5a17690e --- /dev/null +++ b/src/commands/imodel/view/cesium-sandcastle.ts @@ -0,0 +1,254 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Bentley Systems, Incorporated. All rights reserved. +* See LICENSE.md in the project root for license terms and full copyright notice. +*--------------------------------------------------------------------------------------------*/ + +import { Flags } from "@oclif/core"; +import { deflate } from "pako"; + +import BaseCommand from "../../../extensions/base-command.js"; + +export default class CesiumSandcastle extends BaseCommand { + static description = "Setup iModel and get url to view it in Cesium Sandcastle"; + + static flags = { + "changeset-id": Flags.string({ + description: "Changeset id to be viewed in Cesium Sandcastle.", + required: true + }), + "imodel-id": Flags.string({ + description: "iModel id to be viewed in Cesium Sandcastle.", + required: true + }), + }; + + htmlData() : string { + return ` + +
+

Loading...

+
+ + + + + + + + + + + + + + + +
iModel Id + +
Changeset Id + +
Access Token + +
+
` + } + + jsData(iModelId: string, changesetId: string, token: string) : string { + return ` +async function getExistingExport(iModelId, accessToken, changesetId = undefined) { + console.log("Get Existing Export"); + + const headers = { + "Authorization": accessToken, + "Accept": "application/vnd.bentley.itwin-platform.v1+json", + "Content-Type": "application/json", + "Prefer": "return=representation" + }; + + let url = \`https://api.bentley.com/mesh-export/?iModelId=\${iModelId}\`; + if (changesetId) { + url += \`&changesetId=\${changesetId}\`; + } + try { + console.log(url); + const response = await fetch(url, {headers}); + const result = await response.json(); + const existingExport = result.exports.find((exp) => exp.request.exportType === "CESIUM"); + return existingExport; + } + catch { + return undefined; + } +} + + +async function StartExport(iModelId, accessToken, changesetId = undefined) { + console.log("Starting New 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); + 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 { + const response = await fetch(url, { headers }); + const result = JSON.parse(JSON.stringify(await response.json())); + return result; + } catch (err) { + return undefined; + } +} + +async function obtainAndAttachTileset(imodelId, accessToken, changesetId = undefined) { + let tilesetUrl; + const start = Date.now(); + + const existingExport = await getExistingExport(imodelId, accessToken, changesetId); + if (existingExport) { + tilesetUrl = existingExport._links.mesh.href; + } + else { + console.log("No Existing Export Found"); + const delay = ms => new Promise(res => setTimeout(res, ms)); + const exportId = await StartExport(imodelId, accessToken, changesetId); + + let result = await GetExport(exportId, accessToken); + let status = result.export.status; + while (status !== "Complete") { + await delay(3000); + result = await GetExport(exportId, accessToken); + status = result.export.status; + console.log("Export is " + status); + + if (Date.now() - start > 300_000) { + throw new Error("Export did not complete in time."); + } + } + tilesetUrl = result.export._links.mesh.href; + } + + const splitStr = tilesetUrl.split("?"); + tilesetUrl = splitStr[0] + "/tileset.json?" + splitStr[1]; + + const tileset = await Cesium.Cesium3DTileset.fromUrl(tilesetUrl); + viewer.scene.primitives.add(tileset); + viewer.zoomTo(tileset); + console.log("Finished in " + ((Date.now() - start) / 1000).toString() + " seconds"); +} + +async function init() { + if ((viewModel.imodelId !== undefined) && (viewModel.accessToken !== undefined)) { + obtainAndAttachTileset(viewModel.imodelId, viewModel.accessToken, viewModel.changesetId); + } + else { + console.log("Define iModel Id and Access Token, then click 'Obtain and Attach Tileset'"); + } +} + +const viewer = new Cesium.Viewer("cesiumContainer"); +viewer.scene.globe.show = true; +viewer.scene.debugShowFramesPerSecond = true; + +let viewModel = { + imodelId: "${iModelId}", + accessToken: "${token}", + changesetId: "${changesetId}", +}; + +Cesium.knockout.track(viewModel); +const toolbar = document.getElementById("toolbar"); +Cesium.knockout.applyBindings(viewModel, toolbar); + +Cesium.knockout + .getObservable(viewModel, "imodelId") + .subscribe(function (newValue) { + viewModel.imodelId = newValue; + }); + + Cesium.knockout + .getObservable(viewModel, "changesetId") + .subscribe(function (newValue) { + viewModel.changesetId = newValue; + }); + +Cesium.knockout + .getObservable(viewModel, "accessToken") + .subscribe(function (newValue) { + viewModel.accessToken = newValue; + }); + +Sandcastle.addDefaultToolbarButton("Obtain and Attach Tileset", function () { + if (viewer.scene.primitive) { + viewer.scene.primitive.removeAll(); + } + init() +}); + `; + } + + makeCompressedBase64String(data: string[]) : string { + let jsonString = JSON.stringify(data); + jsonString = jsonString.slice(2, 2 + jsonString.length - 4); + let base64String = Buffer.from( + deflate(jsonString, { raw: true }) + ).toString('base64'); + base64String = base64String.replace(/=+$/, ''); // remove padding + + return base64String; + } + + async run() { + const { flags } = await this.parse(CesiumSandcastle); + + const token = await this.getAccessToken(); + + const data = [ + this.jsData(flags["imodel-id"], flags["changeset-id"], token), + this.htmlData(), + ]; + + return this.logAndReturnResult({ url: `https://sandcastle.cesium.com/#c=${this.makeCompressedBase64String(data)}`}); + } +} + + From cc443f6fc381ac22cf9aec8deed0a5abb428b7b0 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Tue, 18 Mar 2025 15:48:36 +0200 Subject: [PATCH 2/8] Adding ability to auto open the URL in default browser --- docs/imodel/view/cesium-sandcastle.md | 11 +++++++++-- src/commands/imodel/view/cesium-sandcastle.ts | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/imodel/view/cesium-sandcastle.md b/docs/imodel/view/cesium-sandcastle.md index 4044b8f4..4876806c 100644 --- a/docs/imodel/view/cesium-sandcastle.md +++ b/docs/imodel/view/cesium-sandcastle.md @@ -12,11 +12,18 @@ Setup iModel and get URL to view it in Cesium Sandcastle. iModel id to be viewed in Cesium Sandcastle. **Type:** `string` **Required:** Yes +- **`--open`** + Open the URL in the browser. + **Type:** `boolean` **Required:** No + ## Examples ```bash -# Example 1: View a specific changeset of an iModel in Cesium Sandcastle -itp imodel view cesium-sandcastle --imodel-id "12345" --changeset-id "67890" +# Example 1: Get a link to a specific changeset of an iModel in Cesium Sandcastle +itp imodel view cesium-sandcastle --imodel-id "5e19bee0-3aea-4355-a9f0-c6df9989ee7d" --changeset-id "2f3b4a8c92d747d5c8a8b2f9cde6742e5d74b3b5" + +# Example 2: Get a link to a specific changeset of an iModel in Cesium Sandcastle and open the URL in the browser +itp imodel view cesium-sandcastle --imodel-id "5e19bee0-3aea-4355-a9f0-c6df9989ee7d" --changeset-id "2f3b4a8c92d747d5c8a8b2f9cde6742e5d74b3b5" --open ``` ## API Reference diff --git a/src/commands/imodel/view/cesium-sandcastle.ts b/src/commands/imodel/view/cesium-sandcastle.ts index 5a17690e..b80a6be6 100644 --- a/src/commands/imodel/view/cesium-sandcastle.ts +++ b/src/commands/imodel/view/cesium-sandcastle.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Flags } from "@oclif/core"; +import open from 'open'; import { deflate } from "pako"; import BaseCommand from "../../../extensions/base-command.js"; @@ -20,6 +21,10 @@ export default class CesiumSandcastle extends BaseCommand { description: "iModel id to be viewed in Cesium Sandcastle.", required: true }), + "open": Flags.boolean({ + description: "Open the URL in the browser.", + required: false, + }), }; htmlData() : string { @@ -246,8 +251,14 @@ Sandcastle.addDefaultToolbarButton("Obtain and Attach Tileset", function () { this.jsData(flags["imodel-id"], flags["changeset-id"], token), this.htmlData(), ]; - - return this.logAndReturnResult({ url: `https://sandcastle.cesium.com/#c=${this.makeCompressedBase64String(data)}`}); + + const url = `https://sandcastle.cesium.com/#c=${this.makeCompressedBase64String(data)}`; + + if (flags.open) { + open(url); + } + + return this.logAndReturnResult({ url }); } } From c5ac658d8a9985135458ec81c95677315380df63 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Mon, 24 Mar 2025 16:27:39 +0200 Subject: [PATCH 3/8] Handle iTwinPlatform calls before export on the CLI instead of sandcastle console. --- src/commands/api/index.ts | 6 +- src/commands/imodel/view/cesium-sandcastle.ts | 338 +++++++----------- 2 files changed, 129 insertions(+), 215 deletions(-) diff --git a/src/commands/api/index.ts b/src/commands/api/index.ts index c6251d90..a1e72576 100644 --- a/src/commands/api/index.ts +++ b/src/commands/api/index.ts @@ -51,13 +51,13 @@ export default class ApiRequest extends BaseCommand { const mappedHeaders: Record = flags.header?.reduce((acc, header) => { const [key, value] = header.split(":"); - acc[key] = value; + acc[key] = value.trim(); return acc; }, {} as Record) || {}; const query: Query[] | undefined = flags.query?.map((query) => { const [key, value] = query.split(":"); - return { key, value }; + return { key: key.trim(), value: value.trim() }; }) || undefined; const client = await this.getITwinApiClient(); @@ -65,12 +65,12 @@ export default class ApiRequest extends BaseCommand { const requestOptions = { apiPath: flags.path, apiVersionHeader: flags["version-header"], + body: flags.body ? JSON.parse(flags.body) : undefined, headers: mappedHeaders, method: flags.method as "DELETE" | "GET" | "PATCH" | "POST" | "PUT", query }; - if (flags["empty-response"]) { await client.sendRequestNoResponse(requestOptions); return this.logAndReturnResult({result: "success"}); diff --git a/src/commands/imodel/view/cesium-sandcastle.ts b/src/commands/imodel/view/cesium-sandcastle.ts index b80a6be6..f106e79d 100644 --- a/src/commands/imodel/view/cesium-sandcastle.ts +++ b/src/commands/imodel/view/cesium-sandcastle.ts @@ -8,6 +8,7 @@ import open from 'open'; import { deflate } from "pako"; import BaseCommand from "../../../extensions/base-command.js"; +import { link, links } from "../../../services/general-models/links.js"; export default class CesiumSandcastle extends BaseCommand { static description = "Setup iModel and get url to view it in Cesium Sandcastle"; @@ -27,239 +28,152 @@ export default class CesiumSandcastle extends BaseCommand { }), }; - htmlData() : string { - return ` + async createExport(iModelId: string, changesetId: string): Promise { + const args = [ + "--method", "POST", + "--path", "mesh-export", + "--version-header", "application/vnd.bentley.itwin-platform.v1+json", + "--body", JSON.stringify({ + changesetId, + exportType: "CESIUM", + iModelId + }), + ]; -
-

Loading...

-
- - - - - - - - - - - - - - - -
iModel Id - -
Changeset Id - -
Access Token - -
-
` + const created = await this.runCommand("api", args); + return created.export; } - jsData(iModelId: string, changesetId: string, token: string) : string { - return ` -async function getExistingExport(iModelId, accessToken, changesetId = undefined) { - console.log("Get Existing Export"); - - const headers = { - "Authorization": accessToken, - "Accept": "application/vnd.bentley.itwin-platform.v1+json", - "Content-Type": "application/json", - "Prefer": "return=representation" - }; - - let url = \`https://api.bentley.com/mesh-export/?iModelId=\${iModelId}\`; - if (changesetId) { - url += \`&changesetId=\${changesetId}\`; - } - try { - console.log(url); - const response = await fetch(url, {headers}); - const result = await response.json(); - const existingExport = result.exports.find((exp) => exp.request.exportType === "CESIUM"); - return existingExport; - } - catch { - return undefined; - } -} - - -async function StartExport(iModelId, accessToken, changesetId = undefined) { - console.log("Starting New 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); - 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 { - const response = await fetch(url, { headers }); - const result = JSON.parse(JSON.stringify(await response.json())); - return result; - } catch (err) { - return undefined; - } -} - -async function obtainAndAttachTileset(imodelId, accessToken, changesetId = undefined) { - let tilesetUrl; - const start = Date.now(); - - const existingExport = await getExistingExport(imodelId, accessToken, changesetId); - if (existingExport) { - tilesetUrl = existingExport._links.mesh.href; - } - else { - console.log("No Existing Export Found"); - const delay = ms => new Promise(res => setTimeout(res, ms)); - const exportId = await StartExport(imodelId, accessToken, changesetId); - - let result = await GetExport(exportId, accessToken); - let status = result.export.status; - while (status !== "Complete") { - await delay(3000); - result = await GetExport(exportId, accessToken); - status = result.export.status; - console.log("Export is " + status); - - if (Date.now() - start > 300_000) { - throw new Error("Export did not complete in time."); - } + async getExports(iModelId: string) : Promise { + const exportArgs = [ + "--method", "GET", + "--path", "mesh-export/", + "--version-header", "application/vnd.bentley.itwin-platform.v1+json", + "--query", `iModelId: ${iModelId}`, + "--header", "Prefer: return=representation" + ]; + const response = await this.runCommand("api", exportArgs); + return response.exports; } - tilesetUrl = result.export._links.mesh.href; - } - const splitStr = tilesetUrl.split("?"); - tilesetUrl = splitStr[0] + "/tileset.json?" + splitStr[1]; - - const tileset = await Cesium.Cesium3DTileset.fromUrl(tilesetUrl); - viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset); - console.log("Finished in " + ((Date.now() - start) / 1000).toString() + " seconds"); -} + async getOrCreateExport(iModelId: string, changesetId: string): Promise { + this.log(`Getting existing exports for iModel: ${iModelId} and changeset: ${changesetId}`); + let existingExports = await this.getExports(iModelId); + const existingExport = existingExports.find((exp) => exp.request.exportType === "CESIUM" && exp.request.changesetId === changesetId); -async function init() { - if ((viewModel.imodelId !== undefined) && (viewModel.accessToken !== undefined)) { - obtainAndAttachTileset(viewModel.imodelId, viewModel.accessToken, viewModel.changesetId); - } - else { - console.log("Define iModel Id and Access Token, then click 'Obtain and Attach Tileset'"); - } -} - -const viewer = new Cesium.Viewer("cesiumContainer"); -viewer.scene.globe.show = true; -viewer.scene.debugShowFramesPerSecond = true; - -let viewModel = { - imodelId: "${iModelId}", - accessToken: "${token}", - changesetId: "${changesetId}", -}; - -Cesium.knockout.track(viewModel); -const toolbar = document.getElementById("toolbar"); -Cesium.knockout.applyBindings(viewModel, toolbar); - -Cesium.knockout - .getObservable(viewModel, "imodelId") - .subscribe(function (newValue) { - viewModel.imodelId = newValue; - }); - - Cesium.knockout - .getObservable(viewModel, "changesetId") - .subscribe(function (newValue) { - viewModel.changesetId = newValue; - }); - -Cesium.knockout - .getObservable(viewModel, "accessToken") - .subscribe(function (newValue) { - viewModel.accessToken = newValue; - }); - -Sandcastle.addDefaultToolbarButton("Obtain and Attach Tileset", function () { - if (viewer.scene.primitive) { - viewer.scene.primitive.removeAll(); - } - init() -}); - `; - } - - makeCompressedBase64String(data: string[]) : string { - let jsonString = JSON.stringify(data); - jsonString = jsonString.slice(2, 2 + jsonString.length - 4); - let base64String = Buffer.from( - deflate(jsonString, { raw: true }) - ).toString('base64'); - base64String = base64String.replace(/=+$/, ''); // remove padding + if (existingExport !== undefined) { + this.log(`Found existing export with id: ${existingExport.id}`); + return existingExport; + } - return base64String; + this.log(`Creating new export for iModel: ${iModelId} and changeset: ${changesetId}`); + let newExport = await this.createExport(iModelId, changesetId); + while (newExport.status !== "Complete") { + this.log(`Export status is ${newExport.status}. Waiting for export to complete...`); + // eslint-disable-next-line no-await-in-loop + existingExports = await this.getExports(iModelId); + newExport = existingExports.find((exp) => exp.id === newExport.id)!; + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => {setTimeout(resolve, 5000)}); + } + + this.log(`Export completed successfully`); + return newExport; } async run() { const { flags } = await this.parse(CesiumSandcastle); - const token = await this.getAccessToken(); - + const exportInfo : ExportInfo = await this.getOrCreateExport(flags["imodel-id"], flags["changeset-id"]); + + this.log(`Extracting tileset URL from export info`); + const tilesetUrl : string = extractTileSetUrl(exportInfo); + const data = [ - this.jsData(flags["imodel-id"], flags["changeset-id"], token), - this.htmlData(), + jsData(tilesetUrl), + htmlData(), ]; - const url = `https://sandcastle.cesium.com/#c=${this.makeCompressedBase64String(data)}`; + const url = `https://sandcastle.cesium.com/#c=${makeCompressedBase64String(data)}`; + + this.log(`Cesium Sandcastle URL:`); + this.log(url); if (flags.open) { + this.log(`Opening URL in browser...`); open(url); } - + return this.logAndReturnResult({ url }); } } +type ExportResponse = { + _links: links + exports: ExportInfo[], +} + +type ExportCreateResponse = { + export: ExportInfo +} + +type ExportInfo = { + _links: { + mesh: link + }, + displayName: string, + error?: string, + id: string, + lastModified: Date, + request: ExportRequest, + status: "Complete" | "InProgress" | "Invalid" | "NotStarted", +} + +type ExportRequest = { + changesetId: string, + exportType: "3DFT" | "3DTiles" | "CESIUM" | "IMODEL", + iModelId: string, +} + +function extractTileSetUrl(exportInfo: ExportInfo): string { + if(exportInfo._links.mesh.href === undefined) { + throw new Error(`No tileset url found for export info id: ${exportInfo.id}`); + } + + const urlParts = exportInfo._links.mesh.href.split("?"); + return urlParts[0] + "/tileset.json?" + urlParts[1]; +} + +function makeCompressedBase64String(data: string[]) : string { + let jsonString = JSON.stringify(data); + jsonString = jsonString.slice(2, 2 + jsonString.length - 4); + let base64String = Buffer.from( + deflate(jsonString, { raw: true }) + ).toString('base64'); + base64String = base64String.replace(/=+$/, ''); // remove padding + + return base64String; +} + +function htmlData() : string { + return ` + + +
+`; +} + +function jsData(tilesetUrl: string) : string { + return ` +const viewer = new Cesium.Viewer("cesiumContainer"); +viewer.scene.globe.show = true; +viewer.scene.debugShowFramesPerSecond = true; +const tilesetUrl = '${tilesetUrl}'; +const tileset = await Cesium.Cesium3DTileset.fromUrl(tilesetUrl); +viewer.scene.primitives.add(tileset); +viewer.zoomTo(tileset); +`; +} From a442933f46b6874f89ac52c61e570ae3d120aad7 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Mon, 24 Mar 2025 16:31:22 +0200 Subject: [PATCH 4/8] Adding cesium-sandcastle docs --- docs/_sidebar.md | 3 +++ docs/imodel/overview.md | 1 + 2 files changed, 4 insertions(+) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 4b27a536..77b5fc3c 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -6,6 +6,7 @@ * [auth](auth/overview.md) * [login](auth/login.md) * [logout](auth/logout.md) +* [api](api.md) * [itwin](itwin/overview.md) * [create](itwin/create.md) * [update](itwin/update.md) @@ -20,6 +21,8 @@ * [delete](imodel/delete.md) * [info](imodel/info.md) * [list](imodel/list.md) + * [view](imodel/view/overview.md) + * [cesium-sandcastle](imodel/view/cesium-sandcastle.md) * [changeset](imodel/changeset/overview.md) * [named-version](imodel/named-version/overview.md) * [connection](imodel/connection/overview.md) diff --git a/docs/imodel/overview.md b/docs/imodel/overview.md index bb9e5b29..d9fb2582 100644 --- a/docs/imodel/overview.md +++ b/docs/imodel/overview.md @@ -10,6 +10,7 @@ Work with iModels of an iTwin. - [itp imodel delete](imodel/delete.md) - [itp imodel info](imodel/info.md) - [itp imodel list](imodel/list.md) +- [itp imodel view](imodel/view/overview.md) - [itp imodel changeset](imodel/changeset/overview.md) - [itp imodel namedversion](imodel/named-version/overview.md) - [itp imodel connection](imodel/connection/overview.md) From 20866344c18becf182970e832e64cd7354129f78 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Mon, 24 Mar 2025 16:36:10 +0200 Subject: [PATCH 5/8] Fixing cross-spawn dependency --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd6b3a60..a206d00f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8605,7 +8605,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.3", + "version": "7.0.6", "inBundle": true, "license": "MIT", "dependencies": { @@ -8721,7 +8721,7 @@ "inBundle": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { From 7295540e7b008591eb555f0860be11eb861ba0b6 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Mon, 24 Mar 2025 16:37:24 +0200 Subject: [PATCH 6/8] Adding single character flag --- docs/imodel/view/cesium-sandcastle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/imodel/view/cesium-sandcastle.md b/docs/imodel/view/cesium-sandcastle.md index 4876806c..b55f85ee 100644 --- a/docs/imodel/view/cesium-sandcastle.md +++ b/docs/imodel/view/cesium-sandcastle.md @@ -8,7 +8,7 @@ Setup iModel and get URL to view it in Cesium Sandcastle. Changeset id to be viewed in Cesium Sandcastle. **Type:** `string` **Required:** Yes -- **`--imodel-id`** +- **`-m, --imodel-id`** iModel id to be viewed in Cesium Sandcastle. **Type:** `string` **Required:** Yes From 440e645b3e19e7d2643633ae69403e496ed78c22 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Mon, 24 Mar 2025 16:38:07 +0200 Subject: [PATCH 7/8] Updating package.json version to 0.0.4 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a206d00f..ecd6ddbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "itp", - "version": "0.0.3", + "version": "0.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "itp", - "version": "0.0.3", + "version": "0.0.4", "license": "MIT", "dependencies": { "@itwin/imodels-client-management": "^5.9.0", diff --git a/package.json b/package.json index 0e76646f..ec1ce1f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "itp", "description": "Work seamlessly with the iTwin Platform", - "version": "0.0.3", + "version": "0.0.4", "author": "Bentley systems, Incorporated", "bin": { "itp": "./bin/run.js" From 113cefd6fd3028a7d0c7259a59dfe35d82b233a8 Mon Sep 17 00:00:00 2001 From: Vilius Albrechtas Date: Mon, 24 Mar 2025 16:45:09 +0200 Subject: [PATCH 8/8] Adding single char flag to command --- src/commands/imodel/view/cesium-sandcastle.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/imodel/view/cesium-sandcastle.ts b/src/commands/imodel/view/cesium-sandcastle.ts index f106e79d..8d266b65 100644 --- a/src/commands/imodel/view/cesium-sandcastle.ts +++ b/src/commands/imodel/view/cesium-sandcastle.ts @@ -19,7 +19,8 @@ export default class CesiumSandcastle extends BaseCommand { required: true }), "imodel-id": Flags.string({ - description: "iModel id to be viewed in Cesium Sandcastle.", + char: "m", + description: "iModel id to be viewed in Cesium Sandcastle.", required: true }), "open": Flags.boolean({