2.11.0 Change Notes

Overview:

Breaking Changes

Breaking API Changes

  • The union type Matrix3dProps inadvertently included Matrix3d. "Props" types are wire formats and so must be pure JavaScript primitives. To fix compilation errors where you are using Matrix3d where a Matrix3dProps is expected, simply call Matrix3d.toJSON on your Matrix3d object. Also, since TransformProps includes Matrix3dProps, you may need to call Transform.toJSON on your Transform objects some places too.

  • The type of Texture.data has been corrected from string to Uint8Array to match the type in the BIS schema. If you get compilation errors, simply remove calls to Buffer.from(texture.data, "base64") for read, and texture.data.toString("base64") if you create texture objects.

  • Several interfaces in the @itwin/components-react package have been renamed:

Previous

interface HighlightedRecordProps {
  activeMatch?: PropertyRecordMatchInfo;
  searchText: string;
}

New

interface HighlightInfo {
  highlightedText: string;
  activeHighlight?: HighlightInfo;
}

This is just a terminology change, so reacting to the change is as simple as renaming searchText -> highlightedText and activeMatch -> highlightedText.

Previous

interface PropertyRecordMatchInfo {
   matchCounts: {
       label: number;
       value: number;
   };
   matchIndex: number;
   propertyName: string;
 }

New

interface HighlightInfo {
  highlightedItemIdentifier: string;
  highlightIndex: number;
}

This is just a terminology change, so reacting to the change is as simple as renaming matchIndex -> highlightedItemIdentifier and propertyName -> highlightedItemIdentifier.

  • Changed highlightProps?: HighlightedRecordProps property to highlight?: HighlightingComponentProps on PrimitiveRendererProps interface. To react to this change, simply rename highlightProps -> highlight.

  • Changed highlightProps?: HighlightedRecordProps property to highlight?: HighlightingComponentProps on PropertyRendererProps interface. To react to this change, simply rename highlightProps -> highlight.

  • The methods fromJson and toJson from alpha class Format, in the quantity package, have been renamed to fromJSON and toJSON respectively.

  • The method parseIntoQuantityValue from alpha class QuantityFormatter in the imodeljs-frontend package, has been renamed to parseToQuantityValue.

BriefcaseManager breaking changes

This version changes the approach to storing Briefcase and Checkpoint files in the local disk cache. Now, BriefcaseManager will create a subdirectory from the root directory supplied in BriefcaseManager.initialize for each iModelId, and then folders called briefcases and checkpoints within that folder. The Briefcase and Checkpoint files themselves are named with the BriefcaseId and ChangeSetId respectively.

For backwards compatibility: When searching for local files from RPC calls, the previous locations and naming rules are checked for briefcase and checkpoint files. This check will be removed in a future version.

Several methods on the @beta class BriefcaseManager have been changed to simplify how local Briefcase files are acquired and managed. Previously the location and name of the local files holding Briefcases was hidden behind a local in-memory cache that complicated the API. Now, the BriefcaseDb.open method takes an argument that specifies the file name. This makes working with local Briefcases much simpler. BriefcaseManager's role is limited to acquiring and releasing briefcases, and for downloading, uploading, and applying changesets.

In particular, the method BriefcaseManager.download was removed and replaced with BriefcaseManager.downloadBriefcase, since backwards compatibility could not be maintained. See the documentation on that method to understand what you may need to change in your code.

The BriefcaseManager.imodelClient has been removed in favor of directly using IModelHost.iModelClient.

Note that the frontend APIs have not changed, so the impact of this change to the beta APIs should be limited.

Also, the @internal class NativeApp has several changed methods, so some refactoring may be necessary to react to those changes for anyone implementing native applications using this internal API.

Section-cut graphics

A DisplayStyleState can now be configured to produce section-cut graphics from the view's ClipVector. In the image below, a clipping plane has been applied to produce a cross-section view of a house. Note that all of the geometry intersecting the clipping plane appears to be hollow, whereas in the real world we'd expect things like walls, floors, and tables to be solid throughout:

Clipped view without section-cut graphics

