From 0d9cfe2790e1ae287520af50aa9719eeedadb648 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 5 Mar 2025 11:23:49 -0500 Subject: [PATCH 01/15] iTwin CCC loader support in Unreal --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 91 +++++++++++++++---- Source/CesiumRuntime/Public/Cesium3DTileset.h | 51 ++++++++++- extern/cesium-native | 2 +- 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 3978628e9..0ba309130 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -433,6 +433,24 @@ void ACesium3DTileset::SetIonAccessToken(const FString& InAccessToken) { } } +void ACesium3DTileset::SetITwinCesiumContentID(int64 InContentID) { + if (InContentID >= 0 && InContentID != this->ITwinCesiumContentID) { + if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent) { + this->DestroyTileset(); + } + this->ITwinCesiumContentID = InContentID; + } +} + +void ACesium3DTileset::SetITwinAccessToken(const FString& InAccessToken) { + if (this->ITwinAccessToken != InAccessToken) { + if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent) { + this->DestroyTileset(); + } + this->ITwinAccessToken = InAccessToken; + } +} + void ACesium3DTileset::SetCesiumIonServer(UCesiumIonServer* Server) { if (this->CesiumIonServer != Server) { if (this->TilesetSource == ETilesetSource::FromCesiumIon) { @@ -1107,30 +1125,47 @@ void ACesium3DTileset::LoadTileset() { Log, TEXT("Loading tileset for asset ID %d"), this->IonAssetID); - FString token = this->IonAccessToken.IsEmpty() - ? this->CesiumIonServer->DefaultIonAccessToken - : this->IonAccessToken; + { + FString token = this->IonAccessToken.IsEmpty() + ? this->CesiumIonServer->DefaultIonAccessToken + : this->IonAccessToken; #if WITH_EDITOR - this->CesiumIonServer->ResolveApiUrl(); + this->CesiumIonServer->ResolveApiUrl(); #endif - std::string ionAssetEndpointUrl = - TCHAR_TO_UTF8(*this->CesiumIonServer->ApiUrl); + std::string ionAssetEndpointUrl = + TCHAR_TO_UTF8(*this->CesiumIonServer->ApiUrl); - if (!ionAssetEndpointUrl.empty()) { - // Make sure the URL ends with a slash - if (!ionAssetEndpointUrl.empty() && *ionAssetEndpointUrl.rbegin() != '/') - ionAssetEndpointUrl += '/'; + if (!ionAssetEndpointUrl.empty()) { + // Make sure the URL ends with a slash + if (!ionAssetEndpointUrl.empty() && + *ionAssetEndpointUrl.rbegin() != '/') + ionAssetEndpointUrl += '/'; - this->_pTileset = MakeUnique( - externals, - static_cast(this->IonAssetID), - TCHAR_TO_UTF8(*token), - options, - ionAssetEndpointUrl); + this->_pTileset = MakeUnique( + externals, + static_cast(this->IonAssetID), + TCHAR_TO_UTF8(*token), + options, + ionAssetEndpointUrl); + } } break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Log, + TEXT("Loading tileset for asset ID %d"), + this->ITwinCesiumContentID); + + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory( + static_cast(this->ITwinCesiumContentID), + TCHAR_TO_UTF8(*this->ITwinAccessToken)), + options); + break; } #ifdef CESIUM_DEBUG_TILE_STATES @@ -1180,6 +1215,13 @@ void ACesium3DTileset::LoadTileset() { TEXT("Loading tileset for asset ID %d done"), this->IonAssetID); break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Log, + TEXT("Loading tileset for asset ID %d done"), + this->ITwinCesiumContentID); + break; } switch (ApplyDpiScaling) { @@ -1221,6 +1263,13 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for asset ID %d"), this->IonAssetID); break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Verbose, + TEXT("Destroying tileset for asset ID %d"), + this->ITwinCesiumContentID); + break; } // The way CesiumRasterOverlay::add is currently implemented, destroying the @@ -1274,6 +1323,13 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for asset ID %d done"), this->IonAssetID); break; + case ETilesetSource::FromITwinCesiumCuratedContent: + UE_LOG( + LogCesium, + Verbose, + TEXT("Destroying tileset for asset ID %d done"), + this->ITwinCesiumContentID); + break; } } @@ -2142,6 +2198,9 @@ void ACesium3DTileset::PostEditChangeProperty( PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, Url) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IonAssetID) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IonAccessToken) || + PropName == + GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinCesiumContentID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinAccessToken) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CreatePhysicsMeshes) || PropName == diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 95c19d593..fee86fd58 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -83,7 +83,14 @@ enum class ETilesetSource : uint8 { /** * The tileset will be loaded from the georeference ellipsoid. */ - FromEllipsoid UMETA(DisplayName = "From Ellipsoid") + FromEllipsoid UMETA(DisplayName = "From Ellipsoid"), + + /** + * The tileset will be loaded from the Bentley iTwin Cesium Curated Content + * API using the provided ITwinCesiumContentID and ITwinAccessToken. + */ + FromITwinCesiumCuratedContent UMETA( + DisplayName = "From iTwin Cesium Curated Content"), }; UENUM(BlueprintType) @@ -764,6 +771,36 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { meta = (EditCondition = "TilesetSource==ETilesetSource::FromCesiumIon")) UCesiumIonServer* CesiumIonServer; + /** + * The ID of the iTwin Cesium Curated Content asset to use. + * + * This property is ignored if TilesetSource isn't set to "From iTwin Cesium + * Curated Content" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetITwinCesiumContentID, + BlueprintSetter = SetITwinCesiumContentID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent", + ClampMin = 0)) + int64 ITwinCesiumContentID; + + /** + * The access token to use to access the iTwin resource. + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetITwinAccessToken, + BlueprintSetter = SetITwinAccessToken, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent")) + FString ITwinAccessToken; + /** * Headers to be attached to each request made for this tileset. */ @@ -1038,6 +1075,18 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { UFUNCTION(BlueprintGetter, Category = "Cesium") UCesiumIonServer* GetCesiumIonServer() const { return CesiumIonServer; } + UFUNCTION(BlueprintGetter, Category = "Cesium") + int64 GetITwinCesiumContentID() const { return ITwinCesiumContentID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetITwinCesiumContentID(int64 InContentID); + + UFUNCTION(BlueprintGetter, Category = "Cesium") + FString GetITwinAccessToken() const { return ITwinAccessToken; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetITwinAccessToken(const FString& InAccessToken); + UFUNCTION(BlueprintGetter, Category = "VirtualTexture") TArray GetRuntimeVirtualTextures() const { return RuntimeVirtualTextures; diff --git a/extern/cesium-native b/extern/cesium-native index b3bbd407a..036ce1f5f 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit b3bbd407ac2bab298a1187ee0192bc19702c26f6 +Subproject commit 036ce1f5f151fb3772966b6861eb02f13078a2b2 From e5a385a8c249289f458336be1907b3f6c4d3bf0d Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 5 Mar 2025 15:45:50 -0500 Subject: [PATCH 02/15] Add mesh export service loader --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 48 ++++++++++++++++++- Source/CesiumRuntime/Public/Cesium3DTileset.h | 32 ++++++++++++- extern/cesium-native | 2 +- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 0ba309130..3db73080c 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -442,9 +442,19 @@ void ACesium3DTileset::SetITwinCesiumContentID(int64 InContentID) { } } +void ACesium3DTileset::SetIModelID(const FString& InModelID) { + if (InModelID != this->IModelID) { + if (this->TilesetSource == ETilesetSource::FromIModelMeshExportService) { + this->DestroyTileset(); + } + this->IModelID = InModelID; + } +} + void ACesium3DTileset::SetITwinAccessToken(const FString& InAccessToken) { if (this->ITwinAccessToken != InAccessToken) { - if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent) { + if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent || + this->TilesetSource == ETilesetSource::FromIModelMeshExportService) { this->DestroyTileset(); } this->ITwinAccessToken = InAccessToken; @@ -1166,6 +1176,21 @@ void ACesium3DTileset::LoadTileset() { TCHAR_TO_UTF8(*this->ITwinAccessToken)), options); break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Loading mesh export for iModel ID %s"), + *this->IModelID); + + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::IModelMeshExportContentLoaderFactory( + TCHAR_TO_UTF8(*this->IModelID), + std::nullopt, + TCHAR_TO_UTF8(*this->ITwinAccessToken)), + options); + break; } #ifdef CESIUM_DEBUG_TILE_STATES @@ -1222,6 +1247,13 @@ void ACesium3DTileset::LoadTileset() { TEXT("Loading tileset for asset ID %d done"), this->ITwinCesiumContentID); break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Loading mesh export for iModel ID %s done"), + *this->IModelID); + break; } switch (ApplyDpiScaling) { @@ -1270,6 +1302,13 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for asset ID %d"), this->ITwinCesiumContentID); break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for iModel ID %s"), + *this->IModelID); + break; } // The way CesiumRasterOverlay::add is currently implemented, destroying the @@ -1330,6 +1369,13 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for asset ID %d done"), this->ITwinCesiumContentID); break; + case ETilesetSource::FromIModelMeshExportService: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for iModel ID %s done"), + *this->IModelID); + break; } } diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index fee86fd58..9290ceafa 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -91,6 +91,13 @@ enum class ETilesetSource : uint8 { */ FromITwinCesiumCuratedContent UMETA( DisplayName = "From iTwin Cesium Curated Content"), + + /** + * The tileset will be loaded from the iModel Mesh Export Service using the + * provided IModelID and ITwinAccessToken. + */ + FromIModelMeshExportService UMETA( + DisplayName = "From iModel Mesh Export Service"), }; UENUM(BlueprintType) @@ -788,6 +795,23 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { ClampMin = 0)) int64 ITwinCesiumContentID; + /** + * The ID of the iModel to use when interacting with the iModel Mesh Export + * Service. + * + * This property is ignored if TilesetSource isn't set to "From iModel Mesh + * Export Service" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetIModelID, + BlueprintSetter = SetIModelID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromIModelMeshExportService")) + FString IModelID; + /** * The access token to use to access the iTwin resource. */ @@ -798,7 +822,7 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { Category = "Cesium", meta = (EditCondition = - "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent")) + "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent || TilesetSource==ETilesetSource::FromIModelMeshExportService")) FString ITwinAccessToken; /** @@ -1081,6 +1105,12 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { UFUNCTION(BlueprintSetter, Category = "Cesium") void SetITwinCesiumContentID(int64 InContentID); + UFUNCTION(BlueprintGetter, Category = "Cesium") + FString GetIModelID() const { return IModelID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetIModelID(const FString& InModelID); + UFUNCTION(BlueprintGetter, Category = "Cesium") FString GetITwinAccessToken() const { return ITwinAccessToken; } diff --git a/extern/cesium-native b/extern/cesium-native index 036ce1f5f..ee9aa028a 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 036ce1f5f151fb3772966b6861eb02f13078a2b2 +Subproject commit ee9aa028a14f576ed179fb398d20f1d6ed459afa From 6536153368270ec43cd1aef06fc54fa8da17393e Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 6 Mar 2025 17:01:26 -0500 Subject: [PATCH 03/15] Add iTwin Reality Data loader --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 60 ++++++++++++++++++- Source/CesiumRuntime/Public/Cesium3DTileset.h | 52 +++++++++++++++- extern/cesium-native | 2 +- 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 3db73080c..57326fbb9 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -451,10 +451,29 @@ void ACesium3DTileset::SetIModelID(const FString& InModelID) { } } +void ACesium3DTileset::SetRealityDataID(const FString& InRealityDataID) { + if (InRealityDataID != this->RealityDataID) { + if (this->TilesetSource == ETilesetSource::FromITwinRealityData) { + this->DestroyTileset(); + } + this->RealityDataID = InRealityDataID; + } +} + +void ACesium3DTileset::SetITwinID(const FString& InITwinID) { + if (InITwinID != this->ITwinID) { + if (this->TilesetSource == ETilesetSource::FromITwinRealityData) { + this->DestroyTileset(); + } + this->ITwinID = InITwinID; + } +} + void ACesium3DTileset::SetITwinAccessToken(const FString& InAccessToken) { if (this->ITwinAccessToken != InAccessToken) { if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent || - this->TilesetSource == ETilesetSource::FromIModelMeshExportService) { + this->TilesetSource == ETilesetSource::FromIModelMeshExportService || + this->TilesetSource == ETilesetSource::FromITwinRealityData) { this->DestroyTileset(); } this->ITwinAccessToken = InAccessToken; @@ -1191,6 +1210,21 @@ void ACesium3DTileset::LoadTileset() { TCHAR_TO_UTF8(*this->ITwinAccessToken)), options); break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Loading reality data ID %s"), + *this->RealityDataID); + + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::ITwinRealityDataContentLoaderFactory( + TCHAR_TO_UTF8(*this->RealityDataID), + this->ITwinID.IsEmpty() ? std::nullopt : std::make_optional(TCHAR_TO_UTF8(*this->ITwinID)), + TCHAR_TO_UTF8(*this->ITwinAccessToken)), + options); + break; } #ifdef CESIUM_DEBUG_TILE_STATES @@ -1254,6 +1288,13 @@ void ACesium3DTileset::LoadTileset() { TEXT("Loading mesh export for iModel ID %s done"), *this->IModelID); break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Loading reality data ID %s done"), + *this->RealityDataID); + break; } switch (ApplyDpiScaling) { @@ -1309,6 +1350,13 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for iModel ID %s"), *this->IModelID); break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for reality data ID %s done"), + *this->RealityDataID); + break; } // The way CesiumRasterOverlay::add is currently implemented, destroying the @@ -1376,6 +1424,13 @@ void ACesium3DTileset::DestroyTileset() { TEXT("Destroying tileset for iModel ID %s done"), *this->IModelID); break; + case ETilesetSource::FromITwinRealityData: + UE_LOG( + LogCesium, + Log, + TEXT("Destroying tileset for reality data ID %s done"), + *this->RealityDataID); + break; } } @@ -2246,6 +2301,9 @@ void ACesium3DTileset::PostEditChangeProperty( PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IonAccessToken) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinCesiumContentID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IModelID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, RealityDataID) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinID) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinAccessToken) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CreatePhysicsMeshes) || diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 9290ceafa..f18f57950 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -98,6 +98,12 @@ enum class ETilesetSource : uint8 { */ FromIModelMeshExportService UMETA( DisplayName = "From iModel Mesh Export Service"), + + /** + * The tileset will be loaded from the iModel Mesh Export Service using the + * provided RealityDataID and ITwinAccessToken. + */ + FromITwinRealityData UMETA(DisplayName = "From iTwin Reality Data") }; UENUM(BlueprintType) @@ -812,6 +818,38 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { "TilesetSource==ETilesetSource::FromIModelMeshExportService")) FString IModelID; + /** + * The ID of the iTwin Reality Data to use when interacting with the Reality Data service. + * + * This property is ignored if TilesetSource isn't set to "From iTwin Reality Data" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetRealityDataID, + BlueprintSetter = SetRealityDataID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinRealityData")) + FString RealityDataID; + + /** + * The ID of the iTwin to use when interacting with the Reality + * Data service. + * + * This property is ignored if TilesetSource isn't set to "From iTwin Reality + * Data" + */ + UPROPERTY( + EditAnywhere, + BlueprintGetter = GetITwinID, + BlueprintSetter = SetITwinID, + Category = "Cesium", + meta = + (EditCondition = + "TilesetSource==ETilesetSource::FromITwinRealityData")) + FString ITwinID; + /** * The access token to use to access the iTwin resource. */ @@ -822,7 +860,7 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { Category = "Cesium", meta = (EditCondition = - "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent || TilesetSource==ETilesetSource::FromIModelMeshExportService")) + "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent || TilesetSource==ETilesetSource::FromIModelMeshExportService || TilesetSource==ETilesetSource::FromITwinRealityData")) FString ITwinAccessToken; /** @@ -1111,6 +1149,18 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { UFUNCTION(BlueprintSetter, Category = "Cesium") void SetIModelID(const FString& InModelID); + UFUNCTION(BlueprintGetter, Category = "Cesium") + FString GetRealityDataID() const { return RealityDataID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetRealityDataID(const FString& InModelID); + + UFUNCTION(BlueprintGetter, Category = "Cesium") + FString GetITwinID() const { return ITwinID; } + + UFUNCTION(BlueprintSetter, Category = "Cesium") + void SetITwinID(const FString& InITwinID); + UFUNCTION(BlueprintGetter, Category = "Cesium") FString GetITwinAccessToken() const { return ITwinAccessToken; } diff --git a/extern/cesium-native b/extern/cesium-native index ee9aa028a..0ffa23285 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit ee9aa028a14f576ed179fb398d20f1d6ed459afa +Subproject commit 0ffa23285746779fab84bf7d008001a980a37256 From 15c5370efbe049aab0156b0c4cd938097d51da06 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 13 Mar 2025 17:23:28 -0400 Subject: [PATCH 04/15] iTwin blueprint API --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 4 +- .../CesiumITwinAPIBlueprintLibrary.cpp | 203 +++++++++++++ Source/CesiumRuntime/Public/Cesium3DTileset.h | 6 +- .../Public/CesiumITwinAPIBlueprintLibrary.h | 271 ++++++++++++++++++ extern/cesium-native | 2 +- 5 files changed, 482 insertions(+), 4 deletions(-) create mode 100644 Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp create mode 100644 Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 57326fbb9..80a0d7754 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -1221,7 +1221,9 @@ void ACesium3DTileset::LoadTileset() { externals, Cesium3DTilesSelection::ITwinRealityDataContentLoaderFactory( TCHAR_TO_UTF8(*this->RealityDataID), - this->ITwinID.IsEmpty() ? std::nullopt : std::make_optional(TCHAR_TO_UTF8(*this->ITwinID)), + this->ITwinID.IsEmpty() ? std::nullopt + : std::make_optional( + TCHAR_TO_UTF8(*this->ITwinID)), TCHAR_TO_UTF8(*this->ITwinAccessToken)), options); break; diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp new file mode 100644 index 000000000..11865b5f0 --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -0,0 +1,203 @@ +#include "CesiumITwinAPIBlueprintLibrary.h" + +#include "CesiumRuntime.h" +#include + +UCesiumITwinAPIAuthorizeAsyncAction* +UCesiumITwinAPIAuthorizeAsyncAction::Authorize(const FString& ClientID) { + UCesiumITwinAPIAuthorizeAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->_clientId = ClientID; + return pAsyncAction; +} + +void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { + CesiumITwinClient::Connection::authorize( + getAsyncSystem(), + getAssetAccessor(), + "Cesium for Unreal", + TCHAR_TO_UTF8(*this->_clientId), + "/itwin/auth/redirect", + 5081, + std::vector{"itwin-platform", "offline_access"}, + [&Callback = this->OnAuthorizationEvent](const std::string& url) { + Callback.Broadcast( + ECesiumITwinDelegateType::OpenUrl, + UTF8_TO_TCHAR(url.c_str()), + nullptr, + TArray()); + }) + .thenInMainThread([this](CesiumUtility::Result< + CesiumITwinClient::Connection>&& connection) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Authorization finished but authorize async action is no longer valid.")); + return; + } + + if (!connection.value) { + TArray Errors; + for (const std::string& error : connection.errors.errors) { + Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); + } + + for (const std::string& warning : connection.errors.warnings) { + Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); + } + + this->OnAuthorizationEvent.Broadcast( + ECesiumITwinDelegateType::Failure, + FString(), + nullptr, + Errors); + } else { + TSharedPtr pInternalConnection = + MakeShared( + MoveTemp(*connection.value)); + UCesiumITwinConnection* pConnection = + NewObject(); + pConnection->SetConnection(pInternalConnection); + this->OnAuthorizationEvent.Broadcast( + ECesiumITwinDelegateType::Success, + FString(), + pConnection, + TArray()); + this->SetReadyToDestroy(); + } + }); +} + +UCesiumITwinAPIGetProfileAsyncAction* +UCesiumITwinAPIGetProfileAsyncAction::GetProfile( + UCesiumITwinConnection* pConnection) { + UCesiumITwinAPIGetProfileAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + return pAsyncAction; +} + +void UCesiumITwinAPIGetProfileAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnProfileResult.Broadcast(nullptr, Errors); + return; + } + + this->pConnection->me().thenInMainThread([this](CesiumUtility::Result< + CesiumITwinClient:: + UserProfile>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Authorization finished but authorize async action is no longer valid.")); + return; + } + + if (!result.value) { + TArray Errors; + for (const std::string& error : result.errors.errors) { + Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); + } + + for (const std::string& warning : result.errors.warnings) { + Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); + } + + OnProfileResult.Broadcast(nullptr, Errors); + } else { + UCesiumITwinUserProfile* pProfile = NewObject(); + pProfile->SetProfile(std::move(*result.value)); + OnProfileResult.Broadcast(pProfile, TArray()); + } + }); +} + +UCesiumITwinAPIGetResourcesAsyncAction* +UCesiumITwinAPIGetResourcesAsyncAction::GetResources( + const UObject* WorldContextObject, + UCesiumITwinConnection* pConnection) { + UCesiumITwinAPIGetResourcesAsyncAction* pAsyncAction = + NewObject( + WorldContextObject->GetWorld()); + pAsyncAction->pConnection = pConnection->pConnection; + return pAsyncAction; +} + +void UCesiumITwinAPIGetResourcesAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnResourcesEvent.Broadcast( + EGetResourcesCallbackType::Failure, + TArray(), + 0, + 0, + Errors); + return; + } + + this->pConnection + ->listAllAvailableResources([this]( + const std::atomic_int& finished, + const std::atomic_int& total) { + AsyncTask(ENamedThreads::GameThread, [&finished, &total, this]() { + this->OnResourcesEvent.Broadcast( + EGetResourcesCallbackType::Status, + {}, + finished.load(), + total.load(), + {}); + }); + }) + .thenInMainThread([this](CesiumUtility::Result< + std::vector>&& + result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Authorization finished but authorize async action is no longer valid.")); + return; + } + + if (!result.value) { + TArray Errors; + for (const std::string& error : result.errors.errors) { + Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); + } + + for (const std::string& warning : result.errors.warnings) { + Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); + } + + this->OnResourcesEvent + .Broadcast(EGetResourcesCallbackType::Failure, {}, 0, 0, Errors); + } else { + TArray Resources; + Resources.Reserve(result.value->size()); + + for (const CesiumITwinClient::ITwinResource& resource : + *result.value) { + UCesiumITwinResource* pNewResource = + NewObject(GetWorld()); + pNewResource->SetResource(resource); + pNewResource->SetConnection(this->pConnection); + Resources.Push(pNewResource); + } + + this->OnResourcesEvent.Broadcast( + EGetResourcesCallbackType::Success, + Resources, + 0, + 0, + {}); + } + }); +} diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index f18f57950..45bcbf389 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -819,9 +819,11 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { FString IModelID; /** - * The ID of the iTwin Reality Data to use when interacting with the Reality Data service. + * The ID of the iTwin Reality Data to use when interacting with the Reality + * Data service. * - * This property is ignored if TilesetSource isn't set to "From iTwin Reality Data" + * This property is ignored if TilesetSource isn't set to "From iTwin Reality + * Data" */ UPROPERTY( EditAnywhere, diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h new file mode 100644 index 000000000..d3e24985e --- /dev/null +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -0,0 +1,271 @@ +// Copyright 2020-2025 CesiumGS, Inc. and Contributors + +#pragma once + +#include "Cesium3DTileset.h" +#include "CesiumGeoreference.h" +#include "Kismet/BlueprintAsyncActionBase.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Templates/SharedPointer.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" +THIRD_PARTY_INCLUDES_START +#include +THIRD_PARTY_INCLUDES_END + +#include "CesiumITwinAPIBlueprintLibrary.generated.h" + +UCLASS(BlueprintType) +class UCesiumITwinUserProfile : public UObject { + GENERATED_BODY() +public: + UCesiumITwinUserProfile() : UObject(), _profile() {} + + UCesiumITwinUserProfile(CesiumITwinClient::UserProfile&& profile) + : UObject(), _profile(std::move(profile)) {} + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_profile.id.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_profile.displayName.c_str()); + } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetGivenName() { return UTF8_TO_TCHAR(_profile.givenName.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetSurname() { return UTF8_TO_TCHAR(_profile.surname.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetEmail() { return UTF8_TO_TCHAR(_profile.email.c_str()); } + + void SetProfile(CesiumITwinClient::UserProfile&& profile) { + this->_profile = std::move(profile); + } + +private: + CesiumITwinClient::UserProfile _profile; +}; + +UCLASS(BlueprintType) +class UCesiumITwinConnection : public UObject { + GENERATED_BODY() +public: + UCesiumITwinConnection() : UObject(), pConnection(nullptr) {} + + UCesiumITwinConnection(TSharedPtr pConnection) + : UObject(), pConnection(MoveTemp(pConnection)) {} + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + bool IsValid() { return this->pConnection != nullptr; } + + void SetConnection(TSharedPtr pConnection) { + this->pConnection = pConnection; + } + + TSharedPtr pConnection; +}; + +UCLASS(BlueprintType) +class UCesiumITwinResource : public UObject { + GENERATED_BODY() +public: + UCesiumITwinResource() : UObject() {} + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_resource.id.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_resource.displayName.c_str()); + } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetSource() { + switch (_resource.source) { + case CesiumITwinClient::ResourceSource::CesiumCuratedContent: + return FString(TEXT("Cesium Curated Content")); + case CesiumITwinClient::ResourceSource::MeshExport: + return FString(TEXT("Mesh Export")); + case CesiumITwinClient::ResourceSource::RealityData: + return FString(TEXT("Reality Data")); + } + + return FString(TEXT("Unknown")); + } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetType() { + switch (_resource.resourceType) { + case CesiumITwinClient::ResourceType::Tileset: + return FString(TEXT("Tileset")); + case CesiumITwinClient::ResourceType::Imagery: + return FString(TEXT("Imagery")); + case CesiumITwinClient::ResourceType::Terrain: + return FString(TEXT("Terrain")); + } + + return FString(TEXT("Unknown")); + } + + UFUNCTION(BlueprintCallable, Category = "Cesium|iTwin") + void Spawn() { + if (_resource.resourceType == CesiumITwinClient::ResourceType::Imagery) { + // TODO + return; + } + + UWorld* pWorld = GetWorld(); + check(pWorld); + + ACesiumGeoreference* pParent = + ACesiumGeoreference::GetDefaultGeoreference(pWorld); + + check(pParent); + + ACesium3DTileset* pTileset = pWorld->SpawnActor(); + pTileset->AttachToActor( + pParent, + FAttachmentTransformRules::KeepRelativeTransform); + + pTileset->SetITwinAccessToken( + UTF8_TO_TCHAR(this->_pConnection->getAccessToken().getToken().c_str())); + + switch (_resource.source) { + case CesiumITwinClient::ResourceSource::CesiumCuratedContent: + pTileset->SetTilesetSource(ETilesetSource::FromITwinCesiumCuratedContent); + pTileset->SetITwinCesiumContentID(std::stoi(_resource.id)); + break; + case CesiumITwinClient::ResourceSource::MeshExport: + pTileset->SetTilesetSource(ETilesetSource::FromIModelMeshExportService); + pTileset->SetIModelID(UTF8_TO_TCHAR(_resource.id.c_str())); + break; + case CesiumITwinClient::ResourceSource::RealityData: + pTileset->SetTilesetSource(ETilesetSource::FromITwinRealityData); + pTileset->SetITwinID(UTF8_TO_TCHAR(_resource.parentId->c_str())); + pTileset->SetRealityDataID(UTF8_TO_TCHAR(_resource.parentId->c_str())); + break; + } + } + + void SetResource(const CesiumITwinClient::ITwinResource& resource) { + this->_resource = resource; + } + + void SetConnection(TSharedPtr pConnection) { + this->_pConnection = pConnection; + } + +private: + CesiumITwinClient::ITwinResource _resource; + TSharedPtr _pConnection; +}; + +UENUM(BlueprintType) +enum class ECesiumITwinDelegateType : uint8 { + Invalid = 0, + OpenUrl = 1, + Failure = 2, + Success = 3 +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams( + FCesiumITwinAuthorizationDelegate, + ECesiumITwinDelegateType, + Type, + const FString&, + Url, + UCesiumITwinConnection*, + Connection, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIAuthorizeAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() + +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIAuthorizeAsyncAction* + Authorize(const FString& ClientID); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinAuthorizationDelegate OnAuthorizationEvent; + + virtual void Activate() override; + +private: + FString _clientId; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FCesiumITwinGetProfileDelegate, + UCesiumITwinUserProfile*, + Profile, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetProfileAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetProfileAsyncAction* + GetProfile(UCesiumITwinConnection* pConnection); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinGetProfileDelegate OnProfileResult; + + virtual void Activate() override; + + TSharedPtr pConnection; +}; + +UENUM(BlueprintType) +enum class EGetResourcesCallbackType : uint8 { Status, Success, Failure }; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams( + FCesiumITwinGetResourcesDelegate, + EGetResourcesCallbackType, + Type, + const TArray&, + Resources, + int32, + FinishedRequests, + int32, + TotalRequests, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetResourcesAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = + (BlueprintInternalUseOnly = true, + WorldContext = "WorldContextObject")) + static UCesiumITwinAPIGetResourcesAsyncAction* GetResources( + const UObject* WorldContextObject, + UCesiumITwinConnection* pConnection); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinGetResourcesDelegate OnResourcesEvent; + + virtual void Activate() override; + + TSharedPtr pConnection; +}; diff --git a/extern/cesium-native b/extern/cesium-native index 0ffa23285..57db116c5 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 0ffa23285746779fab84bf7d008001a980a37256 +Subproject commit 57db116c5131114b711e91e32738776def72018d From 111480f12d6b00a546061b8467d350e9a40d5618 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 20 Mar 2025 10:38:14 -0400 Subject: [PATCH 05/15] Add raster overlay. --- .../CesiumITwinAPIBlueprintLibrary.cpp | 63 +++++++++++++++++++ ...ITwinCesiumCuratedContentRasterOverlay.cpp | 30 +++++++++ .../Public/CesiumITwinAPIBlueprintLibrary.h | 39 +----------- ...umITwinCesiumCuratedContentRasterOverlay.h | 37 +++++++++++ extern/cesium-native | 2 +- 5 files changed, 132 insertions(+), 39 deletions(-) create mode 100644 Source/CesiumRuntime/Private/CesiumITwinCesiumCuratedContentRasterOverlay.cpp create mode 100644 Source/CesiumRuntime/Public/CesiumITwinCesiumCuratedContentRasterOverlay.h diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp index 11865b5f0..143b6ec58 100644 --- a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -1,8 +1,71 @@ #include "CesiumITwinAPIBlueprintLibrary.h" +#include "CesiumITwinCesiumCuratedContentRasterOverlay.h" #include "CesiumRuntime.h" #include +void UCesiumITwinResource::Spawn() { + UWorld* pWorld = GetWorld(); + check(pWorld); + + ACesiumGeoreference* pParent = + ACesiumGeoreference::GetDefaultGeoreference(pWorld); + + check(pParent); + + if (_resource.resourceType == CesiumITwinClient::ResourceType::Imagery) { + // TODO: don't just attach imagery to the last tileset spawned! + TArray ChildActors; + pParent->GetAttachedActors(ChildActors); + for (auto It = ChildActors.rbegin(); It != ChildActors.rend(); ++It) { + ACesium3DTileset* pChildTileset = Cast(*It); + if (IsValid(pChildTileset) && + pChildTileset->GetTilesetSource() == + ETilesetSource::FromITwinCesiumCuratedContent) { + + UCesiumITwinCesiumCuratedContentRasterOverlay* pOverlay = + NewObject( + pChildTileset, + FName(TEXT("Overlay0")), + RF_Transactional); + pOverlay->MaterialLayerKey = "Overlay0"; + pOverlay->ITwinAccessToken = UTF8_TO_TCHAR( + this->_pConnection->getAccessToken().getToken().c_str()); + pOverlay->AssetID = std::stoi(_resource.id); + pOverlay->SetActive(true); + pOverlay->OnComponentCreated(); + pChildTileset->AddInstanceComponent(pOverlay); + break; + } + } + return; + } + + ACesium3DTileset* pTileset = pWorld->SpawnActor(); + pTileset->AttachToActor( + pParent, + FAttachmentTransformRules::KeepRelativeTransform); + + pTileset->SetITwinAccessToken( + UTF8_TO_TCHAR(this->_pConnection->getAccessToken().getToken().c_str())); + + switch (_resource.source) { + case CesiumITwinClient::ResourceSource::CesiumCuratedContent: + pTileset->SetTilesetSource(ETilesetSource::FromITwinCesiumCuratedContent); + pTileset->SetITwinCesiumContentID(std::stoi(_resource.id)); + break; + case CesiumITwinClient::ResourceSource::MeshExport: + pTileset->SetTilesetSource(ETilesetSource::FromIModelMeshExportService); + pTileset->SetIModelID(UTF8_TO_TCHAR(_resource.parentId->c_str())); + break; + case CesiumITwinClient::ResourceSource::RealityData: + pTileset->SetTilesetSource(ETilesetSource::FromITwinRealityData); + pTileset->SetITwinID(UTF8_TO_TCHAR(_resource.parentId->c_str())); + pTileset->SetRealityDataID(UTF8_TO_TCHAR(_resource.id.c_str())); + break; + } +} + UCesiumITwinAPIAuthorizeAsyncAction* UCesiumITwinAPIAuthorizeAsyncAction::Authorize(const FString& ClientID) { UCesiumITwinAPIAuthorizeAsyncAction* pAsyncAction = diff --git a/Source/CesiumRuntime/Private/CesiumITwinCesiumCuratedContentRasterOverlay.cpp b/Source/CesiumRuntime/Private/CesiumITwinCesiumCuratedContentRasterOverlay.cpp new file mode 100644 index 000000000..b0a2ad316 --- /dev/null +++ b/Source/CesiumRuntime/Private/CesiumITwinCesiumCuratedContentRasterOverlay.cpp @@ -0,0 +1,30 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#include "CesiumITwinCesiumCuratedContentRasterOverlay.h" +#include "Cesium3DTilesSelection/Tileset.h" +#include "CesiumActors.h" +#include "CesiumCustomVersion.h" +#include "CesiumIonServer.h" +#include "CesiumRasterOverlays/ITwinCesiumCuratedContentRasterOverlay.h" +#include "CesiumRuntime.h" +#include "CesiumRuntimeSettings.h" + +#if WITH_EDITOR +#include "FileHelpers.h" +#endif + +std::unique_ptr +UCesiumITwinCesiumCuratedContentRasterOverlay::CreateOverlay( + const CesiumRasterOverlays::RasterOverlayOptions& options) { + if (this->AssetID <= 0) { + // Don't create an overlay for an invalid asset ID. + return nullptr; + } + + return std::make_unique< + CesiumRasterOverlays::ITwinCesiumCuratedContentRasterOverlay>( + TCHAR_TO_UTF8(*this->MaterialLayerKey), + this->AssetID, + TCHAR_TO_UTF8(*this->ITwinAccessToken), + options); +} diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h index d3e24985e..bcd7ef5e1 100644 --- a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -111,44 +111,7 @@ class UCesiumITwinResource : public UObject { } UFUNCTION(BlueprintCallable, Category = "Cesium|iTwin") - void Spawn() { - if (_resource.resourceType == CesiumITwinClient::ResourceType::Imagery) { - // TODO - return; - } - - UWorld* pWorld = GetWorld(); - check(pWorld); - - ACesiumGeoreference* pParent = - ACesiumGeoreference::GetDefaultGeoreference(pWorld); - - check(pParent); - - ACesium3DTileset* pTileset = pWorld->SpawnActor(); - pTileset->AttachToActor( - pParent, - FAttachmentTransformRules::KeepRelativeTransform); - - pTileset->SetITwinAccessToken( - UTF8_TO_TCHAR(this->_pConnection->getAccessToken().getToken().c_str())); - - switch (_resource.source) { - case CesiumITwinClient::ResourceSource::CesiumCuratedContent: - pTileset->SetTilesetSource(ETilesetSource::FromITwinCesiumCuratedContent); - pTileset->SetITwinCesiumContentID(std::stoi(_resource.id)); - break; - case CesiumITwinClient::ResourceSource::MeshExport: - pTileset->SetTilesetSource(ETilesetSource::FromIModelMeshExportService); - pTileset->SetIModelID(UTF8_TO_TCHAR(_resource.id.c_str())); - break; - case CesiumITwinClient::ResourceSource::RealityData: - pTileset->SetTilesetSource(ETilesetSource::FromITwinRealityData); - pTileset->SetITwinID(UTF8_TO_TCHAR(_resource.parentId->c_str())); - pTileset->SetRealityDataID(UTF8_TO_TCHAR(_resource.parentId->c_str())); - break; - } - } + void Spawn(); void SetResource(const CesiumITwinClient::ITwinResource& resource) { this->_resource = resource; diff --git a/Source/CesiumRuntime/Public/CesiumITwinCesiumCuratedContentRasterOverlay.h b/Source/CesiumRuntime/Public/CesiumITwinCesiumCuratedContentRasterOverlay.h new file mode 100644 index 000000000..fb7a26eb9 --- /dev/null +++ b/Source/CesiumRuntime/Public/CesiumITwinCesiumCuratedContentRasterOverlay.h @@ -0,0 +1,37 @@ +// Copyright 2020-2024 CesiumGS, Inc. and Contributors + +#pragma once + +#include "CesiumRasterOverlay.h" +#include "CoreMinimal.h" +#include "CesiumITwinCesiumCuratedContentRasterOverlay.generated.h" + +/** + * A raster overlay that uses an IMAGERY asset from iTwin Cesium Curated + * Content. + */ +UCLASS( + DisplayName = "iTwin Cesium Curated Content Raster Overlay", + ClassGroup = (Cesium), + meta = (BlueprintSpawnableComponent)) +class CESIUMRUNTIME_API UCesiumITwinCesiumCuratedContentRasterOverlay + : public UCesiumRasterOverlay { + GENERATED_BODY() + +public: + /** + * The ID of the Cesium Curated Content asset to use. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cesium") + int64 AssetID; + + /** + * The access token to use to access the Cesium Curated Content resource. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cesium") + FString ITwinAccessToken; + +protected: + virtual std::unique_ptr CreateOverlay( + const CesiumRasterOverlays::RasterOverlayOptions& options = {}) override; +}; diff --git a/extern/cesium-native b/extern/cesium-native index 57db116c5..99f3b5010 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 57db116c5131114b711e91e32738776def72018d +Subproject commit 99f3b5010ae378ee9fc1b77e5d77d904bf631e33 From 4d3a81003546c6204695306694100d657da6855d Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 30 Apr 2025 15:02:28 -0400 Subject: [PATCH 06/15] Update cesium-native --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index 99f3b5010..0ac6ced84 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 99f3b5010ae378ee9fc1b77e5d77d904bf631e33 +Subproject commit 0ac6ced8456d008f9e865272b30c4137a3ea209a From 158c84c3c743c0edeb6be067a95aa4c2fd2a3ecd Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 30 Apr 2025 15:04:07 -0400 Subject: [PATCH 07/15] Fix missing include --- Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp index 143b6ec58..1c2de6234 100644 --- a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -1,5 +1,5 @@ #include "CesiumITwinAPIBlueprintLibrary.h" - +#include "Async/Async.h" #include "CesiumITwinCesiumCuratedContentRasterOverlay.h" #include "CesiumRuntime.h" #include From c4f213e2624ec6a606677fe7b62743650d4a6f84 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 30 Apr 2025 17:01:46 -0400 Subject: [PATCH 08/15] Remove ITwinResource stuff --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 4 + .../CesiumITwinAPIBlueprintLibrary.cpp | 146 ------------------ .../Public/CesiumITwinAPIBlueprintLibrary.h | 94 ----------- 3 files changed, 4 insertions(+), 240 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 80a0d7754..f6ada5821 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -9,6 +9,10 @@ #include "Cesium3DTilesSelection/TilesetLoadFailureDetails.h" #include "Cesium3DTilesSelection/TilesetOptions.h" #include "Cesium3DTilesSelection/TilesetSharedAssetSystem.h" +#include "Cesium3DTilesSelection/CesiumIonTilesetContentLoaderFactory.h" +#include "Cesium3DTilesSelection/IModelMeshExportContentLoaderFactory.h" +#include "Cesium3DTilesSelection/ITwinCesiumCuratedContentLoaderFactory.h" +#include "Cesium3DTilesSelection/ITwinRealityDataContentLoaderFactory.h" #include "Cesium3DTilesetLoadFailureDetails.h" #include "Cesium3DTilesetRoot.h" #include "CesiumActors.h" diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp index 1c2de6234..9d7a1773f 100644 --- a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -4,68 +4,6 @@ #include "CesiumRuntime.h" #include -void UCesiumITwinResource::Spawn() { - UWorld* pWorld = GetWorld(); - check(pWorld); - - ACesiumGeoreference* pParent = - ACesiumGeoreference::GetDefaultGeoreference(pWorld); - - check(pParent); - - if (_resource.resourceType == CesiumITwinClient::ResourceType::Imagery) { - // TODO: don't just attach imagery to the last tileset spawned! - TArray ChildActors; - pParent->GetAttachedActors(ChildActors); - for (auto It = ChildActors.rbegin(); It != ChildActors.rend(); ++It) { - ACesium3DTileset* pChildTileset = Cast(*It); - if (IsValid(pChildTileset) && - pChildTileset->GetTilesetSource() == - ETilesetSource::FromITwinCesiumCuratedContent) { - - UCesiumITwinCesiumCuratedContentRasterOverlay* pOverlay = - NewObject( - pChildTileset, - FName(TEXT("Overlay0")), - RF_Transactional); - pOverlay->MaterialLayerKey = "Overlay0"; - pOverlay->ITwinAccessToken = UTF8_TO_TCHAR( - this->_pConnection->getAccessToken().getToken().c_str()); - pOverlay->AssetID = std::stoi(_resource.id); - pOverlay->SetActive(true); - pOverlay->OnComponentCreated(); - pChildTileset->AddInstanceComponent(pOverlay); - break; - } - } - return; - } - - ACesium3DTileset* pTileset = pWorld->SpawnActor(); - pTileset->AttachToActor( - pParent, - FAttachmentTransformRules::KeepRelativeTransform); - - pTileset->SetITwinAccessToken( - UTF8_TO_TCHAR(this->_pConnection->getAccessToken().getToken().c_str())); - - switch (_resource.source) { - case CesiumITwinClient::ResourceSource::CesiumCuratedContent: - pTileset->SetTilesetSource(ETilesetSource::FromITwinCesiumCuratedContent); - pTileset->SetITwinCesiumContentID(std::stoi(_resource.id)); - break; - case CesiumITwinClient::ResourceSource::MeshExport: - pTileset->SetTilesetSource(ETilesetSource::FromIModelMeshExportService); - pTileset->SetIModelID(UTF8_TO_TCHAR(_resource.parentId->c_str())); - break; - case CesiumITwinClient::ResourceSource::RealityData: - pTileset->SetTilesetSource(ETilesetSource::FromITwinRealityData); - pTileset->SetITwinID(UTF8_TO_TCHAR(_resource.parentId->c_str())); - pTileset->SetRealityDataID(UTF8_TO_TCHAR(_resource.id.c_str())); - break; - } -} - UCesiumITwinAPIAuthorizeAsyncAction* UCesiumITwinAPIAuthorizeAsyncAction::Authorize(const FString& ClientID) { UCesiumITwinAPIAuthorizeAsyncAction* pAsyncAction = @@ -180,87 +118,3 @@ void UCesiumITwinAPIGetProfileAsyncAction::Activate() { } }); } - -UCesiumITwinAPIGetResourcesAsyncAction* -UCesiumITwinAPIGetResourcesAsyncAction::GetResources( - const UObject* WorldContextObject, - UCesiumITwinConnection* pConnection) { - UCesiumITwinAPIGetResourcesAsyncAction* pAsyncAction = - NewObject( - WorldContextObject->GetWorld()); - pAsyncAction->pConnection = pConnection->pConnection; - return pAsyncAction; -} - -void UCesiumITwinAPIGetResourcesAsyncAction::Activate() { - if (!this->pConnection) { - TArray Errors; - Errors.Push("No connection to iTwin."); - OnResourcesEvent.Broadcast( - EGetResourcesCallbackType::Failure, - TArray(), - 0, - 0, - Errors); - return; - } - - this->pConnection - ->listAllAvailableResources([this]( - const std::atomic_int& finished, - const std::atomic_int& total) { - AsyncTask(ENamedThreads::GameThread, [&finished, &total, this]() { - this->OnResourcesEvent.Broadcast( - EGetResourcesCallbackType::Status, - {}, - finished.load(), - total.load(), - {}); - }); - }) - .thenInMainThread([this](CesiumUtility::Result< - std::vector>&& - result) { - if (!IsValid(this)) { - UE_LOG( - LogCesium, - Warning, - TEXT( - "Authorization finished but authorize async action is no longer valid.")); - return; - } - - if (!result.value) { - TArray Errors; - for (const std::string& error : result.errors.errors) { - Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); - } - - for (const std::string& warning : result.errors.warnings) { - Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); - } - - this->OnResourcesEvent - .Broadcast(EGetResourcesCallbackType::Failure, {}, 0, 0, Errors); - } else { - TArray Resources; - Resources.Reserve(result.value->size()); - - for (const CesiumITwinClient::ITwinResource& resource : - *result.value) { - UCesiumITwinResource* pNewResource = - NewObject(GetWorld()); - pNewResource->SetResource(resource); - pNewResource->SetConnection(this->pConnection); - Resources.Push(pNewResource); - } - - this->OnResourcesEvent.Broadcast( - EGetResourcesCallbackType::Success, - Resources, - 0, - 0, - {}); - } - }); -} diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h index bcd7ef5e1..d0e866886 100644 --- a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -68,64 +68,6 @@ class UCesiumITwinConnection : public UObject { TSharedPtr pConnection; }; -UCLASS(BlueprintType) -class UCesiumITwinResource : public UObject { - GENERATED_BODY() -public: - UCesiumITwinResource() : UObject() {} - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetID() { return UTF8_TO_TCHAR(_resource.id.c_str()); } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetDisplayName() { - return UTF8_TO_TCHAR(_resource.displayName.c_str()); - } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetSource() { - switch (_resource.source) { - case CesiumITwinClient::ResourceSource::CesiumCuratedContent: - return FString(TEXT("Cesium Curated Content")); - case CesiumITwinClient::ResourceSource::MeshExport: - return FString(TEXT("Mesh Export")); - case CesiumITwinClient::ResourceSource::RealityData: - return FString(TEXT("Reality Data")); - } - - return FString(TEXT("Unknown")); - } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetType() { - switch (_resource.resourceType) { - case CesiumITwinClient::ResourceType::Tileset: - return FString(TEXT("Tileset")); - case CesiumITwinClient::ResourceType::Imagery: - return FString(TEXT("Imagery")); - case CesiumITwinClient::ResourceType::Terrain: - return FString(TEXT("Terrain")); - } - - return FString(TEXT("Unknown")); - } - - UFUNCTION(BlueprintCallable, Category = "Cesium|iTwin") - void Spawn(); - - void SetResource(const CesiumITwinClient::ITwinResource& resource) { - this->_resource = resource; - } - - void SetConnection(TSharedPtr pConnection) { - this->_pConnection = pConnection; - } - -private: - CesiumITwinClient::ITwinResource _resource; - TSharedPtr _pConnection; -}; - UENUM(BlueprintType) enum class ECesiumITwinDelegateType : uint8 { Invalid = 0, @@ -196,39 +138,3 @@ class CESIUMRUNTIME_API UCesiumITwinAPIGetProfileAsyncAction UENUM(BlueprintType) enum class EGetResourcesCallbackType : uint8 { Status, Success, Failure }; - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams( - FCesiumITwinGetResourcesDelegate, - EGetResourcesCallbackType, - Type, - const TArray&, - Resources, - int32, - FinishedRequests, - int32, - TotalRequests, - const TArray&, - Errors); - -UCLASS() -class CESIUMRUNTIME_API UCesiumITwinAPIGetResourcesAsyncAction - : public UBlueprintAsyncActionBase { - GENERATED_BODY() -public: - UFUNCTION( - BlueprintCallable, - Category = "Cesium|iTwin", - meta = - (BlueprintInternalUseOnly = true, - WorldContext = "WorldContextObject")) - static UCesiumITwinAPIGetResourcesAsyncAction* GetResources( - const UObject* WorldContextObject, - UCesiumITwinConnection* pConnection); - - UPROPERTY(BlueprintAssignable) - FCesiumITwinGetResourcesDelegate OnResourcesEvent; - - virtual void Activate() override; - - TSharedPtr pConnection; -}; From 14b1bb0574b211352e67d7cf19d578bbd6abe1a1 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 1 May 2025 11:15:22 -0400 Subject: [PATCH 09/15] Fix build with latest native --- Source/CesiumRuntime/Private/Cesium3DTileset.cpp | 5 ++++- extern/cesium-native | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 5d7921419..cd6649103 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -1228,7 +1228,10 @@ void ACesium3DTileset::LoadTileset() { this->ITwinID.IsEmpty() ? std::nullopt : std::make_optional( TCHAR_TO_UTF8(*this->ITwinID)), - TCHAR_TO_UTF8(*this->ITwinAccessToken)), + TCHAR_TO_UTF8(*this->ITwinAccessToken), + [asyncSystem](const std::string&) { + return asyncSystem.createResolvedFuture>(CesiumUtility::Result(std::string{})); + }), options); break; } diff --git a/extern/cesium-native b/extern/cesium-native index 288d9147a..0ac6ced84 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 288d9147a8ef4772564e0101deda643dac61b8bb +Subproject commit 0ac6ced8456d008f9e865272b30c4137a3ea209a From 5911836e86bc982643370a04d4ad28053970ccbf Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 13 May 2025 13:57:51 -0400 Subject: [PATCH 10/15] Implement token refresh --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 55 ++++++++++++++----- Source/CesiumRuntime/Public/Cesium3DTileset.h | 13 +++-- .../Public/CesiumITwinAPIBlueprintLibrary.h | 10 ++++ 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index cd6649103..28c597df3 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -4,15 +4,15 @@ #include "Async/Async.h" #include "Camera/CameraTypes.h" #include "Camera/PlayerCameraManager.h" +#include "Cesium3DTilesSelection/CesiumIonTilesetContentLoaderFactory.h" #include "Cesium3DTilesSelection/EllipsoidTilesetLoader.h" +#include "Cesium3DTilesSelection/IModelMeshExportContentLoaderFactory.h" +#include "Cesium3DTilesSelection/ITwinCesiumCuratedContentLoaderFactory.h" +#include "Cesium3DTilesSelection/ITwinRealityDataContentLoaderFactory.h" #include "Cesium3DTilesSelection/Tile.h" #include "Cesium3DTilesSelection/TilesetLoadFailureDetails.h" #include "Cesium3DTilesSelection/TilesetOptions.h" #include "Cesium3DTilesSelection/TilesetSharedAssetSystem.h" -#include "Cesium3DTilesSelection/CesiumIonTilesetContentLoaderFactory.h" -#include "Cesium3DTilesSelection/IModelMeshExportContentLoaderFactory.h" -#include "Cesium3DTilesSelection/ITwinCesiumCuratedContentLoaderFactory.h" -#include "Cesium3DTilesSelection/ITwinRealityDataContentLoaderFactory.h" #include "Cesium3DTilesetLoadFailureDetails.h" #include "Cesium3DTilesetRoot.h" #include "CesiumActors.h" @@ -473,14 +473,15 @@ void ACesium3DTileset::SetITwinID(const FString& InITwinID) { } } -void ACesium3DTileset::SetITwinAccessToken(const FString& InAccessToken) { - if (this->ITwinAccessToken != InAccessToken) { +void ACesium3DTileset::SetITwinConnection( + UCesiumITwinConnection* InConnection) { + if (this->ITwinConnection != InConnection) { if (this->TilesetSource == ETilesetSource::FromITwinCesiumCuratedContent || this->TilesetSource == ETilesetSource::FromIModelMeshExportService || this->TilesetSource == ETilesetSource::FromITwinRealityData) { this->DestroyTileset(); } - this->ITwinAccessToken = InAccessToken; + this->ITwinConnection = InConnection; } } @@ -1196,7 +1197,9 @@ void ACesium3DTileset::LoadTileset() { externals, Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory( static_cast(this->ITwinCesiumContentID), - TCHAR_TO_UTF8(*this->ITwinAccessToken)), + this->ITwinConnection->GetConnection() + ->getAccessToken() + .getToken()), options); break; case ETilesetSource::FromIModelMeshExportService: @@ -1211,7 +1214,9 @@ void ACesium3DTileset::LoadTileset() { Cesium3DTilesSelection::IModelMeshExportContentLoaderFactory( TCHAR_TO_UTF8(*this->IModelID), std::nullopt, - TCHAR_TO_UTF8(*this->ITwinAccessToken)), + this->ITwinConnection->GetConnection() + ->getAccessToken() + .getToken()), options); break; case ETilesetSource::FromITwinRealityData: @@ -1228,9 +1233,33 @@ void ACesium3DTileset::LoadTileset() { this->ITwinID.IsEmpty() ? std::nullopt : std::make_optional( TCHAR_TO_UTF8(*this->ITwinID)), - TCHAR_TO_UTF8(*this->ITwinAccessToken), - [asyncSystem](const std::string&) { - return asyncSystem.createResolvedFuture>(CesiumUtility::Result(std::string{})); + this->ITwinConnection->GetConnection()->getAccessToken().getToken(), + [asyncSystem, pConnection = this->ITwinConnection->GetConnection()]( + const std::string&) { + if (!pConnection->getRefreshToken()) { + return asyncSystem.createResolvedFuture< + CesiumUtility:: + Result>(CesiumUtility::ErrorList::error( + "Access token for reality data is expired, no refresh token available.")); + } + + return pConnection->me().thenImmediately( + [pConnection]( + CesiumUtility::Result&& + result) -> CesiumUtility::Result { + if (!result.value) { + return CesiumUtility::Result(result.errors); + } + + if (!pConnection->getAccessToken().isValid()) { + return CesiumUtility::Result< + std::string>(CesiumUtility::ErrorList::error( + "Tried to refresh access token for reality data, but was not able to obtain valid token.")); + } + + return CesiumUtility::Result( + pConnection->getAccessToken().getToken()); + }); }), options); break; @@ -2346,7 +2375,7 @@ void ACesium3DTileset::PostEditChangeProperty( PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, IModelID) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, RealityDataID) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinID) || - PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinAccessToken) || + PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, ITwinConnection) || PropName == GET_MEMBER_NAME_CHECKED(ACesium3DTileset, CreatePhysicsMeshes) || PropName == diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 28c3a8e80..7a51db8ab 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -10,6 +10,7 @@ #include "CesiumEncodedMetadataComponent.h" #include "CesiumFeaturesMetadataComponent.h" #include "CesiumGeoreference.h" +#include "CesiumITwinAPIBlueprintLibrary.h" #include "CesiumIonServer.h" #include "CesiumPointCloudShading.h" #include "CesiumSampleHeightResult.h" @@ -857,13 +858,13 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { */ UPROPERTY( EditAnywhere, - BlueprintGetter = GetITwinAccessToken, - BlueprintSetter = SetITwinAccessToken, + BlueprintGetter = GetITwinConnection, + BlueprintSetter = SetITwinConnection, Category = "Cesium", meta = (EditCondition = "TilesetSource==ETilesetSource::FromITwinCesiumCuratedContent || TilesetSource==ETilesetSource::FromIModelMeshExportService || TilesetSource==ETilesetSource::FromITwinRealityData")) - FString ITwinAccessToken; + UCesiumITwinConnection* ITwinConnection; /** * Headers to be attached to each request made for this tileset. @@ -1164,10 +1165,12 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { void SetITwinID(const FString& InITwinID); UFUNCTION(BlueprintGetter, Category = "Cesium") - FString GetITwinAccessToken() const { return ITwinAccessToken; } + UCesiumITwinConnection* GetITwinAccessToken() const { + return ITwinConnection; + } UFUNCTION(BlueprintSetter, Category = "Cesium") - void SetITwinAccessToken(const FString& InAccessToken); + void SetITwinConnection(UCesiumITwinConnection* InAccessToken); UFUNCTION(BlueprintGetter, Category = "VirtualTexture") TArray GetRuntimeVirtualTextures() const { diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h index d0e866886..ff57cd1aa 100644 --- a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -61,6 +61,16 @@ class UCesiumITwinConnection : public UObject { UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") bool IsValid() { return this->pConnection != nullptr; } + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetAccessToken() { + return UTF8_TO_TCHAR( + this->pConnection->getAccessToken().getToken().c_str()); + } + + TSharedPtr& GetConnection() { + return this->pConnection; + } + void SetConnection(TSharedPtr pConnection) { this->pConnection = pConnection; } From 5ece3c000dfcdeb0f0b69fddf4ab89af3f681bb5 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 13 May 2025 14:46:43 -0400 Subject: [PATCH 11/15] More implementation, use feature-overlay branch of native --- .../CesiumITwinAPIBlueprintLibrary.cpp | 6 +- .../Public/CesiumITwinAPIBlueprintLibrary.h | 143 +++++++++++++----- extern/cesium-native | 2 +- 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp index 9d7a1773f..3c3709ae7 100644 --- a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -23,7 +23,7 @@ void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { std::vector{"itwin-platform", "offline_access"}, [&Callback = this->OnAuthorizationEvent](const std::string& url) { Callback.Broadcast( - ECesiumITwinDelegateType::OpenUrl, + ECesiumITwinAuthorizationDelegateType::OpenUrl, UTF8_TO_TCHAR(url.c_str()), nullptr, TArray()); @@ -50,7 +50,7 @@ void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { } this->OnAuthorizationEvent.Broadcast( - ECesiumITwinDelegateType::Failure, + ECesiumITwinAuthorizationDelegateType::Failure, FString(), nullptr, Errors); @@ -62,7 +62,7 @@ void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { NewObject(); pConnection->SetConnection(pInternalConnection); this->OnAuthorizationEvent.Broadcast( - ECesiumITwinDelegateType::Success, + ECesiumITwinAuthorizationDelegateType::Success, FString(), pConnection, TArray()); diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h index ff57cd1aa..f93f04e86 100644 --- a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -15,40 +15,6 @@ THIRD_PARTY_INCLUDES_END #include "CesiumITwinAPIBlueprintLibrary.generated.h" -UCLASS(BlueprintType) -class UCesiumITwinUserProfile : public UObject { - GENERATED_BODY() -public: - UCesiumITwinUserProfile() : UObject(), _profile() {} - - UCesiumITwinUserProfile(CesiumITwinClient::UserProfile&& profile) - : UObject(), _profile(std::move(profile)) {} - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetID() { return UTF8_TO_TCHAR(_profile.id.c_str()); } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetDisplayName() { - return UTF8_TO_TCHAR(_profile.displayName.c_str()); - } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetGivenName() { return UTF8_TO_TCHAR(_profile.givenName.c_str()); } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetSurname() { return UTF8_TO_TCHAR(_profile.surname.c_str()); } - - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") - FString GetEmail() { return UTF8_TO_TCHAR(_profile.email.c_str()); } - - void SetProfile(CesiumITwinClient::UserProfile&& profile) { - this->_profile = std::move(profile); - } - -private: - CesiumITwinClient::UserProfile _profile; -}; - UCLASS(BlueprintType) class UCesiumITwinConnection : public UObject { GENERATED_BODY() @@ -79,7 +45,7 @@ class UCesiumITwinConnection : public UObject { }; UENUM(BlueprintType) -enum class ECesiumITwinDelegateType : uint8 { +enum class ECesiumITwinAuthorizationDelegateType : uint8 { Invalid = 0, OpenUrl = 1, Failure = 2, @@ -88,7 +54,7 @@ enum class ECesiumITwinDelegateType : uint8 { DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams( FCesiumITwinAuthorizationDelegate, - ECesiumITwinDelegateType, + ECesiumITwinAuthorizationDelegateType, Type, const FString&, Url, @@ -119,6 +85,40 @@ class CESIUMRUNTIME_API UCesiumITwinAPIAuthorizeAsyncAction FString _clientId; }; +UCLASS(BlueprintType) +class UCesiumITwinUserProfile : public UObject { + GENERATED_BODY() +public: + UCesiumITwinUserProfile() : UObject(), _profile() {} + + UCesiumITwinUserProfile(CesiumITwinClient::UserProfile&& profile) + : UObject(), _profile(std::move(profile)) {} + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_profile.id.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_profile.displayName.c_str()); + } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetGivenName() { return UTF8_TO_TCHAR(_profile.givenName.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetSurname() { return UTF8_TO_TCHAR(_profile.surname.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetEmail() { return UTF8_TO_TCHAR(_profile.email.c_str()); } + + void SetProfile(CesiumITwinClient::UserProfile&& profile) { + this->_profile = std::move(profile); + } + +private: + CesiumITwinClient::UserProfile _profile; +}; + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FCesiumITwinGetProfileDelegate, UCesiumITwinUserProfile*, @@ -147,4 +147,73 @@ class CESIUMRUNTIME_API UCesiumITwinAPIGetProfileAsyncAction }; UENUM(BlueprintType) -enum class EGetResourcesCallbackType : uint8 { Status, Success, Failure }; +enum class ECesiumITwinStatus : uint8 { + Unknown = 0, + Active = 1, + Inactive = 2, + Trial = 3 +}; + +UCLASS(BlueprintType) +class UCesiumITwin : public UObject { + GENERATED_BODY() +public: + UCesiumITwin() : UObject(), _iTwin() {} + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_iTwin.id.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetClass() { return UTF8_TO_TCHAR(_iTwin.iTwinClass.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetSubClass() { return UTF8_TO_TCHAR(_iTwin.subClass.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetType() { return UTF8_TO_TCHAR(_iTwin.type.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetNumber() { return UTF8_TO_TCHAR(_iTwin.number.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { return UTF8_TO_TCHAR(_iTwin.displayName.c_str()); } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumITwinStatus GetStatus() { return (ECesiumITwinStatus)_iTwin.status; } + + void SetITwin(CesiumITwinClient::ITwin&& iTwin) { + this->_iTwin = std::move(iTwin); + } + +private: + CesiumITwinClient::ITwin _iTwin; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FCesiumITwinListITwinsDelegate, + TArray, + ITwins, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetITwinsAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetITwinsAsyncAction* + GetITwins(UCesiumITwinConnection* pConnection); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListITwinsDelegate OnITwinsResult; + + virtual void Activate() override; + + TSharedPtr pConnection; +}; diff --git a/extern/cesium-native b/extern/cesium-native index 0ac6ced84..ba96e56c0 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 0ac6ced8456d008f9e865272b30c4137a3ea209a +Subproject commit ba96e56c09ba1eb858e3a7f05580e9069a3a9c6e From a154519757d11635128b6cce4d60628b11c55054 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 13 May 2025 15:54:03 -0400 Subject: [PATCH 12/15] IModel blueprint --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 14 +- .../Private/CesiumCartographicPolygon.cpp | 2 +- .../CesiumITwinAPIBlueprintLibrary.cpp | 157 ++++++++++++++--- Source/CesiumRuntime/Public/Cesium3DTileset.h | 4 +- .../Public/CesiumITwinAPIBlueprintLibrary.h | 159 +++++++++++++++++- extern/cesium-native | 2 +- 6 files changed, 296 insertions(+), 42 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 28c597df3..bc2dd4605 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -1197,9 +1197,7 @@ void ACesium3DTileset::LoadTileset() { externals, Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory( static_cast(this->ITwinCesiumContentID), - this->ITwinConnection->GetConnection() - ->getAccessToken() - .getToken()), + this->ITwinConnection->GetConnection()->getAuthToken().getToken()), options); break; case ETilesetSource::FromIModelMeshExportService: @@ -1214,9 +1212,7 @@ void ACesium3DTileset::LoadTileset() { Cesium3DTilesSelection::IModelMeshExportContentLoaderFactory( TCHAR_TO_UTF8(*this->IModelID), std::nullopt, - this->ITwinConnection->GetConnection() - ->getAccessToken() - .getToken()), + this->ITwinConnection->GetConnection()->getAuthToken().getToken()), options); break; case ETilesetSource::FromITwinRealityData: @@ -1233,7 +1229,7 @@ void ACesium3DTileset::LoadTileset() { this->ITwinID.IsEmpty() ? std::nullopt : std::make_optional( TCHAR_TO_UTF8(*this->ITwinID)), - this->ITwinConnection->GetConnection()->getAccessToken().getToken(), + this->ITwinConnection->GetConnection()->getAuthToken().getToken(), [asyncSystem, pConnection = this->ITwinConnection->GetConnection()]( const std::string&) { if (!pConnection->getRefreshToken()) { @@ -1251,14 +1247,14 @@ void ACesium3DTileset::LoadTileset() { return CesiumUtility::Result(result.errors); } - if (!pConnection->getAccessToken().isValid()) { + if (!pConnection->getAuthToken().isValid()) { return CesiumUtility::Result< std::string>(CesiumUtility::ErrorList::error( "Tried to refresh access token for reality data, but was not able to obtain valid token.")); } return CesiumUtility::Result( - pConnection->getAccessToken().getToken()); + pConnection->getAuthToken().getToken()); }); }), options); diff --git a/Source/CesiumRuntime/Private/CesiumCartographicPolygon.cpp b/Source/CesiumRuntime/Private/CesiumCartographicPolygon.cpp index 7f6027d7e..c107c6589 100644 --- a/Source/CesiumRuntime/Private/CesiumCartographicPolygon.cpp +++ b/Source/CesiumRuntime/Private/CesiumCartographicPolygon.cpp @@ -48,7 +48,7 @@ ACesiumCartographicPolygon::CreateCartographicPolygon( int32 splinePointsCount = this->Polygon->GetNumberOfSplinePoints(); if (splinePointsCount < 3) { - return CartographicPolygon({}); + return CartographicPolygon(std::vector{}); } std::vector polygon(splinePointsCount); diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp index 3c3709ae7..e99394feb 100644 --- a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -2,8 +2,24 @@ #include "Async/Async.h" #include "CesiumITwinCesiumCuratedContentRasterOverlay.h" #include "CesiumRuntime.h" +#include #include +namespace { +TArray errorListToArray(const CesiumUtility::ErrorList& errors) { + TArray Errors; + for (const std::string& error : errors.errors) { + Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); + } + + for (const std::string& warning : errors.warnings) { + Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); + } + + return Errors; +} +} // namespace + UCesiumITwinAPIAuthorizeAsyncAction* UCesiumITwinAPIAuthorizeAsyncAction::Authorize(const FString& ClientID) { UCesiumITwinAPIAuthorizeAsyncAction* pAsyncAction = @@ -40,20 +56,11 @@ void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { } if (!connection.value) { - TArray Errors; - for (const std::string& error : connection.errors.errors) { - Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); - } - - for (const std::string& warning : connection.errors.warnings) { - Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); - } - this->OnAuthorizationEvent.Broadcast( ECesiumITwinAuthorizationDelegateType::Failure, FString(), nullptr, - Errors); + errorListToArray(connection.errors)); } else { TSharedPtr pInternalConnection = MakeShared( @@ -96,21 +103,12 @@ void UCesiumITwinAPIGetProfileAsyncAction::Activate() { LogCesium, Warning, TEXT( - "Authorization finished but authorize async action is no longer valid.")); + "Get profile finished but get profile async action is no longer valid.")); return; } if (!result.value) { - TArray Errors; - for (const std::string& error : result.errors.errors) { - Errors.Emplace(UTF8_TO_TCHAR(error.c_str())); - } - - for (const std::string& warning : result.errors.warnings) { - Errors.Emplace(UTF8_TO_TCHAR(warning.c_str())); - } - - OnProfileResult.Broadcast(nullptr, Errors); + OnProfileResult.Broadcast(nullptr, errorListToArray(result.errors)); } else { UCesiumITwinUserProfile* pProfile = NewObject(); pProfile->SetProfile(std::move(*result.value)); @@ -118,3 +116,120 @@ void UCesiumITwinAPIGetProfileAsyncAction::Activate() { } }); } + +const int PageSize = 50; + +UCesiumITwinAPIGetITwinsAsyncAction* +UCesiumITwinAPIGetITwinsAsyncAction::GetITwins( + UCesiumITwinConnection* pConnection, + int Page) { + UCesiumITwinAPIGetITwinsAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + return pAsyncAction; +} + +void UCesiumITwinAPIGetITwinsAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnITwinsResult.Broadcast(TArray(), false, Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->itwins(params).thenInMainThread( + [this](CesiumUtility::Result< + CesiumITwinClient::PagedList>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get itwins finished but get itwins async action is no longer valid.")); + return; + } + + if (!result.value) { + OnITwinsResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray iTwins; + iTwins.Reserve(result.value->size()); + for (CesiumITwinClient::ITwin& iTwin : *result.value) { + UCesiumITwin* pITwin = NewObject(); + pITwin->SetITwin(MoveTemp(iTwin)); + iTwins.Emplace(pITwin); + } + OnITwinsResult.Broadcast( + iTwins, + result.value->hasNextUrl(), + TArray()); + } + }); +} + +UCesiumITwinAPIGetIModelsAsyncAction* +UCesiumITwinAPIGetIModelsAsyncAction::GetIModels( + UCesiumITwinConnection* pConnection, + const FString& iTwinId, + int Page) { + UCesiumITwinAPIGetIModelsAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + pAsyncAction->iTwinId = iTwinId; + return pAsyncAction; +} + +void UCesiumITwinAPIGetIModelsAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnIModelsResult.Broadcast(TArray(), false, Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->imodels(TCHAR_TO_UTF8(*this->iTwinId), params) + .thenInMainThread([this]( + CesiumUtility::Result>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get itwins finished but get itwins async action is no longer valid.")); + return; + } + + if (!result.value) { + OnIModelsResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray iModels; + iModels.Reserve(result.value->size()); + for (CesiumITwinClient::IModel& iModel : *result.value) { + UCesiumIModel* pIModel = NewObject(); + pIModel->SetIModel(MoveTemp(iModel)); + iModels.Emplace(pIModel); + } + OnIModelsResult.Broadcast( + iModels, + result.value->hasNextUrl(), + TArray()); + } + }); +} diff --git a/Source/CesiumRuntime/Public/Cesium3DTileset.h b/Source/CesiumRuntime/Public/Cesium3DTileset.h index 7a51db8ab..23436c82c 100644 --- a/Source/CesiumRuntime/Public/Cesium3DTileset.h +++ b/Source/CesiumRuntime/Public/Cesium3DTileset.h @@ -1165,9 +1165,7 @@ class CESIUMRUNTIME_API ACesium3DTileset : public AActor { void SetITwinID(const FString& InITwinID); UFUNCTION(BlueprintGetter, Category = "Cesium") - UCesiumITwinConnection* GetITwinAccessToken() const { - return ITwinConnection; - } + UCesiumITwinConnection* GetITwinConnection() const { return ITwinConnection; } UFUNCTION(BlueprintSetter, Category = "Cesium") void SetITwinConnection(UCesiumITwinConnection* InAccessToken); diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h index f93f04e86..6a1193b10 100644 --- a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -2,7 +2,6 @@ #pragma once -#include "Cesium3DTileset.h" #include "CesiumGeoreference.h" #include "Kismet/BlueprintAsyncActionBase.h" #include "Kismet/BlueprintFunctionLibrary.h" @@ -29,16 +28,15 @@ class UCesiumITwinConnection : public UObject { UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetAccessToken() { - return UTF8_TO_TCHAR( - this->pConnection->getAccessToken().getToken().c_str()); + return UTF8_TO_TCHAR(this->pConnection->getAuthToken().getToken().c_str()); } TSharedPtr& GetConnection() { return this->pConnection; } - void SetConnection(TSharedPtr pConnection) { - this->pConnection = pConnection; + void SetConnection(TSharedPtr pConnection_) { + this->pConnection = pConnection_; } TSharedPtr pConnection; @@ -154,30 +152,61 @@ enum class ECesiumITwinStatus : uint8 { Trial = 3 }; +/** + * @brief Information on a single iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/operations/get-my-itwins/#itwin-summary + * for more information. + */ UCLASS(BlueprintType) class UCesiumITwin : public UObject { GENERATED_BODY() public: UCesiumITwin() : UObject(), _iTwin() {} + /** @brief The iTwin Id. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetID() { return UTF8_TO_TCHAR(_iTwin.id.c_str()); } + /** + * @brief The `Class` of your iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses + * for more information. + */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetClass() { return UTF8_TO_TCHAR(_iTwin.iTwinClass.c_str()); } + /** + * @brief The `subClass` of your iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses + * for more information. + */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetSubClass() { return UTF8_TO_TCHAR(_iTwin.subClass.c_str()); } + /** @brief An open ended property to better define your iTwin's Type. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetType() { return UTF8_TO_TCHAR(_iTwin.type.c_str()); } + /** + * @brief A unique number or code for the iTwin. + * + * This is the value that uniquely identifies the iTwin within your + * organization. + */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetNumber() { return UTF8_TO_TCHAR(_iTwin.number.c_str()); } + /** @brief A display name for the iTwin. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetDisplayName() { return UTF8_TO_TCHAR(_iTwin.displayName.c_str()); } + /** @brief The status of the iTwin. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") ECesiumITwinStatus GetStatus() { return (ECesiumITwinStatus)_iTwin.status; } @@ -189,7 +218,7 @@ class UCesiumITwin : public UObject { CesiumITwinClient::ITwin _iTwin; }; -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( FCesiumITwinListITwinsDelegate, TArray, ITwins, @@ -208,7 +237,7 @@ class CESIUMRUNTIME_API UCesiumITwinAPIGetITwinsAsyncAction Category = "Cesium|iTwin", meta = (BlueprintInternalUseOnly = true)) static UCesiumITwinAPIGetITwinsAsyncAction* - GetITwins(UCesiumITwinConnection* pConnection); + GetITwins(UCesiumITwinConnection* pConnection, int Page); UPROPERTY(BlueprintAssignable) FCesiumITwinListITwinsDelegate OnITwinsResult; @@ -216,4 +245,120 @@ class CESIUMRUNTIME_API UCesiumITwinAPIGetITwinsAsyncAction virtual void Activate() override; TSharedPtr pConnection; + int page; +}; + +UENUM(BlueprintType) +enum class ECesiumIModelState : uint8 { + Unknown = 0, + Initialized = 1, + NotInitialized = 2 +}; + +/** + * @brief An iModel. + * + * See + * https://developer.bentley.com/apis/imodels-v2/operations/get-imodel-details/#imodel + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumIModel : public UObject { + GENERATED_BODY() +public: + UCesiumIModel() : UObject(), _iModel() {} + + /** @brief The iTwin Id. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_iModel.id.c_str()); } + + /** @brief A display name for the iTwin. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_iModel.displayName.c_str()); + } + + /** + * @brief The `Class` of your iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses + * for more information. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetName() { return UTF8_TO_TCHAR(_iModel.name.c_str()); } + + /** + * @brief The `subClass` of your iTwin. + * + * See + * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses + * for more information. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDescription() { + return UTF8_TO_TCHAR(_iModel.description.c_str()); + } + + /** @brief The status of the iTwin. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumIModelState GetState() { return (ECesiumIModelState)_iModel.state; } + + /** @brief The status of the iTwin. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FBox GetExtent() { + const CesiumGeospatial::Cartographic& southWest = + _iModel.extent.getSouthwest(); + const CesiumGeospatial::Cartographic& northEast = + _iModel.extent.getNortheast(); + return FBox( + FVector( + CesiumUtility::Math::radiansToDegrees(southWest.longitude), + CesiumUtility::Math::radiansToDegrees(southWest.latitude), + southWest.height), + FVector( + CesiumUtility::Math::radiansToDegrees(northEast.longitude), + CesiumUtility::Math::radiansToDegrees(northEast.latitude), + northEast.height)); + } + + void SetIModel(CesiumITwinClient::IModel&& iModel) { + this->_iModel = std::move(iModel); + } + +private: + CesiumITwinClient::IModel _iModel; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FCesiumITwinListIModelsDelegate, + TArray, + IModels, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetIModelsAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetIModelsAsyncAction* GetIModels( + UCesiumITwinConnection* pConnection, + const FString& ITwinId, + int Page); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListIModelsDelegate OnIModelsResult; + + virtual void Activate() override; + + TSharedPtr pConnection; + int page; + FString iTwinId; }; diff --git a/extern/cesium-native b/extern/cesium-native index ba96e56c0..75c025aab 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit ba96e56c09ba1eb858e3a7f05580e9069a3a9c6e +Subproject commit 75c025aabb2576fdb4cf5a5e789fad1a36d7ec9a From dd8adc28a72bb27a315408c49d8f356f1c5e1103 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 14 May 2025 11:59:25 -0400 Subject: [PATCH 13/15] Main iTwin APIs in Cesium for Unreal --- .../CesiumITwinAPIBlueprintLibrary.cpp | 183 +++++++- .../Public/CesiumITwinAPIBlueprintLibrary.h | 432 +++++++++++++++++- extern/cesium-native | 2 +- 3 files changed, 597 insertions(+), 20 deletions(-) diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp index e99394feb..50552c632 100644 --- a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -209,7 +209,7 @@ void UCesiumITwinAPIGetIModelsAsyncAction::Activate() { LogCesium, Warning, TEXT( - "Get itwins finished but get itwins async action is no longer valid.")); + "Get imodels finished but get imodels async action is no longer valid.")); return; } @@ -233,3 +233,184 @@ void UCesiumITwinAPIGetIModelsAsyncAction::Activate() { } }); } + +UCesiumITwinAPIGetIModelMeshExportsAsyncAction* +UCesiumITwinAPIGetIModelMeshExportsAsyncAction::GetIModelMeshExports( + UCesiumITwinConnection* pConnection, + const FString& iModelId, + int Page) { + UCesiumITwinAPIGetIModelMeshExportsAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + pAsyncAction->iModelId = iModelId; + return pAsyncAction; +} + +void UCesiumITwinAPIGetIModelMeshExportsAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnIModelMeshExportsResult.Broadcast( + TArray(), + false, + Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->meshExports(TCHAR_TO_UTF8(*this->iModelId), params) + .thenInMainThread([this]( + CesiumUtility::Result>&& + result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get imodel mesh exports finished but get imodel mesh exports async action is no longer valid.")); + return; + } + + if (!result.value) { + OnIModelMeshExportsResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray iModels; + iModels.Reserve(result.value->size()); + for (CesiumITwinClient::IModelMeshExport& iModel : *result.value) { + UCesiumIModelMeshExport* pIModel = + NewObject(); + pIModel->SetIModelMeshExport(MoveTemp(iModel)); + iModels.Emplace(pIModel); + } + OnIModelMeshExportsResult.Broadcast( + iModels, + result.value->hasNextUrl(), + TArray()); + } + }); +} + +UCesiumITwinAPIGetRealityDataAsyncAction* +UCesiumITwinAPIGetRealityDataAsyncAction::GetITwinRealityData( + UCesiumITwinConnection* pConnection, + const FString& iTwinId, + int Page) { + UCesiumITwinAPIGetRealityDataAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + pAsyncAction->page = FMath::Max(Page, 1); + pAsyncAction->iTwinId = iTwinId; + return pAsyncAction; +} + +void UCesiumITwinAPIGetRealityDataAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnITwinRealityDataResult.Broadcast( + TArray(), + false, + Errors); + return; + } + + CesiumITwinClient::QueryParameters params; + params.top = PageSize; + params.skip = PageSize * (this->page - 1); + + this->pConnection->realityData(TCHAR_TO_UTF8(*this->iTwinId), params) + .thenInMainThread([this]( + CesiumUtility::Result>&& + result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get reality data finished but get reality data async action is no longer valid.")); + return; + } + + if (!result.value) { + OnITwinRealityDataResult.Broadcast( + TArray(), + false, + errorListToArray(result.errors)); + } else { + TArray realityDataObjects; + realityDataObjects.Reserve(result.value->size()); + for (CesiumITwinClient::ITwinRealityData& realityData : + *result.value) { + UCesiumITwinRealityData* pRealityData = + NewObject(); + pRealityData->SetITwinRealityData(MoveTemp(realityData)); + realityDataObjects.Emplace(pRealityData); + } + OnITwinRealityDataResult.Broadcast( + realityDataObjects, + result.value->hasNextUrl(), + TArray()); + } + }); +} + +UCesiumITwinAPIListCesiumCuratedContentAsyncAction* +UCesiumITwinAPIListCesiumCuratedContentAsyncAction:: + GetCesiumCuratedContentAssets(UCesiumITwinConnection* pConnection) { + UCesiumITwinAPIListCesiumCuratedContentAsyncAction* pAsyncAction = + NewObject(); + pAsyncAction->pConnection = pConnection->pConnection; + return pAsyncAction; +} + +void UCesiumITwinAPIListCesiumCuratedContentAsyncAction::Activate() { + if (!this->pConnection) { + TArray Errors; + Errors.Push("No connection to iTwin."); + OnListCesiumCuratedContentDelegate.Broadcast( + TArray(), + Errors); + return; + } + + this->pConnection->cesiumCuratedContent().thenInMainThread( + [this](CesiumUtility::Result>&& result) { + if (!IsValid(this)) { + UE_LOG( + LogCesium, + Warning, + TEXT( + "Get cesium curated content finished but get cesium curated content async action is no longer valid.")); + return; + } + + if (!result.value) { + OnListCesiumCuratedContentDelegate.Broadcast( + TArray(), + errorListToArray(result.errors)); + } else { + TArray assets; + assets.Reserve(result.value->size()); + for (CesiumITwinClient::CesiumCuratedContentAsset& asset : + *result.value) { + UCesiumCuratedContentAsset* pAsset = + NewObject(); + pAsset->SetCesiumCuratedContentAsset(MoveTemp(asset)); + assets.Emplace(pAsset); + } + OnListCesiumCuratedContentDelegate.Broadcast( + assets, + TArray()); + } + }); +} diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h index 6a1193b10..f46a0789d 100644 --- a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -268,43 +268,33 @@ class UCesiumIModel : public UObject { public: UCesiumIModel() : UObject(), _iModel() {} - /** @brief The iTwin Id. */ + /** @brief The iModel Id. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetID() { return UTF8_TO_TCHAR(_iModel.id.c_str()); } - /** @brief A display name for the iTwin. */ + /** @brief Display name of the iModel. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetDisplayName() { return UTF8_TO_TCHAR(_iModel.displayName.c_str()); } - /** - * @brief The `Class` of your iTwin. - * - * See - * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses - * for more information. - */ + /** @brief Name of the iModel. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetName() { return UTF8_TO_TCHAR(_iModel.name.c_str()); } - /** - * @brief The `subClass` of your iTwin. - * - * See - * https://developer.bentley.com/apis/itwins/overview/#itwin-classes-and-subclasses - * for more information. - */ + /** @brief Description of the iModel. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetDescription() { return UTF8_TO_TCHAR(_iModel.description.c_str()); } - /** @brief The status of the iTwin. */ + /** @brief Indicates the state of the iModel. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") ECesiumIModelState GetState() { return (ECesiumIModelState)_iModel.state; } - /** @brief The status of the iTwin. */ + /** + * @brief The maximum rectangular area on the Earth which encloses the iModel. + */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FBox GetExtent() { const CesiumGeospatial::Cartographic& southWest = @@ -362,3 +352,409 @@ class CESIUMRUNTIME_API UCesiumITwinAPIGetIModelsAsyncAction int page; FString iTwinId; }; + +/** + * @brief The status of an iModel Mesh Export. + * + * See + * https://developer.bentley.com/apis/mesh-export/operations/get-export/#exportstatus + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumIModelMeshExportStatus : uint8 { + Unknown = 0, + NotStarted = 1, + InProgress = 2, + Complete = 3, + Invalid = 4 +}; + +/** + * @brief The type of mesh exported. + * + * See + * https://developer.bentley.com/apis/mesh-export/operations/get-export/#startexport-exporttype + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumIModelMeshExportType : uint8 { + Unknown = 0, + /** + * @brief iTwin "3D Fast Transmission" (3DFT) format. + */ + ITwin3DFT = 1, + IModel = 2, + Cesium = 3, + Cesium3DTiles = 4, +}; + +/** + * @brief An iModel mesh export. + * + * See + * https://developer.bentley.com/apis/mesh-export/operations/get-export/#export + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumIModelMeshExport : public UObject { + GENERATED_BODY() +public: + UCesiumIModelMeshExport() : UObject(), _meshExport() {} + + /** @brief ID of the export request. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_meshExport.id.c_str()); } + + /** @brief Name of the exported iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_meshExport.displayName.c_str()); + } + + /** @brief The status of the export job. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumIModelMeshExportStatus GetState() { + return (ECesiumIModelMeshExportStatus)_meshExport.status; + } + + /** @brief The status of the export job. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumIModelMeshExportType GetExportType() { + return (ECesiumIModelMeshExportType)_meshExport.exportType; + } + + void SetIModelMeshExport(CesiumITwinClient::IModelMeshExport&& iModel) { + this->_meshExport = std::move(iModel); + } + +private: + CesiumITwinClient::IModelMeshExport _meshExport; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FCesiumITwinListIModelMeshExportsDelegate, + TArray, + MeshExports, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetIModelMeshExportsAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetIModelMeshExportsAsyncAction* GetIModelMeshExports( + UCesiumITwinConnection* pConnection, + const FString& iModelId, + int Page); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListIModelMeshExportsDelegate OnIModelMeshExportsResult; + + virtual void Activate() override; + + TSharedPtr pConnection; + int page; + FString iModelId; +}; + +/** + * @brief Indicates the nature of the reality data. + * + * See + * https://developer.bentley.com/apis/reality-management/rm-rd-details/#classification + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumITwinRealityDataClassification : uint8 { + Unknown = 0, + Terrain = 1, + Imagery = 2, + Pinned = 3, + Model = 4, + Undefined = 5 +}; + +/** + * @brief Information on reality data. + * + * See + * https://developer.bentley.com/apis/reality-management/operations/get-all-reality-data/#reality-data-metadata + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumITwinRealityData : public UObject { + GENERATED_BODY() +public: + UCesiumITwinRealityData() : UObject(), _realityData() {} + + /** + * @brief Identifier of the reality data. + * + * This identifier is assigned by the service at the creation of the reality + * data. It is also unique. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return UTF8_TO_TCHAR(_realityData.id.c_str()); } + + /** + * @brief The name of the reality data. + * + * This property may not contain any control sequence such as a URL or code. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDisplayName() { + return UTF8_TO_TCHAR(_realityData.displayName.c_str()); + } + + /** + * @brief A textual description of the reality data. + * + * This property may not contain any control sequence such as a URL or code. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDescription() { + return UTF8_TO_TCHAR(_realityData.description.c_str()); + } + + /** + * @brief Specific value constrained field that indicates the nature of the + * reality data. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumITwinRealityDataClassification GetClassification() { + return (ECesiumITwinRealityDataClassification)_realityData.classification; + } + + /** + * @brief A key indicating the format of the data. + * + * The type property should be a specific indication of the format of the + * reality data. Given a type, the consuming software should be able to + * determine if it has the capacity to open the reality data. Although the + * type field is a free string some specific values are reserved and other + * values should be selected judiciously. Look at the documentation for an + * exhaustive list of reserved reality data types. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetType() { return UTF8_TO_TCHAR(_realityData.type.c_str()); } + + /** + * @brief Contains the rectangular area on the Earth which encloses the + * reality data. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FBox GetExtent() { + const CesiumGeospatial::Cartographic& southWest = + _realityData.extent.getSouthwest(); + const CesiumGeospatial::Cartographic& northEast = + _realityData.extent.getNortheast(); + return FBox( + FVector( + CesiumUtility::Math::radiansToDegrees(southWest.longitude), + CesiumUtility::Math::radiansToDegrees(southWest.latitude), + southWest.height), + FVector( + CesiumUtility::Math::radiansToDegrees(northEast.longitude), + CesiumUtility::Math::radiansToDegrees(northEast.latitude), + northEast.height)); + } + + /** + * @brief A boolean value that is true if the data is being created. It is + * false if the data has been completely uploaded. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + bool GetAuthoring() { return this->_realityData.authoring; } + + void SetITwinRealityData(CesiumITwinClient::ITwinRealityData&& realityData) { + this->_realityData = std::move(realityData); + } + +private: + CesiumITwinClient::ITwinRealityData _realityData; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( + FCesiumITwinListRealityDataDelegate, + TArray, + RealityData, + bool, + HasAnotherPage, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIGetRealityDataAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIGetRealityDataAsyncAction* GetITwinRealityData( + UCesiumITwinConnection* pConnection, + const FString& iTwinId, + int Page); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListRealityDataDelegate OnITwinRealityDataResult; + + virtual void Activate() override; + + TSharedPtr pConnection; + int page; + FString iTwinId; +}; + +/** + * @brief The type of content obtained from the iTwin Cesium Curated Content + * API. + * + * See + * https://developer.bentley.com/apis/cesium-curated-content/operations/list-content/#contenttype + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumCuratedContentType : uint8 { + /** @brief The content type returned is not a known type. */ + Unknown = 0, + /** @brief The content is a 3D Tiles tileset. */ + Cesium3DTiles = 1, + /** @brief The content is a glTF model. */ + Gltf = 2, + /** + * @brief The content is imagery that can be loaded with \ref + * CesiumRasterOverlays::ITwinCesiumCuratedContentRasterOverlay. + */ + Imagery = 3, + /** @brief The content is quantized mesh terrain. */ + Terrain = 4, + /** @brief The content is in the Keyhole Markup Language (KML) format. */ + Kml = 5, + /** + * @brief The content is in the Cesium Language (CZML) format. + * + * See https://github.com/AnalyticalGraphicsInc/czml-writer/wiki/CZML-Guide + * for more information. + */ + Czml = 6, + /** @brief The content is in the GeoJSON format. */ + GeoJson = 7, +}; + +/** + * @brief Describes the state of the content during the upload and tiling + * processes. + * + * See + * https://developer.bentley.com/apis/cesium-curated-content/operations/list-content/#contentstatus + * for more information. + */ +UENUM(BlueprintType) +enum class ECesiumCuratedContentStatus : uint8 { + Unknown = 0, + AwaitingFiles = 1, + NotStarted = 2, + InProgress = 3, + Complete = 4, + Error = 5, + DataError = 6 +}; + +/** + * @brief A single asset obtained from the iTwin Cesium Curated Content API. + * + * See + * https://developer.bentley.com/apis/cesium-curated-content/operations/list-content/#content + * for more information. + */ +UCLASS(BlueprintType) +class UCesiumCuratedContentAsset : public UObject { + GENERATED_BODY() +public: + UCesiumCuratedContentAsset() : UObject(), _asset() {} + + /** + * @brief The unique identifier for the content. + * + * @remark The value returned from the API is a `uint64`, which Unreal does + * not support in Blueprint. As this value is only used as a unique idenitifer + * and not a numeric value, it is converted to a string for use in Blueprint. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetID() { return FString::Printf(TEXT("%llu"), _asset.id); } + + /** @brief The type of the content. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumCuratedContentType GetType() { + return (ECesiumCuratedContentType)_asset.type; + } + + /** @brief Name of the exported iModel. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetName() { return UTF8_TO_TCHAR(_asset.name.c_str()); } + + /** @brief A Markdown string describing the content. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetDescription() { return UTF8_TO_TCHAR(_asset.description.c_str()); } + + /** + * @brief A Markdown compatible string containing any required attribution for + * the content. + * + * Clients will be required to display the attribution to end users. + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + FString GetAttribution() { return UTF8_TO_TCHAR(_asset.attribution.c_str()); } + + /** @brief The status of the content. */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") + ECesiumCuratedContentStatus GetState() { + return (ECesiumCuratedContentStatus)_asset.status; + } + + void SetCesiumCuratedContentAsset( + CesiumITwinClient::CesiumCuratedContentAsset&& asset) { + this->_asset = std::move(asset); + } + +private: + CesiumITwinClient::CesiumCuratedContentAsset _asset; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( + FCesiumITwinListCesiumCuratedContentDelegate, + TArray, + Assets, + const TArray&, + Errors); + +UCLASS() +class CESIUMRUNTIME_API UCesiumITwinAPIListCesiumCuratedContentAsyncAction + : public UBlueprintAsyncActionBase { + GENERATED_BODY() +public: + UFUNCTION( + BlueprintCallable, + Category = "Cesium|iTwin", + meta = (BlueprintInternalUseOnly = true)) + static UCesiumITwinAPIListCesiumCuratedContentAsyncAction* + GetCesiumCuratedContentAssets(UCesiumITwinConnection* pConnection); + + UPROPERTY(BlueprintAssignable) + FCesiumITwinListCesiumCuratedContentDelegate + OnListCesiumCuratedContentDelegate; + + virtual void Activate() override; + + TSharedPtr pConnection; +}; diff --git a/extern/cesium-native b/extern/cesium-native index 75c025aab..db4121144 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit 75c025aabb2576fdb4cf5a5e789fad1a36d7ec9a +Subproject commit db4121144849dfd3fb538ce128a8463084d82dd1 From e33520d45579ef69efe9d4d0c4099a4754a11eb6 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 14 May 2025 15:22:31 -0400 Subject: [PATCH 14/15] Update cesium-native --- extern/cesium-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cesium-native b/extern/cesium-native index db4121144..740fe4bf7 160000 --- a/extern/cesium-native +++ b/extern/cesium-native @@ -1 +1 @@ -Subproject commit db4121144849dfd3fb538ce128a8463084d82dd1 +Subproject commit 740fe4bf7fef0e7c22a4e56ba4b9cf41f6853c08 From 2a9c2c38be95259c6caa473f88595cca3dc1fd1d Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 12 Jun 2025 14:32:49 -0400 Subject: [PATCH 15/15] Fix crashes --- .../CesiumRuntime/Private/Cesium3DTileset.cpp | 131 +++++++++++------- .../CesiumITwinAPIBlueprintLibrary.cpp | 27 ++-- .../Public/CesiumITwinAPIBlueprintLibrary.h | 3 +- 3 files changed, 100 insertions(+), 61 deletions(-) diff --git a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp index 4bdc5e57f..a4e3654a2 100644 --- a/Source/CesiumRuntime/Private/Cesium3DTileset.cpp +++ b/Source/CesiumRuntime/Private/Cesium3DTileset.cpp @@ -1196,12 +1196,19 @@ void ACesium3DTileset::LoadTileset() { TEXT("Loading tileset for asset ID %d"), this->ITwinCesiumContentID); - this->_pTileset = MakeUnique( - externals, - Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory( - static_cast(this->ITwinCesiumContentID), - this->ITwinConnection->GetConnection()->getAuthToken().getToken()), - options); + { + const std::string token = IsValid(this->ITwinConnection) + ? this->ITwinConnection->GetConnection() + ->getAuthenticationToken() + .getToken() + : ""; + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::ITwinCesiumCuratedContentLoaderFactory( + static_cast(this->ITwinCesiumContentID), + token), + options); + } break; case ETilesetSource::FromIModelMeshExportService: UE_LOG( @@ -1210,13 +1217,20 @@ void ACesium3DTileset::LoadTileset() { TEXT("Loading mesh export for iModel ID %s"), *this->IModelID); - this->_pTileset = MakeUnique( - externals, - Cesium3DTilesSelection::IModelMeshExportContentLoaderFactory( - TCHAR_TO_UTF8(*this->IModelID), - std::nullopt, - this->ITwinConnection->GetConnection()->getAuthToken().getToken()), - options); + { + const std::string token = IsValid(this->ITwinConnection) + ? this->ITwinConnection->GetConnection() + ->getAuthenticationToken() + .getToken() + : ""; + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::IModelMeshExportContentLoaderFactory( + TCHAR_TO_UTF8(*this->IModelID), + std::nullopt, + token), + options); + } break; case ETilesetSource::FromITwinRealityData: UE_LOG( @@ -1225,42 +1239,61 @@ void ACesium3DTileset::LoadTileset() { TEXT("Loading reality data ID %s"), *this->RealityDataID); - this->_pTileset = MakeUnique( - externals, - Cesium3DTilesSelection::ITwinRealityDataContentLoaderFactory( - TCHAR_TO_UTF8(*this->RealityDataID), - this->ITwinID.IsEmpty() ? std::nullopt - : std::make_optional( - TCHAR_TO_UTF8(*this->ITwinID)), - this->ITwinConnection->GetConnection()->getAuthToken().getToken(), - [asyncSystem, pConnection = this->ITwinConnection->GetConnection()]( - const std::string&) { - if (!pConnection->getRefreshToken()) { - return asyncSystem.createResolvedFuture< - CesiumUtility:: - Result>(CesiumUtility::ErrorList::error( - "Access token for reality data is expired, no refresh token available.")); - } - - return pConnection->me().thenImmediately( - [pConnection]( - CesiumUtility::Result&& - result) -> CesiumUtility::Result { - if (!result.value) { - return CesiumUtility::Result(result.errors); - } - - if (!pConnection->getAuthToken().isValid()) { - return CesiumUtility::Result< - std::string>(CesiumUtility::ErrorList::error( - "Tried to refresh access token for reality data, but was not able to obtain valid token.")); - } - - return CesiumUtility::Result( - pConnection->getAuthToken().getToken()); - }); - }), - options); + { + const std::string token = IsValid(this->ITwinConnection) + ? this->ITwinConnection->GetConnection() + ->getAuthenticationToken() + .getToken() + : ""; + + this->_pTileset = MakeUnique( + externals, + Cesium3DTilesSelection::ITwinRealityDataContentLoaderFactory( + TCHAR_TO_UTF8(*this->RealityDataID), + this->ITwinID.IsEmpty() ? std::nullopt + : std::make_optional( + TCHAR_TO_UTF8(*this->ITwinID)), + token, + [asyncSystem, + pConnectionObject = this->ITwinConnection](const std::string&) { + if (!IsValid(pConnectionObject) || + !pConnectionObject->GetConnection().IsValid()) { + return asyncSystem.createResolvedFuture< + CesiumUtility:: + Result>(CesiumUtility::ErrorList::error( + "iTwin connection property on tileset is not valid.")); + } + + const TSharedPtr pConnection = + pConnectionObject->GetConnection(); + if (!pConnection->getRefreshToken()) { + return asyncSystem.createResolvedFuture< + CesiumUtility:: + Result>(CesiumUtility::ErrorList::error( + "Access token for reality data is expired, no refresh token available.")); + } + + return pConnection->me().thenImmediately( + [pConnection]( + CesiumUtility::Result&& + result) -> CesiumUtility::Result { + if (!result.value) { + return CesiumUtility::Result( + result.errors); + } + + if (!pConnection->getAuthenticationToken().isValid()) { + return CesiumUtility::Result< + std::string>(CesiumUtility::ErrorList::error( + "Tried to refresh access token for reality data, but was not able to obtain valid token.")); + } + + return CesiumUtility::Result( + pConnection->getAuthenticationToken().getToken()); + }); + }), + options); + } break; } diff --git a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp index 50552c632..441e3f3fe 100644 --- a/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp +++ b/Source/CesiumRuntime/Private/CesiumITwinAPIBlueprintLibrary.cpp @@ -56,11 +56,14 @@ void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { } if (!connection.value) { - this->OnAuthorizationEvent.Broadcast( - ECesiumITwinAuthorizationDelegateType::Failure, - FString(), - nullptr, - errorListToArray(connection.errors)); + AsyncTask(ENamedThreads::GameThread, [connection, this]() { + this->OnAuthorizationEvent.Broadcast( + ECesiumITwinAuthorizationDelegateType::Failure, + FString(), + nullptr, + errorListToArray(connection.errors)); + this->SetReadyToDestroy(); + }); } else { TSharedPtr pInternalConnection = MakeShared( @@ -68,12 +71,14 @@ void UCesiumITwinAPIAuthorizeAsyncAction::Activate() { UCesiumITwinConnection* pConnection = NewObject(); pConnection->SetConnection(pInternalConnection); - this->OnAuthorizationEvent.Broadcast( - ECesiumITwinAuthorizationDelegateType::Success, - FString(), - pConnection, - TArray()); - this->SetReadyToDestroy(); + AsyncTask(ENamedThreads::GameThread, [pConnection, this]() { + this->OnAuthorizationEvent.Broadcast( + ECesiumITwinAuthorizationDelegateType::Success, + FString(), + pConnection, + TArray()); + this->SetReadyToDestroy(); + }); } }); } diff --git a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h index f46a0789d..e21b26787 100644 --- a/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h +++ b/Source/CesiumRuntime/Public/CesiumITwinAPIBlueprintLibrary.h @@ -28,7 +28,8 @@ class UCesiumITwinConnection : public UObject { UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Cesium|iTwin") FString GetAccessToken() { - return UTF8_TO_TCHAR(this->pConnection->getAuthToken().getToken().c_str()); + return UTF8_TO_TCHAR( + this->pConnection->getAuthenticationToken().getToken().c_str()); } TSharedPtr& GetConnection() {