3.4.0 Change Notes

Table of contents:

Electron 17 support

In addition to the already supported Electron 14, iTwin.js now supports Electron versions 15, 16, and 17. At the moment, support for Electron 18 and 19 is blocked due to a bug in the V8 javascript engine.

IModelSchemaLoader replaced

The IModelSchemaLoader class has been replaced with SchemaLoader for obtaining schemas from an iModel. This allows us to remove the @itwin/ecschema-metadata dependency from @itwin/core-backend.

// Old
import { IModelSchemaLoader } from "@itwin/core-backend";
const loader = new IModelSchemaLoader(iModel);
const schema = loader.getSchema("BisCore");

// New
import { SchemaLoader } from "@itwin/ecschema-metadata";
const loader = new SchemaLoader((name) => iModel.getSchemaProps(name); );
const schema = loader.getSchema("BisCore");

SchemaLoader can be constructed with any function that returns ECSchemaProps when passed a schema name string.

Previously when we did SELECT * on a link table, it would only return ECInstanceId, ECClassId, SourceECInstanceId and TargetECInstanceId. It would omit SourceECClassId and TargetECClassId. Those two omitted columns are now included in the query result rows.

Cloud storage changes

The existing beta implementations of cloud storage tile cache (CloudStorageService - AzureBlobStorage, AliCloudStorageService) have been deprecated in favor of the iTwin/object-storage project, which exposes a unified cloud-agnostic object storage interface in turn simplifying the setup of Microsoft Azure or S3 based (OSS, MinIO) cloud storage providers.

CloudStorageService remains to support older frontends, however the new implementation of cloud storage still has to be setup. This is done automatically if IModelHostOptions.tileCacheAzureCredentials are used.

A different cloud provider may be set in IModelHostOptions.tileCacheStorage and IModelAppOptions.tileAdmin.tileStorage, which could be any of the implementations iTwin/object-storage provides.


Custom terrain providers

Previously, 3d terrain required access to Cesium World Terrain, a paid service. Now, applications can use their own sources of 3d terrain by registering a TerrainProvider and implementing a TerrainMeshProvider to produce 3d terrain meshes.

The name of the provider is stored in TerrainSettings.providerName. The default is "CesiumWorldTerrain".

See BingTerrainProvider for an example of a custom terrain provider.

Ambient occlusion improvements

The ambient occlusion effect has undergone some quality improvements.


  • The shadows cast by ambient occlusion will decrease in size the more distant the geometry is.
  • The maximum distance for applying ambient occlusion now defaults to 10,000 meters instead of 100 meters.
  • The effect will now fade as it approaches the maximum distance.

Old effect, as shown below:

AO effect is the same strength in the near distance and far distance

New effect, shown below:

AO effect fades in the distance; shadows decrease in size

For more details, see the new descriptions of the texelStepSize and maxDistance properties of AmbientOcclusion.Props.

Improved display transform support

In some cases, geometry is displayed within a Viewport at a different location, orientation, and/or scale than that with which it is persisted in the iModel. For example:

Tools that interact both with a Viewport and with persistent geometry sometimes need to account for such display transforms. Such tools can now use ViewState.computeDisplayTransform to compute the transform applied to a model or element for display. For example, AccuSnap applies the display transform to the snap points and curves received from the backend to display them correctly in the viewport; and ViewClipByElementTool applies it to the element's bounding box to orient the clip with the element as displayed in the viewport.

Wait for scene completion

As you navigate inside a Viewport, Tiles, texture images, and other resources are streamed in asynchronously to display the contents of the view. Sometimes, you may want to wait until the scene has been fully rendered - for example, so that you can capture a complete image of the viewport's contents. Viewport.waitForSceneCompletion provides a Promise that resolves when the scene has been fully rendered. Here's an example that captures an image of the fully-rendered scene:

async function captureImage(vp: Viewport): Promise<ImageBuffer | undefined> {
  await vp.waitForSceneCompletion();
  return vp.readImageBuffer();


Restoring presentation tree state

It is now possible to restore previously saved Presentation tree state on component mount.

// Save current tree state
const { nodeLoader } = usePresentationTreeNodeLoader(args);
useEffect(() => exampleStoreTreeModel(nodeLoader.modelSource.getModel()), []);

// Restore tree state on component mount
const seedTreeModel = exampleRetrieveStoredTreeModel();
const { nodeLoader } = usePresentationTreeNodeLoader({ ...args, seedTreeModel });

Diagnostics improvements and OpenTelemetry

The Presentation Diagnostics API has been upgraded from @alpha to @beta. Several new features have also been added:

Localization changes

Previously, some of the data produced by the Presentation library was being localized both on the backend. This behavior was dropped in favor of localizing everything on the frontend. As a result, the requirement to supply localization assets with the backend is also removed.

In case of a backend-only application, localization may be setup by providing a localization function when initializing the Presentation backend. By default the library localizes known strings to English.

Deprecated APIs:

  • PresentationManagerProps.localeDirectories
  • PresentationManagerProps.defaultLocale
  • PresentationManager.activeLocale

Content sources

Presentation API provides the PresentationManager.getContentSources function to retrieve a list of classes that are used to build content for a given class of elements. It used to create this list based on actual instances in the given iModel, which made the call very expensive on large iModels. Requesting this for bis.Element would result in a response like the following:

    selectClassInfo: { name: "ConcreteElementClassA" },
    relatedPropertyPaths: [
        relationshipInfo: { name: "bis.ElementOwnsMultiAspects" },
        targetClassInfo: { name: "ConcreteAspectX" },
      }], [{
        relationshipInfo: { name: "bis.ElementOwnsMultiAspects" },
        targetClassInfo: { name: "ConcreteAspectY" },
      // ... and so on for every different concrete related property class
    selectClassInfo: { name: "ConcreteElementClassB" },
  // ... and so on for every different element class that has instances

It turns out that for purposes this function is intended for, it's not necessary to get concrete classes and their base classes can be used instead. So now, when requesting this for bis.Element, the response is like the following:

    selectClassInfo: { name: "bis.Element" },
    relatedPropertyPaths: [
        relationshipInfo: { name: "bis.ElementOwnsMultiAspects" },
        targetClassInfo: { name: "bis.ElementMultiAspect" },

This allows the result to be created purely by looking at ECSchemas in the iModel instead of querying actual instances and relationships, which provides an orders of magnitude performance improvement over the previous approach.


Coplanar facet consolidation

A new method, PolyfaceQuery.cloneWithMaximalPlanarFacets, can identify groups of adjacent coplanar facets in a mesh and produce a new mesh in which each group is consolidated into a single facet. The consolidated facets are necessarily not triangular and various bridge edges will be present in non-convex facets.


Filling mesh holes

A new method, PolyfaceQuery.fillSimpleHoles, can identify holes in a mesh and produce a new mesh in which some or all of the holes are replaced with facets. Which holes are filled can be controlled using HoleFillOptions to specify constraints such as maximum hole perimeter, number of edges, and/or loop direction.




The synchronous void-returning overload of IModelTransformer.initFromExternalSourceAspects has been deprecated. It will still perform the old behavior synchronously until it is removed. It will now however return a Promise (which should be awaited) if invoked with the an InitFromExternalSourceAspectsArgs argument, which is necessary when processing changes instead of the full source contents.

Last Updated: 21 November, 2022