Now, every DisplayStyleSettings has a ClipStyle that specifies how the clipping planes should affect the view. ClipStyle.produceCutGeometry specifies that additional graphics should be produced for solid geometry intersecting the clipping planes, causing solid objects to continue to appear solid when sliced by a clipping plane, as shown below:

Clipped view with section-cut graphics

A ClipStyle can also specify a CutStyle controlling how the section-cut geometry is displayed by overriding:

In the image below, the section-cut graphics are drawn in orange with visible blue edges as specified by the CutStyle.

Section-cut view with CutStyle overriding symbology

NOTE: If a ClipStyle is associated with a Viewport, it should be modified via Viewport.clipStyle to ensure the viewport's contents are updated to reflect the change.

Custom screen-space effects

The display system now allows applications to apply their own post-processing effects to the image rendered by a Viewport using RenderSystem.createScreenSpaceEffectBuilder. A couple of example effects can be found in the frontend-devtools package, including LensDistortionEffect (illustrated below) for simulating the distortion produced by real-world cameras with wide fields of view; and various ConvolutionEffects for producing a blurred, sharpened, or embossed image.

Wide-angle camera with no lens distortion: Wide-angle camera with no lens distortion

Wide-angle camera with lens distortion: Wide-angle camera with lens distortion

Automatic viewport synchronization

A Viewport holds a reference to a ViewState defining its viewing parameters, which in turn holds references to objects like a DisplayStyleState and CategorySelectorState controlling the styling and contents of the Viewport. Previously, directly modifying the ViewState or its components would not cause the Viewport to update immediately on-screen - instead, callers would either need to manually invoke methods like Viewport.invalidateScene to notify the Viewport that something had changed; or use special Viewport APIs like Viewport.changeCategoryDisplay that would forward to the appropriate object and also invalidate the Viewport's internal state. This was error-prone and tedious.

New events have been added to ViewState and its related classes to notify listeners when aspects of their states change. For example, changing the background color via DisplayStyleSettings.backgroundColor raises the DisplayStyleSettings.onBackgroundColorChanged event. A Viewport now listens for such events and updates its own state automatically. This eliminates the need for most manual synchronization. However, Viewport.synchWithView should still be used when modifying the ViewState's Frustum

Globe location tool fixes

The globe location tools now will properly use GCS reprojection when navigating. Previously, navigating to certain cartographic locations within the iModel extents could be slightly inaccurate.

The tools affected are:

The ViewGlobeLocationTool has been further improved to navigate better across long distances when using plane mode.

There is now a method called lookAtGlobalLocationFromGcs on ViewState3d. This method behaves exactly like lookAtGlobalLocation except that is async and uses the GCS to reproject the location.

ViewState3d also has GCS versions of these methods:

  • rootToCartographicFromGcs behaves like rootToCartographic except it is async and uses the GCS to reproject the location.
  • cartographicToRootFromGcs behaves like cartographicToRoot except it is async and uses the GCS to reproject the location.

Changes to display style excluded elements

DisplayStyleSettings.excludedElements allows a display style to specify a set of elements that should not be drawn. Previously, this set was always persisted to the database as an array of element Ids, and represented in JSON and in memory as a Set<string>. However, element Ids tend to be long strings (at least 13 characters), and sets of excluded elements can occasionally grow quite large. To reduce the amount of data associated with these sets:

To adjust code that uses DisplayStyleSettings.excludedElements, given settings: DisplayStyleSettings:

  settings.excludedElements.add(id); // Replace this...
  settings.addExcludedElements(id); // ...with this.

  settings.excludedElements.delete(id); // Replace this...
  settings.dropExcludedElements(id); // ...with this.

  settings.excludedElements.clear(); // Replace this...
  settings.clearExcludedElements(); // ...with this.

  for (const id of settings.excludedElements) { } // Replace this...
  for (const id of settings.excludedElementIds) { } // ...with this.

Note that DisplayStyleSettings.addExcludedElements and DisplayStyleSettings.dropExcludedElements can accept any number of Ids. If you have multiple Ids, prefer to pass them all at once rather than one at a time - it is more efficient.

Tile compression

