();
+
+ // For small number of pages, show all pages
+ if (totalPages <= 5)
+ {
+ for (int i = 0; i < totalPages; i++)
+ pages.Add(i);
+ return pages;
+ }
+
+ // Always show first page
+ pages.Add(0);
+
+ // For first page
+ if (currentPage == 0)
+ {
+ pages.Add(1); // Show page 2
+ pages.Add(-1); // Ellipsis
+ pages.Add(totalPages - 1); // Last page
+ }
+ // For second page
+ else if (currentPage == 1)
+ {
+ pages.Add(1); // Page 2 (current)
+ pages.Add(2); // Page 3
+ pages.Add(-1); // Ellipsis
+ pages.Add(totalPages - 1); // Last page
+ }
+ // For third page
+ else if (currentPage == 2)
+ {
+ pages.Add(1); // Page 2
+ pages.Add(2); // Page 3 (current)
+ pages.Add(3); // Page 4
+ pages.Add(-1); // Ellipsis
+ pages.Add(totalPages - 1); // Last page
+ }
+ // For middle pages
+ else if (currentPage > 2 && currentPage < totalPages - 3)
+ {
+ pages.Add(-1); // Ellipsis
+ pages.Add(currentPage - 1); // Previous page
+ pages.Add(currentPage); // Current page
+ pages.Add(currentPage + 1); // Next page
+ pages.Add(-1); // Ellipsis
+ pages.Add(totalPages - 1); // Last page
+ }
+ // For third-to-last page
+ else if (currentPage == totalPages - 3)
+ {
+ pages.Add(-1); // Ellipsis
+ pages.Add(totalPages - 4); // Page before current
+ pages.Add(totalPages - 3); // Current page
+ pages.Add(totalPages - 2); // Page after current
+ pages.Add(totalPages - 1); // Last page
+ }
+ // For second-to-last page
+ else if (currentPage == totalPages - 2)
+ {
+ pages.Add(-1); // Ellipsis
+ pages.Add(totalPages - 3); // Page before current
+ pages.Add(totalPages - 2); // Current page
+ pages.Add(totalPages - 1); // Last page
+ }
+ // For last page
+ else if (currentPage == totalPages - 1)
+ {
+ pages.Add(-1); // Ellipsis
+ pages.Add(totalPages - 2); // Page before current
+ pages.Add(totalPages - 1); // Current page (last page)
+ }
+
+ return pages;
+ }
+}
diff --git a/Editor/iTwinForUnity/Utils/TilesetsUIUtils.cs.meta b/Editor/iTwinForUnity/Utils/TilesetsUIUtils.cs.meta
new file mode 100644
index 00000000..11a81ab2
--- /dev/null
+++ b/Editor/iTwinForUnity/Utils/TilesetsUIUtils.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 03e65a19eb8f9ad45ade377ecf0b8084
\ No newline at end of file
diff --git a/Runtime/iTwinForUnity.meta b/Runtime/iTwinForUnity.meta
new file mode 100644
index 00000000..4ba62339
--- /dev/null
+++ b/Runtime/iTwinForUnity.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fbb8948436d4df64bbf39f62c002c1cf
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/iTwinForUnity/BentleyTilesetMetadata.cs b/Runtime/iTwinForUnity/BentleyTilesetMetadata.cs
new file mode 100644
index 00000000..a873169e
--- /dev/null
+++ b/Runtime/iTwinForUnity/BentleyTilesetMetadata.cs
@@ -0,0 +1,133 @@
+using UnityEngine;
+using System;
+
+///
+/// Stores Bentley iTwin metadata associated with a Cesium3DTileset
+///
+[DisallowMultipleComponent]
+[RequireComponent(typeof(CesiumForUnity.Cesium3DTileset))]
+public class BentleyTilesetMetadata : MonoBehaviour
+{
+ // Public accessible fields (visible in Inspector)
+ [SerializeField] private string _iTwinId = string.Empty;
+ [SerializeField] private string _iModelId = string.Empty;
+ [SerializeField] private string _iModelName = string.Empty;
+ [SerializeField] private string _iModelDescription = string.Empty;
+ [SerializeField] private string _exportDate = string.Empty;
+
+ // Private fields (for internal use)
+ [SerializeField] private string _changesetId = string.Empty;
+ [SerializeField] private string _iTwinName = string.Empty;
+ [SerializeField] private string _changesetVersion = string.Empty;
+ [SerializeField] private string _changesetDescription = string.Empty;
+ [SerializeField] private string _changesetCreatedDate = string.Empty;
+ [SerializeField] private string _exportUrl = string.Empty;
+ // Thumbnail storage
+ [SerializeField] private byte[] _iTwinThumbnailBytes = null;
+ [SerializeField] private byte[] _iModelThumbnailBytes = null;
+
+ // Public properties (accessible to scripts)
+ ///
+ /// The ID of the iTwin project this tileset was exported from
+ ///
+ public string iTwinId => _iTwinId;
+
+ ///
+ /// The ID of the iModel this tileset was exported from
+ ///
+ public string iModelId => _iModelId;
+
+ ///
+ /// The name of the iModel this tileset was exported from
+ ///
+ public string iModelName => _iModelName;
+
+ ///
+ /// The description of the iModel this tileset was exported from
+ ///
+ public string iModelDescription => _iModelDescription;
+
+ ///
+ /// The date when this tileset was exported
+ ///
+ public string exportDate => _exportDate;
+
+ ///
+ /// The ID of the changeset used for this export (empty string if latest)
+ ///
+ public string changesetId => _changesetId;
+
+ ///
+ /// Sets basic metadata values
+ ///
+ public void SetMetadata(string iTwinId, string iModelId, string iModelName, string iModelDescription, string changesetId = "")
+ {
+ _iTwinId = iTwinId;
+ _iModelId = iModelId;
+ _iModelName = iModelName;
+ _iModelDescription = iModelDescription;
+ _changesetId = changesetId;
+ _exportDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+ }
+
+ ///
+ /// Sets extended metadata including thumbnails and additional details
+ ///
+ public void SetExtendedMetadata(
+ string iTwinId,
+ string iTwinName,
+ string iModelId,
+ string iModelName,
+ string iModelDescription,
+ string changesetId,
+ string changesetVersion,
+ string changesetDescription,
+ string changesetCreatedDate,
+ string exportUrl,
+ Texture2D iTwinThumbnail = null,
+ Texture2D iModelThumbnail = null)
+ {
+ // Set basic metadata
+ SetMetadata(iTwinId, iModelId, iModelName, iModelDescription, changesetId);
+
+ // Set extended metadata
+ _iTwinName = iTwinName ?? string.Empty;
+ _changesetVersion = changesetVersion ?? string.Empty;
+ _changesetDescription = changesetDescription ?? string.Empty;
+ _changesetCreatedDate = changesetCreatedDate ?? string.Empty;
+ _exportUrl = exportUrl ?? string.Empty;
+
+ // Store thumbnails
+ if (iTwinThumbnail != null)
+ _iTwinThumbnailBytes = iTwinThumbnail.EncodeToPNG();
+
+ if (iModelThumbnail != null)
+ _iModelThumbnailBytes = iModelThumbnail.EncodeToPNG();
+ }
+
+ ///
+ /// Gets the iTwin thumbnail if available
+ ///
+ public Texture2D GetITwinThumbnail()
+ {
+ if (_iTwinThumbnailBytes == null || _iTwinThumbnailBytes.Length == 0)
+ return null;
+
+ var tex = new Texture2D(2, 2);
+ tex.LoadImage(_iTwinThumbnailBytes);
+ return tex;
+ }
+
+ ///
+ /// Gets the iModel thumbnail if available
+ ///
+ public Texture2D GetIModelThumbnail()
+ {
+ if (_iModelThumbnailBytes == null || _iModelThumbnailBytes.Length == 0)
+ return null;
+
+ var tex = new Texture2D(2, 2);
+ tex.LoadImage(_iModelThumbnailBytes);
+ return tex;
+ }
+}
\ No newline at end of file
diff --git a/Runtime/iTwinForUnity/BentleyTilesetMetadata.cs.meta b/Runtime/iTwinForUnity/BentleyTilesetMetadata.cs.meta
new file mode 100644
index 00000000..288ae8ac
--- /dev/null
+++ b/Runtime/iTwinForUnity/BentleyTilesetMetadata.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 75101e59a3a44ee4f92cf8111ef5bf0c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 9d9689eab72c8480c90679f4dcf18820, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/iTwinForUnity/Constants.meta b/Runtime/iTwinForUnity/Constants.meta
new file mode 100644
index 00000000..434c5566
--- /dev/null
+++ b/Runtime/iTwinForUnity/Constants.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e6d5fd93768206440b070be5ff049aa3
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/iTwinForUnity/Constants/AuthConstants.cs b/Runtime/iTwinForUnity/Constants/AuthConstants.cs
new file mode 100644
index 00000000..e8a9ffc3
--- /dev/null
+++ b/Runtime/iTwinForUnity/Constants/AuthConstants.cs
@@ -0,0 +1,30 @@
+using UnityEngine;
+
+///
+/// Centralized constants and configuration for Bentley authentication system
+///
+public static class AuthConstants
+{
+ // OAuth Configuration
+ public const string DEFAULT_REDIRECT_URI = "http://localhost:58789/";
+ public const string DEFAULT_SCOPES = "itwin-platform";
+ public const string MESH_EXPORT_SCOPE = "mesh-export";
+ public const string GENERAL_SCOPE = "itwin-platform";
+ public const string AUTHORIZATION_URL = "https://ims.bentley.com/as/authorization.oauth2";
+ public const string TOKEN_URL = "https://ims.bentley.com/as/token.oauth2";
+
+ // EditorPrefs Keys
+ public const string PREF_ACCESS_TOKEN = "Bentley_Editor_AccessToken";
+ public const string PREF_REFRESH_TOKEN = "Bentley_Editor_RefreshToken";
+ public const string PREF_EXPIRY = "Bentley_Editor_TokenExpiry";
+ public const string PREF_CLIENT_ID = "Bentley_Editor_ClientId";
+ public const string PREF_REDIRECT_URI = "Bentley_Editor_RedirectUri";
+
+ // Timing Constants
+ public const int TOKEN_EXPIRY_BUFFER_MINUTES = 1;
+
+ // HTTP Response Messages
+ public const string SUCCESS_RESPONSE_HTML = @"Authentication Successful
You can close this window now.
";
+
+ public const string ERROR_RESPONSE_HTML = @"Authentication Failed
Please close this window and try again.
";
+}
diff --git a/Runtime/iTwinForUnity/Constants/AuthConstants.cs.meta b/Runtime/iTwinForUnity/Constants/AuthConstants.cs.meta
new file mode 100644
index 00000000..403ae07a
--- /dev/null
+++ b/Runtime/iTwinForUnity/Constants/AuthConstants.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: ec2fe8e0512c8c9418a8be55e3395d07
\ No newline at end of file
diff --git a/Runtime/iTwinForUnity/Constants/TilesetConstants.cs b/Runtime/iTwinForUnity/Constants/TilesetConstants.cs
new file mode 100644
index 00000000..ec556577
--- /dev/null
+++ b/Runtime/iTwinForUnity/Constants/TilesetConstants.cs
@@ -0,0 +1,30 @@
+using UnityEngine;
+
+public static class TilesetConstants
+{
+ // Color definitions
+ public static readonly Color HeaderBgColor = new Color(0.15f, 0.15f, 0.15f);
+ public static readonly Color CardBgColor = new Color(0.22f, 0.22f, 0.22f);
+ public static readonly Color CardBgHoverColor = new Color(0.26f, 0.26f, 0.26f);
+ public static readonly Color AccentColor = new Color(0.2f, 0.6f, 0.9f);
+ public static readonly Color PrimaryTextColor = new Color(0.9f, 0.9f, 0.9f);
+ public static readonly Color SecondaryTextColor = new Color(0.7f, 0.7f, 0.7f);
+
+ // UI dimensions
+ public const int ITEMS_PER_PAGE = 3;
+ public const int THUMBNAIL_SIZE = 80;
+ public const int CARD_PADDING = 12;
+ public const int SEARCH_BOX_HEIGHT = 28;
+ public const int BUTTON_HEIGHT = 24;
+
+ // Animation timing
+ public const float SPINNER_FRAME_RATE = 0.15f;
+ public const int MAX_DESCRIPTION_LENGTH = 200;
+ public const int MAX_ID_DISPLAY_LENGTH = 12;
+
+ // Spinner frames
+ public static readonly string[] SpinnerFrames = new string[] { "◐", "◓", "◑", "◒" };
+
+ // Search and keyboard shortcuts
+ public const string SEARCH_CONTROL_NAME = "TilesetSearchField";
+}
diff --git a/Runtime/iTwinForUnity/Constants/TilesetConstants.cs.meta b/Runtime/iTwinForUnity/Constants/TilesetConstants.cs.meta
new file mode 100644
index 00000000..d9770948
--- /dev/null
+++ b/Runtime/iTwinForUnity/Constants/TilesetConstants.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 260ad3528f808b34293c0b69618e7391
\ No newline at end of file
diff --git a/Runtime/iTwinForUnity/DataModels.meta b/Runtime/iTwinForUnity/DataModels.meta
new file mode 100644
index 00000000..ebd8752d
--- /dev/null
+++ b/Runtime/iTwinForUnity/DataModels.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2865ce9ce90123f459c12f6db664497f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/iTwinForUnity/DataModels/BentleyDataModels.cs b/Runtime/iTwinForUnity/DataModels/BentleyDataModels.cs
new file mode 100644
index 00000000..8c37c851
--- /dev/null
+++ b/Runtime/iTwinForUnity/DataModels/BentleyDataModels.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+///
+/// Represents an iTwin project from Bentley's iTwin platform.
+/// An iTwin is a digital twin infrastructure project that can contain multiple iModels.
+///
+[Serializable]
+public class ITwin
+{
+ ///
+ /// Unique identifier for the iTwin project
+ ///
+ public string id;
+
+ ///
+ /// Human-readable display name of the iTwin project
+ ///
+ public string displayName;
+
+ ///
+ /// Thumbnail image for the iTwin project (loaded asynchronously)
+ ///
+ [NonSerialized] public Texture2D thumbnail;
+
+ ///
+ /// Indicates whether the thumbnail is currently being loaded
+ ///
+ [NonSerialized] public bool loadingThumbnail;
+
+ ///
+ /// Flag to track if thumbnail loading has been attempted (regardless of success)
+ ///
+ [NonSerialized] public bool thumbnailLoaded = false;
+}
+
+///
+/// Represents an iModel within an iTwin project.
+/// An iModel is a specific data model that contains 3D data and can have multiple changesets.
+///
+[Serializable]
+public class IModel
+{
+ ///
+ /// Unique identifier for the iModel
+ ///
+ public string id;
+
+ ///
+ /// Human-readable display name of the iModel
+ ///
+ public string displayName;
+
+ ///
+ /// Detailed description of the iModel (loaded asynchronously)
+ ///
+ public string description;
+
+ ///
+ /// Thumbnail image for the iModel (loaded asynchronously)
+ ///
+ [NonSerialized] public Texture2D thumbnail;
+
+ ///
+ /// Indicates whether the thumbnail is currently being loaded
+ ///
+ [NonSerialized] public bool loadingThumbnail;
+
+ ///
+ /// Indicates whether the detailed information is currently being loaded
+ ///
+ [NonSerialized] public bool loadingDetails;
+
+ ///
+ /// Flag to track if detail loading has been attempted (regardless of success)
+ ///
+ [NonSerialized] public bool detailsLoaded = false;
+
+ ///
+ /// Flag to track if thumbnail loading has been attempted (regardless of success)
+ ///
+ [NonSerialized] public bool thumbnailLoaded = false;
+
+ ///
+ /// Indicates whether the changesets are currently being loaded
+ ///
+ [NonSerialized] public bool loadingChangesets;
+
+ ///
+ /// Collection of changesets associated with this iModel (loaded asynchronously)
+ ///
+ [NonSerialized] public List changesets = new List();
+
+ ///
+ /// Index of the currently selected changeset (0 = latest/newest)
+ ///
+ [NonSerialized] public int selectedChangesetIndex = 0;
+}
+
+///
+/// Represents a changeset within an iModel.
+/// A changeset is a version of the iModel data at a specific point in time.
+///
+[Serializable]
+public class ChangeSet
+{
+ ///
+ /// Unique identifier for the changeset
+ ///
+ public string id;
+
+ ///
+ /// Description of the changes made in this changeset
+ ///
+ public string description;
+
+ ///
+ /// Version string identifying this changeset
+ ///
+ public string version;
+
+ ///
+ /// Date and time when this changeset was created
+ ///
+ public DateTime createdDate;
+}
+
+///
+/// Response wrapper for API calls that return multiple iTwin projects
+///
+[Serializable]
+public class ITwinsResponse
+{
+ ///
+ /// Collection of iTwin projects returned by the API
+ ///
+ public List iTwins;
+}
+
+///
+/// Response wrapper for API calls that return multiple iModels
+///
+[Serializable]
+public class IModelsResponse
+{
+ ///
+ /// Collection of iModels returned by the API
+ ///
+ public List iModels;
+}
+
+///
+/// Response wrapper for API calls that return detailed information about a single iModel
+///
+[Serializable]
+public class IModelDetailsResponse
+{
+ ///
+ /// Detailed iModel information
+ ///
+ public IModelDetail iModel;
+
+ ///
+ /// Detailed iModel information structure matching the API response format
+ ///
+ [Serializable]
+ public class IModelDetail
+ {
+ ///
+ /// Unique identifier for the iModel
+ ///
+ public string id;
+
+ ///
+ /// Human-readable display name of the iModel
+ ///
+ public string displayName;
+
+ ///
+ /// Detailed description of the iModel
+ ///
+ public string description;
+ }
+}
+
+///
+/// Response wrapper for API calls that return multiple changesets
+///
+[Serializable]
+public class ChangeSetsResponse
+{
+ ///
+ /// Collection of changesets returned by the API
+ ///
+ public List changesets;
+}
diff --git a/Runtime/iTwinForUnity/DataModels/BentleyDataModels.cs.meta b/Runtime/iTwinForUnity/DataModels/BentleyDataModels.cs.meta
new file mode 100644
index 00000000..dfdb7ce1
--- /dev/null
+++ b/Runtime/iTwinForUnity/DataModels/BentleyDataModels.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 278b8aa05a867c54b98d9414e6af0b2a
\ No newline at end of file
diff --git a/Runtime/iTwinForUnity/MeshExport.meta b/Runtime/iTwinForUnity/MeshExport.meta
new file mode 100644
index 00000000..5490638d
--- /dev/null
+++ b/Runtime/iTwinForUnity/MeshExport.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a3d751d80f574f143b4e5db03cdb934c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/iTwinForUnity/MeshExport/MeshExportClient.cs b/Runtime/iTwinForUnity/MeshExport/MeshExportClient.cs
new file mode 100644
index 00000000..078bdcdb
--- /dev/null
+++ b/Runtime/iTwinForUnity/MeshExport/MeshExportClient.cs
@@ -0,0 +1,353 @@
+using UnityEngine;
+using UnityEngine.Networking;
+using System;
+using System.Collections;
+using System.Text;
+using Newtonsoft.Json;
+
+///
+/// Primary client for orchestrating mesh export workflows from Bentley's iTwin platform.
+/// Handles export initiation, status monitoring, progress tracking, and file download coordination.
+/// All methods return Unity coroutines for asynchronous execution in the Editor environment.
+///
+public class MeshExportClient
+{
+ ///
+ /// Base URL for Bentley's mesh export API endpoints
+ ///
+ private const string BaseUrl = "https://api.bentley.com/mesh-export/";
+
+ ///
+ /// Request payload for initiating a new mesh export operation
+ ///
+ [Serializable]
+ public class StartExportRequest
+ {
+ ///
+ /// Unique identifier of the iModel to export
+ ///
+ public string iModelId;
+
+ ///
+ /// Unique identifier of the specific changeset to export
+ ///
+ public string changesetId;
+
+ ///
+ /// Type of export format requested (e.g., "3DTILES", "GLTF")
+ ///
+ public string exportType;
+ }
+
+ ///
+ /// Response wrapper for export initiation API calls
+ ///
+ public class StartExportResponse
+ {
+ ///
+ /// The created export job details
+ ///
+ [JsonProperty("export")] public ExportWrapper Export { get; set; }
+ }
+
+ ///
+ /// Response wrapper for export status retrieval API calls
+ ///
+ public class GetExportResponse
+ {
+ ///
+ /// The current export job details and status
+ ///
+ [JsonProperty("export")] public ExportWrapper Export { get; set; }
+ }
+
+ ///
+ /// Represents a mesh export job with its current status and download information
+ ///
+ public class ExportWrapper
+ {
+ ///
+ /// Unique identifier for this export job
+ ///
+ [JsonProperty("id")] public string Id { get; set; }
+
+ ///
+ /// Current status of the export (e.g., "Queued", "Processing", "Complete", "Failed")
+ ///
+ [JsonProperty("status")] public string Status { get; set; }
+
+ ///
+ /// Human-readable name for this export job
+ ///
+ [JsonProperty("displayName")] public string DisplayName { get; set; }
+
+ ///
+ /// Navigation links for accessing export resources
+ ///
+ [JsonProperty("_links")] public ExportLinks Links { get; set; }
+
+ ///
+ /// Convenience property to access the mesh download URL
+ ///
+ public string Href => Links?.Mesh?.Href;
+ }
+
+ ///
+ /// Contains navigation links for export-related resources
+ ///
+ public class ExportLinks
+ {
+ ///
+ /// Link to the exported mesh data
+ ///
+ [JsonProperty("mesh")] public MeshLink Mesh { get; set; }
+ }
+
+ ///
+ /// Contains the URL for downloading mesh data
+ ///
+ public class MeshLink
+ {
+ ///
+ /// Direct URL to the mesh file download
+ ///
+ [JsonProperty("href")] public string Href { get; set; }
+ }
+
+ ///
+ /// Response wrapper for listing existing exports for an iModel
+ ///
+ public class ListExportsResponse
+ {
+ ///
+ /// Array of existing export jobs for the requested iModel
+ ///
+ [JsonProperty("exports")]
+ public ExportWrapper[] Exports { get; set; }
+ }
+
+ ///
+ /// Initiates a new mesh export job for the specified iModel and changeset.
+ /// This coroutine sends the export request to Bentley's API and returns the export job details.
+ ///
+ /// Valid OAuth access token for API authentication
+ /// Unique identifier of the iModel to export
+ /// Unique identifier of the changeset to export
+ /// Desired export format type
+ /// Callback invoked with export result or error message
+ /// Unity coroutine for asynchronous execution
+ public IEnumerator StartExportCoroutine(
+ string accessToken,
+ string iModelId,
+ string changesetId,
+ string exportType,
+ Action callback)
+ {
+ var reqObj = new StartExportRequest
+ {
+ iModelId = iModelId,
+ changesetId = changesetId,
+ exportType = exportType
+ };
+ string json = JsonConvert.SerializeObject(reqObj);
+ byte[] body = Encoding.UTF8.GetBytes(json);
+
+ using var request = new UnityWebRequest(BaseUrl, "POST")
+ {
+ uploadHandler = new UploadHandlerRaw(body),
+ downloadHandler = new DownloadHandlerBuffer()
+ };
+
+ request.SetRequestHeader("Authorization", $"Bearer {accessToken}");
+ request.SetRequestHeader("Accept", "application/vnd.bentley.itwin-platform.v1+json");
+ request.SetRequestHeader("Content-Type", "application/json");
+
+ yield return request.SendWebRequest(); // Works in Editor Coroutine
+
+ if (request.result != UnityWebRequest.Result.Success)
+ {
+ string error = $"StartExport failed ({request.responseCode}): {request.error}\n{request.downloadHandler?.text}";
+ Debug.LogError("MeshExportClient_Editor: " + error);
+ callback?.Invoke(null, error);
+ }
+ else
+ {
+ try
+ {
+ string responseText = request.downloadHandler.text;
+ var resp = JsonConvert.DeserializeObject(responseText);
+ if (resp?.Export == null) {
+ throw new JsonException("Parsed response or 'export' field is null.");
+ }
+ callback?.Invoke(resp.Export, null);
+ }
+ catch (Exception ex)
+ {
+ string error = $"Failed to parse StartExport response: {ex.Message}\nResponse JSON: {request.downloadHandler?.text}";
+ Debug.LogError("MeshExportClient_Editor: " + error);
+ callback?.Invoke(null, error);
+ }
+ }
+ }
+
+ ///
+ /// Coroutine to poll the Mesh‑Export API until the export is complete.
+ /// Called by the EditorWindow using EditorCoroutineUtility.
+ ///
+ public IEnumerator GetExportCoroutine(
+ string accessToken,
+ string exportId,
+ Action callback, // Callback parameters: ExportWrapper result, string error
+ int pollIntervalSeconds = 5) // Increased default interval
+{
+ string url = BaseUrl + exportId;
+ int attempts = 0;
+
+ while (true)
+ {
+ attempts++;
+ using var request = UnityWebRequest.Get(url);
+ request.SetRequestHeader("Authorization", $"Bearer {accessToken}");
+ request.SetRequestHeader("Accept", "application/vnd.bentley.itwin-platform.v1+json");
+
+ yield return request.SendWebRequest(); // Works in Editor Coroutine
+
+ if (request.result != UnityWebRequest.Result.Success)
+ {
+ string error = $"GetExport failed ({request.responseCode}): {request.error}\n{request.downloadHandler?.text}";
+ Debug.LogError("MeshExportClient_Editor: " + error);
+ callback?.Invoke(null, error);
+ yield break; // Exit coroutine on error
+ }
+
+ try
+ {
+ string responseText = request.downloadHandler.text;
+
+ var resp = JsonConvert.DeserializeObject(responseText);
+ if (resp?.Export == null) {
+ throw new JsonException("Parsed response or 'export' field is null.");
+ }
+ var export = resp.Export;
+
+ if (export.Status.Equals("complete", StringComparison.OrdinalIgnoreCase) ||
+ export.Status.Equals("succeeded", StringComparison.OrdinalIgnoreCase)) // Handle 'succeeded' as well
+ {
+ if (string.IsNullOrEmpty(export.Href)) {
+ string error = "Export completed/succeeded but download href is missing.";
+ Debug.LogError("MeshExportClient_Editor: " + error + "\nResponse JSON: " + responseText);
+ callback?.Invoke(null, error);
+ } else {
+ callback?.Invoke(export, null);
+ }
+ yield break; // Exit coroutine on completion
+ }
+ else if (export.Status.Equals("failed", StringComparison.OrdinalIgnoreCase) ||
+ export.Status.Equals("cancelled", StringComparison.OrdinalIgnoreCase))
+ {
+ string error = $"Export job ended with status: {export.Status}";
+ Debug.LogError("MeshExportClient_Editor: " + error + "\nResponse JSON: " + responseText);
+ callback?.Invoke(null, error);
+ yield break; // Exit coroutine on failure/cancellation
+ }
+ // Continue polling for other statuses like 'running', 'created', 'queued'
+ }
+ catch (Exception ex)
+ {
+ string error = $"Failed to parse GetExport response: {ex.Message}\nResponse JSON: {request.downloadHandler?.text}";
+ Debug.LogError("MeshExportClient_Editor: " + error);
+ callback?.Invoke(null, error);
+ yield break; // Exit coroutine on parsing error
+ }
+
+ // Use WaitForSecondsRealtime in Editor Coroutines if precise timing is needed,
+ // but WaitForSeconds is generally fine here.
+ yield return new WaitForSeconds(pollIntervalSeconds);
+ }
+}
+
+ ///
+ /// Coroutine to get or start a mesh export job.
+ /// Called by the EditorWindow using EditorCoroutineUtility.
+ ///
+ public IEnumerator GetOrStartExportCoroutine(
+ string accessToken,
+ string iModelId,
+ string changesetId,
+ string exportType,
+ Action callback)
+ {
+ // 1. Try to get existing export
+ string url = $"{BaseUrl}?iModelId={iModelId}&exportType={exportType}";
+ if (!string.IsNullOrEmpty(changesetId))
+ url += $"&changesetId={changesetId}";
+
+ bool shouldStartExport = false;
+ ExportWrapper existingExport = null;
+
+ using (var getRequest = UnityWebRequest.Get(url))
+ {
+ getRequest.SetRequestHeader("Authorization", $"Bearer {accessToken}");
+ getRequest.SetRequestHeader("Accept", "application/vnd.bentley.itwin-platform.v1+json");
+
+ yield return getRequest.SendWebRequest();
+
+ if (getRequest.result == UnityWebRequest.Result.Success)
+ {
+ try
+ {
+ var responseText = getRequest.downloadHandler.text;
+ var listResp = JsonConvert.DeserializeObject(responseText);
+ if (listResp?.Exports != null && listResp.Exports.Length > 0)
+ {
+ existingExport = listResp.Exports[0];
+ // If already complete and has href, return it
+ if ((existingExport.Status.Equals("complete", StringComparison.OrdinalIgnoreCase) ||
+ existingExport.Status.Equals("succeeded", StringComparison.OrdinalIgnoreCase)) &&
+ !string.IsNullOrEmpty(existingExport.Href))
+ {
+ callback?.Invoke(existingExport, null);
+ yield break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.LogWarning("Failed to parse existing exports: " + ex.Message);
+ shouldStartExport = true; // Continue to start export
+ }
+ }
+ else
+ {
+ shouldStartExport = true; // Continue to start export
+ }
+ }
+
+ if (existingExport != null && !shouldStartExport)
+ {
+ // Poll until complete
+ yield return GetExportCoroutine(accessToken, existingExport.Id, callback);
+ yield break;
+ }
+
+ // 2. Start new export
+#pragma warning disable CS0219 // Variable is assigned but its value is never used
+ bool done = false;
+#pragma warning restore CS0219 // Variable is assigned but its value is never used
+ ExportWrapper startedExport = null;
+ string startError = null;
+ yield return StartExportCoroutine(accessToken, iModelId, changesetId, exportType, (result, error) =>
+ {
+ startedExport = result;
+ startError = error;
+ done = true;
+ });
+ if (!string.IsNullOrEmpty(startError) || startedExport == null)
+ {
+ callback?.Invoke(null, startError ?? "Failed to start export.");
+ yield break;
+ }
+ // 3. Poll until complete
+ yield return GetExportCoroutine(accessToken, startedExport.Id, callback);
+ }
+}
\ No newline at end of file
diff --git a/Runtime/iTwinForUnity/MeshExport/MeshExportClient.cs.meta b/Runtime/iTwinForUnity/MeshExport/MeshExportClient.cs.meta
new file mode 100644
index 00000000..7c304a38
--- /dev/null
+++ b/Runtime/iTwinForUnity/MeshExport/MeshExportClient.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 934e4e0a2ca2b1a46951f83d8b980ca0
\ No newline at end of file
diff --git a/Runtime/iTwinForUnity/MeshExport/README.md b/Runtime/iTwinForUnity/MeshExport/README.md
new file mode 100644
index 00000000..8bbee844
--- /dev/null
+++ b/Runtime/iTwinForUnity/MeshExport/README.md
@@ -0,0 +1,295 @@
+# Mesh Export System
+
+This directory contains components responsible for exporting mesh data from Bentley's iTwin platform, including progress tracking, file management, and integration with the main workflow system.
+
+## Overview
+
+The Mesh Export System provides:
+- Asynchronous mesh export from iTwin iModels
+- Progress tracking and status monitoring
+- Error handling and retry capabilities
+- File format management and conversion
+- Integration with Bentley's mesh export APIs
+
+## Architecture
+
+The mesh export system follows an asynchronous, status-based architecture:
+- **Export Clients**: Handle communication with Bentley's export APIs
+- **Progress Tracking**: Monitor export status and provide user feedback
+- **File Management**: Handle downloaded files and format conversion
+- **Error Handling**: Robust error recovery and user notification
+
+## Directory Structure
+
+```
+MeshExport/
+├── README.md # This file - Mesh export system overview
+└── MeshExportClient.cs # Primary mesh export client and workflow orchestrator
+```
+
+## Core Components
+
+### MeshExportClient.cs
+**Purpose**: Primary client for orchestrating the complete mesh export workflow from initiation to final file download.
+
+**Key Responsibilities:**
+- Initiating mesh export requests with Bentley's APIs
+- Monitoring export progress with polling mechanism
+- Handling export status transitions (queued, processing, completed, failed)
+- Downloading completed export files
+- Managing export timeouts and error conditions
+- Providing progress callbacks to UI components
+
+**Export Workflow:**
+1. **Export Initiation**: Start export request for specific iModel and changeset
+2. **Status Polling**: Regularly check export progress and status
+3. **Progress Reporting**: Update UI with current export status and progress
+4. **Completion Handling**: Download files when export completes successfully
+5. **Error Recovery**: Handle failures with appropriate retry logic
+
+**Key Methods:**
+- `GetOrStartExportCoroutine()`: Checks for existing exports or starts new ones
+- `PollExportStatusCoroutine()`: Monitors export progress with configurable intervals
+- `HandleExportComplete()`: Processes successful export completion
+- `HandleExportError()`: Manages error states and recovery options
+
+## Export Process Flow
+
+### 1. Export Request
+```
+User initiates export -> Validate parameters -> Send export request -> Receive export ID
+```
+
+### 2. Status Monitoring
+```
+Poll export status -> Check progress -> Update UI -> Continue until complete/failed
+```
+
+### 3. Completion Handling
+```
+Export complete -> Download files -> Validate downloads -> Notify user -> Cleanup
+```
+
+## Data Models
+
+### Export Status Types
+The system handles various export states:
+- **Queued**: Export request accepted and waiting in queue
+- **Processing**: Export is actively being processed
+- **Completed**: Export finished successfully, files ready for download
+- **Failed**: Export encountered an error and could not complete
+- **Cancelled**: Export was cancelled by user or system
+- **Timeout**: Export exceeded maximum processing time
+
+### Export Response Models
+```csharp
+[Serializable]
+public class ExportResponse
+{
+ public string exportId; // Unique identifier for this export
+ public string status; // Current export status
+ public int progressPercentage; // Completion percentage (0-100)
+ public string downloadUrl; // URL for downloading completed files
+ public DateTime createdDate; // When export was initiated
+ public DateTime? completedDate; // When export finished (if applicable)
+ public string errorMessage; // Error description if failed
+}
+```
+
+## API Integration
+
+### Bentley Mesh Export API
+The system integrates with Bentley's mesh export APIs:
+- **Export Endpoint**: Initiates new mesh export requests
+- **Status Endpoint**: Retrieves current export status and progress
+- **Download Endpoint**: Provides access to completed export files
+- **List Endpoint**: Retrieves existing exports for an iModel
+
+### Authentication Requirements
+All API calls require proper authentication:
+- Bearer token authentication using OAuth 2.0 access tokens
+- Automatic token refresh when tokens expire
+- Graceful handling of authentication errors
+- Secure token storage and transmission
+
+### Rate Limiting
+The system respects API rate limits:
+- Configurable polling intervals to avoid excessive requests
+- Exponential backoff on rate limit responses (HTTP 429)
+- Queue management for multiple concurrent exports
+- Priority handling for different export types
+
+## Error Handling
+
+### Network Errors
+- Connection timeout handling with configurable retry attempts
+- HTTP error code interpretation and user-friendly messaging
+- Network availability detection and offline graceful degradation
+
+### Export Errors
+- Detailed error message parsing from API responses
+- Export failure categorization (temporary vs permanent failures)
+- Automatic retry for transient failures
+- User notification with actionable error information
+
+### File Download Errors
+- Verification of downloaded file integrity
+- Partial download recovery and resume capability
+- Disk space validation before download initiation
+- File permission and access error handling
+
+## Performance Optimization
+
+### Polling Strategy
+- Adaptive polling intervals based on export complexity
+- Reduced polling frequency for long-running exports
+- Immediate status checks for recently initiated exports
+- Background polling to avoid blocking UI interactions
+
+### Memory Management
+- Efficient handling of large export files
+- Streaming downloads for large datasets
+- Proper disposal of temporary resources
+- Memory usage monitoring during export operations
+
+### Concurrency
+- Support for multiple simultaneous exports
+- Thread-safe status tracking and updates
+- Coordinated UI updates from background threads
+- Resource sharing and conflict resolution
+
+## Configuration
+
+### Timeout Settings
+```csharp
+public static class ExportConstants
+{
+ public const int DEFAULT_POLL_INTERVAL_SECONDS = 30;
+ public const int MAX_EXPORT_TIMEOUT_MINUTES = 60;
+ public const int RETRY_ATTEMPTS_ON_FAILURE = 3;
+ public const int EXPONENTIAL_BACKOFF_BASE_SECONDS = 2;
+}
+```
+
+### File Format Support
+The system supports various export formats:
+- **3D Tiles**: Cesium 3D Tiles format for web visualization
+- **glTF**: Standard 3D format for general use
+- **OBJ**: Wavefront OBJ format for compatibility
+- **Custom**: Bentley-specific formats as needed
+
+## Integration Points
+
+### With Authentication System
+- Seamless integration with Bentley authentication
+- Automatic token refresh during long-running exports
+- Proper error handling for authentication failures
+- Secure credential management throughout export process
+
+### With UI Components
+- Real-time progress updates to export UI components
+- Status change notifications for workflow coordination
+- Error reporting with user-friendly messages
+- Completion callbacks for UI state updates
+
+### With File System
+- Managed download location selection
+- File organization and naming conventions
+- Integration with Unity's asset import system
+- Cleanup of temporary files and resources
+
+## Development Guidelines
+
+### Adding New Export Types
+1. Define export type constants and parameters
+2. Implement API request formatting for the new type
+3. Add status polling logic specific to the export type
+4. Update error handling for type-specific failures
+5. Test with various iModel sizes and complexity levels
+
+### Extending Progress Tracking
+1. Identify additional progress metrics to track
+2. Update API response parsing for new progress data
+3. Enhance UI feedback with additional progress information
+4. Consider performance impact of increased polling detail
+
+### Error Recovery Enhancement
+1. Analyze common failure patterns
+2. Implement specific recovery strategies for each failure type
+3. Add user options for manual recovery actions
+4. Update error messaging with clear resolution steps
+
+## Common Patterns
+
+### Export Initiation Pattern
+```csharp
+public IEnumerator StartExportCoroutine(string iModelId, string changesetId, Action onComplete)
+{
+ var exportRequest = new ExportRequest
+ {
+ iModelId = iModelId,
+ changesetId = changesetId,
+ format = ExportFormat.CesiumTiles
+ };
+
+ yield return SendExportRequestCoroutine(exportRequest);
+ yield return PollExportStatusCoroutine(exportRequest.exportId, onComplete);
+}
+```
+
+### Status Polling Pattern
+```csharp
+private IEnumerator PollExportStatusCoroutine(string exportId, Action onComplete)
+{
+ var startTime = DateTime.Now;
+
+ while (DateTime.Now - startTime < TimeSpan.FromMinutes(MAX_EXPORT_TIMEOUT_MINUTES))
+ {
+ var status = yield return GetExportStatusCoroutine(exportId);
+
+ if (status.IsComplete)
+ {
+ onComplete?.Invoke(status);
+ yield break;
+ }
+
+ yield return new WaitForSeconds(GetPollInterval(status));
+ }
+
+ // Handle timeout
+ onComplete?.Invoke(ExportResult.Timeout);
+}
+```
+
+### Error Handling Pattern
+```csharp
+private void HandleExportError(ExportError error)
+{
+ switch (error.Type)
+ {
+ case ExportErrorType.Transient:
+ // Retry with exponential backoff
+ ScheduleRetry(error.RetryCount);
+ break;
+
+ case ExportErrorType.Authentication:
+ // Refresh token and retry
+ RefreshTokenAndRetry();
+ break;
+
+ case ExportErrorType.Permanent:
+ // Notify user and stop
+ NotifyUserOfPermanentFailure(error.Message);
+ break;
+ }
+}
+```
+
+## Future Enhancements
+
+The mesh export system is designed for extensibility:
+- Support for additional export formats and parameters
+- Advanced progress visualization and analytics
+- Batch export capabilities for multiple iModels
+- Integration with cloud storage services
+- Custom post-processing workflows for exported data
diff --git a/Runtime/iTwinForUnity/MeshExport/README.md.meta b/Runtime/iTwinForUnity/MeshExport/README.md.meta
new file mode 100644
index 00000000..37cc2970
--- /dev/null
+++ b/Runtime/iTwinForUnity/MeshExport/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a6169308122490c48802e9260587490a
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/iTwinForUnity/README.md b/Runtime/iTwinForUnity/README.md
new file mode 100644
index 00000000..9bd4c8c0
--- /dev/null
+++ b/Runtime/iTwinForUnity/README.md
@@ -0,0 +1,111 @@
+# iTwin Unity Runtime Components
+
+This document provides a comprehensive overview of the iTwin Unity plugin's runtime architecture and components.
+
+## Overview
+
+The iTwin Unity Runtime contains all components that can be used at runtime in Unity, including MonoBehaviours, data models, and utility classes that don't depend on UnityEditor APIs. This assembly is designed to work in both the Unity Editor and in built Unity applications.
+
+## Architecture Principles
+
+The Runtime assembly follows several key architectural principles:
+
+### Runtime-Safe Design
+- No dependencies on UnityEditor APIs
+- Components work in both Editor and built applications
+- Compatible with all Unity build targets
+
+### Component-Based Design
+- Classes are broken down into focused, single-responsibility components
+- Each component handles a specific aspect of functionality
+- Components communicate through well-defined interfaces
+
+### Separation of Concerns
+- Data models are separate from business logic
+- Scene components are separate from utility functions
+- Clear boundaries between different functional areas
+
+### Architectural Layers
+- **Data Layer**: Serializable data models and constants
+- **Component Layer**: Unity MonoBehaviours and scene components
+- **Service Layer**: Runtime-safe utilities and mesh export functionality
+- **Infrastructure Layer**: Constants, configurations, and cross-cutting concerns
+
+## Assembly Structure
+
+```
+Runtime/
+├── Runtime.asmdef # Assembly definition (references: CesiumRuntime)
+├── README.md # This documentation
+├── BentleyTilesetMetadata.cs # Runtime MonoBehaviour component
+├── Constants/ # Application constants and configurations
+│ ├── TilesetConstants.cs # Tileset-related constants
+│ └── AuthConstants.cs # Authentication constants
+├── DataModels/ # Data transfer objects and models
+│ └── BentleyDataModels.cs # Core data models (iTwin, iModel, etc.)
+├── MeshExport/ # Runtime mesh export functionality
+│ ├── README.md # Mesh export documentation
+│ └── MeshExportClient.cs # Runtime-compatible export client
+└── Utils/ # Runtime-safe utility classes
+ └── (Runtime utility methods) # Helper methods for runtime use
+```
+
+## Key Components
+
+### BentleyTilesetMetadata
+**File**: `BentleyTilesetMetadata.cs`
+**Type**: MonoBehaviour Component
+
+A Unity component that stores iTwin metadata associated with Cesium3DTileset objects. This component:
+- Stores iTwin project and iModel information
+- Maintains changeset details and export metadata
+- Provides inspector-friendly serialized fields
+- Integrates with Cesium for Unity runtime
+
+### Data Models (`/DataModels`)
+**Type**: Serializable Data Classes
+
+Core data structures that represent iTwin platform entities:
+- `ITwin`: Represents an iTwin project
+- `IModel`: Represents an iModel within an iTwin
+- `Changeset`: Represents a version/changeset of an iModel
+- Response models for API communication
+
+### Constants (`/Constants`)
+**Type**: Static Configuration Classes
+
+Application-wide constants and configuration values:
+- Authentication endpoints and parameters
+- Tileset management constants
+- API URLs and default values
+
+### Mesh Export (`/MeshExport`)
+**Type**: Service Classes
+
+Runtime-compatible mesh export functionality:
+- API communication for mesh export requests
+- Progress tracking and status monitoring
+- URL generation for exported tilesets
+
+## Assembly Dependencies
+
+### External Dependencies
+- **CesiumRuntime**: Integration with Cesium for Unity's runtime components
+- **Unity.Mathematics**: For mathematical operations
+- **Newtonsoft.Json**: JSON serialization/deserialization
+
+### Usage Guidelines
+- All classes in this assembly must be runtime-safe
+- No `using UnityEditor;` statements allowed
+- Components should work in both Editor and built applications
+- Use `#if UNITY_EDITOR` guards only for non-essential Editor-specific optimizations
+
+## Integration with Editor Assembly
+
+The Runtime assembly is referenced by the Editor assembly (`iTwinForUnity.Editor`) which provides:
+- Custom inspectors for Runtime components
+- Editor windows and UI functionality
+- Authentication and project browsing
+- Advanced tileset management tools
+
+For questions about the runtime architecture or implementation details, refer to the XML documentation comments in the code or consult the Editor assembly documentation for UI-related functionality.
diff --git a/Runtime/iTwinForUnity/README.md.meta b/Runtime/iTwinForUnity/README.md.meta
new file mode 100644
index 00000000..ea96395c
--- /dev/null
+++ b/Runtime/iTwinForUnity/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: b35d8a502c712644793b31123d9dfdc3
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/package.json b/package.json
index 7ab5e15f..18160df2 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,8 @@
"com.unity.mathematics": "1.2.0",
"com.unity.test-framework": "1.1.31",
"com.unity.shadergraph": "12.1.6",
- "com.unity.inputsystem": "1.4.2"
+ "com.unity.inputsystem": "1.4.2",
+ "com.unity.editorcoroutines": "1.0.0",
+ "com.unity.nuget.newtonsoft-json": "3.2.1"
}
}
\ No newline at end of file