IModelHostConfiguration.compressCachedTiles specifies whether tiles uploaded to blob storage should be compressed using gzip. Previously, it defaulted to false if omitted. The default has now been switched to true. Compressing tiles conserves bandwidth; the tiles are transparently and efficiently decompressed by the browser.

Upgrading schemas in an iModel

In previous versions the method to open briefcases (BriefcaseDb.open) and standalone files (StandaloneDb.open renamed to StandaloneDb.openFile) provided options to upgrade the schemas in the iModel. This functionality has been now separated out, and there are separate methods to validate and upgrade the schemas in the iModel. As a result OpenBriefcaseProps and SnapshotOpenOptions do not include options to upgrade anymore.

See section on Upgrading Schemas for more information.

Filtering in Property Grid

Now it is possible to filter property grid items (Records or Categories) using FilteringPropertyDataProvider. In addition, support for highlighting parts of items that matched the search criteria has also been added.

Property Filtering

Filtering is done by FilteringPropertyDataProvider and IPropertyDataFilterer that is passed to the provider. We provide a number of read-to-use filterers:

  • CompositePropertyDataFilterer combines two other filterers
  • PropertyCategoryLabelFilterer filters PropertyCategories by label
  • DisplayValuePropertyDataFilterer filters PropertyRecords by display value
  • LabelPropertyDataFilterer filters PropertyRecords by property label
  • FavoritePropertiesDataFilterer (in @itwin/presentation-components) filters PropertyRecords by whether the property is favorite or not.

Example:

const searchString = "Test";

const filteringDataProvider = useDisposable(React.useCallback(() => {
  // Combine a filterer that filters out favorite properties having `searchString` in their category label,
  // property label or display value
  const valueFilterer = new DisplayValuePropertyDataFilterer(searchString);
  const labelFilterer = new LabelPropertyDataFilterer(searchString);
  const categoryFilterer = new PropertyCategoryLabelFilterer(searchString);
  const recordFilterer = new CompositePropertyDataFilterer(labelFilterer, CompositeFilterType.Or, valueFilterer);
  const recordAndCategoryFilterer = new CompositePropertyDataFilterer(recordFilterer, CompositeFilterType.Or, categoryFilterer);
  const favoritesFilterer = new FavoritePropertiesDataFilterer({ source: dataProvider, favoritesScope: FAVORITES_SCOPE, isActive: true });
  const combinedFilterer = new CompositePropertyDataFilterer(recordAndCategoryFilterer, CompositeFilterType.And, favoritesFilterer);
  // Create the provider
  return new FilteringPropertyDataProvider(dataProvider, combinedFilterer);
}, [dataProvider]));

// Getting results from FilteringDataProvider
const { value: filteringResult } = useDebouncedAsyncValue(React.useCallback(async () => {
  return await filteringDataProvider.getData();
}, [filteringDataProvider]));

// Getting a match at index 10, which we want to actively highlight
const [activeHighlight, setActiveHighlight] = React.useState<HighlightInfo>();
React.useEffect(() => {
    if (filteringResult?.getMatchByIndex)
      setActiveHighlight(filteringResult.getMatchByIndex(10));
}, [filteringDataProvider, filteringResult]);

// Set up props for highlighting matches
const highlightProps: HighlightingComponentProps = {
  highlightedText: searchString,
  activeHighlight,
  filteredTypes: filteringResult?.filteredTypes,
};

// Render the component with filtering data provider and highlight props
return (
  <VirtualizedPropertyGridWithDataProvider
    dataProvider={filteringDataProvider}
    highlight={highlightProps}
    ...
  />
);

Presentation

Formatted property values in ECExpressions

ECExpressions now support formatted property values. GetFormattedValue function can be used in ECExpressions to get formatted value of the property. This adds ability to filter instances by some formatted value:

GetFormattedValue(this.Length, "Metric") = "10.0 m"

Enhanced navigation property values

Navigation property values now contain related instance class information in addition to instance ids, making them now full-fledged instance keys. As a result, it is now possible to use navigation property values directly with Unified Selection APIs.

Interactable navigation properties

A property value renderer for instance key values has been added, which allows users to click the key value to select the referenced instance in Unified Selection. This can be used, for instance, to implement navigation between related elements through navigation properties. To enable this new renderer, you'll need to wrap your UI components with UnifiedSelectionContextProvider and set SelectableInstance renderer on specific properties using Presentation Rules:

{
  "ruleType": "ContentModifier",
  "propertyOverrides": [
    {
      "name": "<navigation property to make clickable>",
      "renderer": {
        "rendererName": "SelectableInstance"
      }
    }
  ]
}

Breaking changes to ContentRelatedInstances

Behavior of ContentRelatedInstances specification used in content rules was changed. It used to include input instances into the result if all paths in relationshipPaths property had count: "*" and target class matched input instance class. The behavior was changed to match cases where steps relationshipPaths have count set to specific number - the result only includes instances resulting from step outputs. See RelationshipPathSpecification documentation for more details and examples.

Example:

{
  "ruleType": "Content",
  "specifications": [
    {
      "specType": "ContentRelatedInstances",
      "relationshipPaths": [
        [
          {
            "relationship": {
              "schemaName": "BisCore",
              "className": "GeometricElement3dHasTypeDefinition"
            },
            "direction": "Backward",
            "count": "*"
          },
          {
            "relationship": {
              "schemaName": "BisCore",
              "className": "ElementOwnsChildElements"
            },
            "direction": "Forward",
            "count": "*"
          }
        ]
      ]
    }
  ]
}

This rule used to include BisCore.TypeDefinitionElement instance used as input along side BisCore.GeometricElement3d instances. If previous behavior is desired SelectedNodeInstance specification can be added to the content rule:

{
  "ruleType": "Content",
  "specifications": [
    {
      "specType": "SelectedNodeInstances"
    },
    {
      "specType": "ContentRelatedInstances",
      "relationshipPaths": [
        [
          {
            "relationship": {
              "schemaName": "BisCore",
              "className": "GeometricElement3dHasTypeDefinition"
            },
            "direction": "Backward",
            "count": "*"
          },
          {
            "relationship": {
              "schemaName": "BisCore",
              "className": "ElementOwnsChildElements"
            },
            "direction": "Forward",
            "count": "*"
          }
        ]
      ]
    }
  ]
}

QuantityFormatter updates

The QuantityFormatter now support four unit systems: Metric, Imperial, US Survey, and US Customary. This allows it to align with the four unit systems supported in the Presentation package. The method setActiveUnitSystem and property activeUnitSystem can be used to set and query the active unit system. See UnitSystemKey in @itwin/core-frontend package.

export type UnitSystemKey = "metric" | "imperial" | "usCustomary" | "usSurvey";

There are also new methods to set and clear override formats for a particular QuantityType. The example below show how to set an override format for QuantityType.Length used be the measure tool.

    const overrideLengthFormats = {
      metric: {
        composite: {
          includeZero: true,
          spacer: " ",
          units: [{ label: "cm", name: "Units.CM" }],
        },
        formatTraits: ["keepSingleZero", "showUnitLabel"],
        precision: 4,
        type: "Decimal",
      },
      imperial: {
        composite: {
          includeZero: true,
          spacer: " ",
          units: [{ label: "in", name: "Units.IN" }],
        },
        formatTraits: ["keepSingleZero", "showUnitLabel"],
        precision: 4,
        type: "Decimal",
      },
      usCustomary: {
        composite: {
          includeZero: true,
          spacer: " ",
          units: [{ label: "in", name: "Units.IN" }],
        },
        formatTraits: ["keepSingleZero", "showUnitLabel"],
        precision: 4,
        type: "Decimal",
      },
      usSurvey: {
        composite: {
          includeZero: true,
          spacer: " ",
          units: [{ label: "in", name: "Units.US_SURVEY_IN" }],
        },
        formatTraits: ["keepSingleZero", "showUnitLabel"],
        precision: 4,
        type: "Decimal",
      },
    };

await IModelApp.quantityFormatter.setOverrideFormats(QuantityType.Length, overrideLengthFormats);

Last Updated: 12 February, 2024