Skip to content

Commit

Permalink
Replace usage of queryModelExtents with queryModelExtentsAsync (#4516)
Browse files Browse the repository at this point in the history
* rpc method

* deprecate queryModelRanges

* backend except getViewStateData

* getViewStateData

* doc

* doc

* cleanup

* lint

* fix doc links

* un-deprecate Model.queryExtents.

* NextVersion

* typo
  • Loading branch information
pmconne authored Oct 20, 2022
1 parent 12cfb61 commit d529a84
Show file tree
Hide file tree
Showing 21 changed files with 246 additions and 119 deletions.
7 changes: 6 additions & 1 deletion common/api/core-backend.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ import { MarkRequired } from '@itwin/core-bentley';
import { MassPropertiesRequestProps } from '@itwin/core-common';
import { MassPropertiesResponseProps } from '@itwin/core-common';
import { Metadata } from '@itwin/object-storage-core';
import { ModelExtentsProps } from '@itwin/core-common';
import { ModelGeometryChangesProps } from '@itwin/core-common';
import { ModelIdAndGeometryGuid } from '@itwin/core-common';
import { ModelLoadProps } from '@itwin/core-common';
Expand Down Expand Up @@ -2508,6 +2509,7 @@ export class GeometricModel extends Model {
// (undocumented)
geometryGuid?: GuidString;
queryExtents(): AxisAlignedBox3d;
queryRange(): Promise<AxisAlignedBox3d>;
}

// @public
Expand Down Expand Up @@ -2908,8 +2910,10 @@ export namespace IModelDb {
getModelProps<T extends ModelProps>(id: Id64String): T;
getSubModel<T extends Model>(modeledElementId: Id64String | GuidString | Code, modelClass?: EntityClassType<Model>): T;
insertModel(props: ModelProps): Id64String;
queryExtents(ids: Id64String | Id64String[]): Promise<ModelExtentsProps[]>;
// @internal
queryLastModifiedTime(modelId: Id64String): string;
queryRange(ids: Id64String | Id64String[]): Promise<AxisAlignedBox3d>;
tryGetModel<T extends Model>(modelId: Id64String, modelClass?: EntityClassType<Model>): T | undefined;
tryGetModelProps<T extends ModelProps>(id: Id64String): T | undefined;
tryGetSubModel<T extends Model>(modeledElementId: Id64String | GuidString | Code, modelClass?: EntityClassType<Model>): T | undefined;
Expand Down Expand Up @@ -2940,8 +2944,9 @@ export namespace IModelDb {
constructor(_iModel: IModelDb);
static readonly defaultQueryParams: ViewQueryParams;
getThumbnail(viewDefinitionId: Id64String): ThumbnailProps | undefined;
// (undocumented)
// @deprecated (undocumented)
getViewStateData(viewDefinitionId: string, options?: ViewStateLoadProps): ViewStateProps;
getViewStateProps(viewDefinitionId: string, options?: ViewStateLoadProps): Promise<ViewStateProps>;
iterateViews(params: ViewQueryParams, callback: (view: ViewDefinition) => boolean): boolean;
queryViewDefinitionProps(className?: string, limit?: number, offset?: number, wantPrivate?: boolean): ViewDefinitionProps[];
saveThumbnail(viewDefinitionId: Id64String, thumbnail: ThumbnailProps): number;
Expand Down
9 changes: 9 additions & 0 deletions common/api/core-common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4716,6 +4716,8 @@ export abstract class IModelReadRpcInterface extends RpcInterface {
// (undocumented)
queryEntityIds(_iModelToken: IModelRpcProps, _params: EntityQueryParams): Promise<Id64String[]>;
// (undocumented)
queryModelExtents(_iModelToken: IModelRpcProps, _modelIds: Id64String[]): Promise<ModelExtentsProps[]>;
// (undocumented)
queryModelProps(_iModelToken: IModelRpcProps, _params: EntityQueryParams): Promise<ModelProps[]>;
// (undocumented)
queryModelRanges(_iModelToken: IModelRpcProps, _modelIds: Id64String[]): Promise<Range3dProps[]>;
Expand Down Expand Up @@ -5516,6 +5518,13 @@ export class ModelClipGroups {
toJSON(): ModelClipGroupProps[];
}

// @public
export interface ModelExtentsProps {
extents: Range3dProps;
id: Id64String;
status: IModelStatus;
}

// @public
export interface ModelGeometryChanges {
readonly elements: Iterable<ElementGeometryChange>;
Expand Down
3 changes: 2 additions & 1 deletion common/api/core-frontend.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ import { MeshPolyline } from '@itwin/core-common';
import { MeshPolylineList } from '@itwin/core-common';
import { MessagePresenter } from '@itwin/appui-abstract';
import { MessageSeverity } from '@itwin/appui-abstract';
import { ModelExtentsProps } from '@itwin/core-common';
import { ModelGeometryChanges } from '@itwin/core-common';
import { ModelGeometryChangesProps } from '@itwin/core-common';
import { ModelIdAndGeometryGuid } from '@itwin/core-common';
Expand Down Expand Up @@ -3472,7 +3473,6 @@ export abstract class GeometricModelState extends ModelState implements Geometri
abstract get is3d(): boolean;
// @internal (undocumented)
get isGeometricModel(): boolean;
// @internal
queryModelRange(): Promise<Range3d>;
// @internal (undocumented)
get treeModelId(): Id64String;
Expand Down Expand Up @@ -4927,6 +4927,7 @@ export namespace IModelConnection {
// @internal (undocumented)
get loaded(): Map<string, ModelState>;
query(queryParams: ModelQueryParams): AsyncIterableIterator<ModelProps>;
queryExtents(modelIds: Id64String | Id64String[]): Promise<ModelExtentsProps[]>;
queryModelRanges(modelIds: Id64Arg): Promise<Range3dProps[]>;
queryProps(queryParams: ModelQueryParams): Promise<ModelProps[]>;
get repositoryModelId(): string;
Expand Down
1 change: 1 addition & 0 deletions common/api/summary/core-common.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ internal;MeshPolylineList
public;ModelClipGroup
public;ModelClipGroupProps
public;ModelClipGroups
public;ModelExtentsProps
public;ModelGeometryChanges
public;ModelGeometryChanges
public;ModelGeometryChangesProps
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-backend",
"comment": "Add asynchronous functions to obtain the extents of geometric models.",
"type": "none"
}
],
"packageName": "@itwin/core-backend"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-common",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/core-common"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-frontend",
"comment": "Add functions to obtain the extents of geometric models asynchronously on the backend.",
"type": "none"
}
],
"packageName": "@itwin/core-frontend"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-transformer",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/core-transformer"
}
61 changes: 18 additions & 43 deletions core/backend/src/CustomViewState3dCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { CompressedId64Set, Id64Array, Id64String, Logger, StopWatch } from "@itwin/core-bentley";
import { CustomViewState3dCreatorOptions, CustomViewState3dProps, IModelError, IModelStatus, QueryRowFormat } from "@itwin/core-common";
import { CustomViewState3dCreatorOptions, CustomViewState3dProps, QueryRowFormat } from "@itwin/core-common";
import { Range3d } from "@itwin/core-geometry";
import { BackendLoggerCategory } from "./BackendLoggerCategory";
import { IModelDb } from "./IModelDb";
Expand All @@ -20,16 +20,15 @@ export class CustomViewState3dCreator {
public constructor(iModel: IModelDb) {
this._imodel = iModel;
}
/**
* Gets default view state data such as category Ids and modelextents. If no model ids are passed in, all 3D models in the iModel are used.
/** Gets default view state data such as category Ids and modelextents. If no model ids are passed in, all 3D models in the iModel are used.
* @param [modelIds] Ids of models to display in the view.
* @throws [IModelError]($common) If no 3d models are found in the iModel.
* @returns CustomViewState3dProps
*/
public async getCustomViewState3dData(options: CustomViewState3dCreatorOptions): Promise<CustomViewState3dProps> {
let decompressedModelIds;
if (options?.modelIds !== undefined)
decompressedModelIds = CompressedId64Set.decompressArray(options.modelIds);

const models: Id64Array = decompressedModelIds ?? await this._getAllModels();
const categories: Id64Array = await this._getAllCategories();
const modelExtents: Range3d = await this._getModelExtents(models);
Expand All @@ -49,64 +48,40 @@ export class CustomViewState3dCreator {
return categories;
}

/**
* Gets the union of the extents of each model id passed in. Can return null range if no ids are passed, or no geometry found for the models.
* @param modelIdsList array of modelIds to get extents for
* @returns Range3d, the union of the extents of each model id
*/
private async _getModelExtents(modelIdsList: Id64String[]): Promise<Range3d> {
const modelExtents = new Range3d();
if (modelIdsList.length === 0)
return modelExtents;
const modelIds = new Set(modelIdsList);
for (const id of modelIds) {
const modelExtentsStopWatch = new StopWatch("getModelExtents query", false);
try {
await new Promise((resolve) => setImmediate(resolve)); // Free up main thread temporarily. Ideally we get queryModelExtents off the main thread and do not need to do this.
Logger.logInfo(loggerCategory, "Starting getModelExtents query.", {modelId: id});
modelExtentsStopWatch.start();
const props = this._imodel.nativeDb.queryModelExtents({ id }).modelExtents;
modelExtentsStopWatch.stop();
Logger.logInfo(loggerCategory, "Finished getModelExtents query.", {timeElapsedMs: modelExtentsStopWatch.elapsed, modelId: id});
modelExtents.union(Range3d.fromJSON(props), modelExtents);
} catch (err: any) {
modelExtentsStopWatch.stop();
if ((err as IModelError).errorNumber === IModelStatus.NoGeometry) { // if there was no geometry, just return null range
Logger.logInfo(loggerCategory, "Finished getModelExtents query with NoGeometry error.", {timeElapsedMs: modelExtentsStopWatch.elapsed, modelId: id});
continue;
}
Logger.logInfo(loggerCategory, "Finished getModelExtents query with error.", {timeElapsedMs: modelExtentsStopWatch.elapsed, modelId: id, errorMessage: err?.message, errorNumber: err?.errorNumber});
/** Compute the union of the extents of all the specified models. */
private async _getModelExtents(modelIds: Id64String[]): Promise<Range3d> {
if (modelIds.length === 0)
return new Range3d();

if (modelIds.size === 1)
throw err; // if they're asking for more than one model, don't throw on error.
continue;
}
}
return modelExtents;
const timer = new StopWatch("getModelExtents query", true);
const range = await this._imodel.models.queryRange(modelIds);

timer.stop();
Logger.logInfo(loggerCategory, "Finished getModelExtents query.", {timeElapsedMs: timer.elapsed});

return range;
}

/**
* Get all PhysicalModel ids in the iModel
*/
/** Get the Ids of all spatially-located, non-template 3d models in the iModel. */
private async _getAllModels(): Promise<Id64Array> {
// Note: IsNotSpatiallyLocated was introduced in a later version of the BisCore ECSchema.
// If the iModel has an earlier version, the statement will throw because the property does not exist.
// If the iModel was created from an earlier version and later upgraded to a newer version, the property may be NULL for models created prior to the upgrade.
const select = "SELECT ECInstanceId FROM Bis.GeometricModel3D WHERE IsPrivate = false AND IsTemplate = false";
const spatialCriterion = "AND (IsNotSpatiallyLocated IS NULL OR IsNotSpatiallyLocated = false)";

let models = [];
Logger.logInfo(loggerCategory, "Starting getAllModels query.");
try {
models = await this._executeQuery(`${select} ${spatialCriterion}`);
} catch {
models = await this._executeQuery(select);
}

Logger.logInfo(loggerCategory, "Finished getAllModels query.");
return models;
}
/**
* Helper function to execute ECSql queries.
*/

private _executeQuery = async (query: string) => {
const rows = [];
for await (const row of this._imodel.query(query, undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames }))
Expand Down
76 changes: 63 additions & 13 deletions core/backend/src/IModelDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
EntityMetaData, EntityProps, EntityQueryParams, FilePropertyProps, FontId, FontMap, FontType, GeoCoordinatesRequestProps,
GeoCoordinatesResponseProps, GeometryContainmentRequestProps, GeometryContainmentResponseProps, IModel, IModelCoordinatesRequestProps,
IModelCoordinatesResponseProps, IModelError, IModelNotFoundResponse, IModelTileTreeProps, LocalFileName, MassPropertiesRequestProps,
MassPropertiesResponseProps, ModelLoadProps, ModelProps, ModelSelectorProps, OpenBriefcaseProps, ProfileOptions, PropertyCallback, QueryBinder,
MassPropertiesResponseProps, ModelExtentsProps, ModelLoadProps, ModelProps, ModelSelectorProps, OpenBriefcaseProps, ProfileOptions, PropertyCallback, QueryBinder,
QueryOptions, QueryOptionsBuilder, QueryRowFormat, SchemaState, SheetProps, SnapRequestProps, SnapResponseProps, SnapshotOpenOptions,
SpatialViewDefinitionProps, SubCategoryResultRow, TextureData, TextureLoadProps, ThumbnailProps, UpgradeOptions, ViewDefinitionProps,
ViewQueryParams, ViewStateLoadProps, ViewStateProps,
Expand Down Expand Up @@ -1498,6 +1498,33 @@ export namespace IModelDb { // eslint-disable-line no-redeclare
}
});
}

/** For each specified [[GeometricModel]], attempts to obtain the union of the volumes of all geometric elements within that model.
* @param ids The Id or Ids of the [[GeometricModel]]s for which to obtain the extents.
* @returns An array of results, one per supplied Id, in the order in which the Ids were supplied. If the extents could not be obtained, the
* corresponding results entry's `extents` will be a "null" range (@see [Range3d.isNull]($geometry)) and its `status` will indicate
* why the extents could not be obtained (e.g., because the Id did not identify a [[GeometricModel]]).
* @see [[queryRange]] to obtain the union of all of the models' extents.
*/
public async queryExtents(ids: Id64String | Id64String[]): Promise<ModelExtentsProps[]> {
ids = typeof ids === "string" ? [ids] : ids;
if (ids.length === 0)
return [];

return this._iModel.nativeDb.queryModelExtentsAsync(ids);
}

/** Computes the union of the volumes of all geoemtric elements within any number of [[GeometricModel]]s, specified by model Id.
* @see [[queryExtents]] to obtain discrete volumes for each model.
*/
public async queryRange(ids: Id64String | Id64String[]): Promise<AxisAlignedBox3d> {
const results = await this.queryExtents(ids);
const range = new Range3d();
for (const result of results)
range.union(Range3d.fromJSON(result.extents), range);

return range;
}
}

/** The collection of elements in an [[IModelDb]].
Expand Down Expand Up @@ -2004,19 +2031,21 @@ export namespace IModelDb { // eslint-disable-line no-redeclare
return finished;
}

public getViewStateData(viewDefinitionId: string, options?: ViewStateLoadProps): ViewStateProps {
private loadViewStateProps(viewDefinitionElement: ViewDefinition, options?: ViewStateLoadProps, drawingExtents?: Range3d): ViewStateProps {
const elements = this._iModel.elements;
const viewDefinitionElement = elements.getElement<ViewDefinition>(viewDefinitionId);
const viewDefinitionProps = viewDefinitionElement.toJSON();
const categorySelectorProps = elements.getElementProps<CategorySelectorProps>(viewDefinitionProps.categorySelectorId);

const displayStyleOptions: ElementLoadProps = {
const displayStyleProps = elements.getElementProps<DisplayStyleProps>({
id: viewDefinitionProps.displayStyleId,
displayStyle: options?.displayStyle,
};
const displayStyleProps = elements.getElementProps<DisplayStyleProps>(displayStyleOptions);
});

const viewStateData: ViewStateProps = { viewDefinitionProps, displayStyleProps, categorySelectorProps };
const viewStateData: ViewStateProps = {
viewDefinitionProps,
displayStyleProps,
categorySelectorProps,
};

const modelSelectorId = (viewDefinitionProps as SpatialViewDefinitionProps).modelSelectorId;
if (modelSelectorId !== undefined) {
Expand All @@ -2029,12 +2058,8 @@ export namespace IModelDb { // eslint-disable-line no-redeclare
}));
} else if (viewDefinitionElement instanceof DrawingViewDefinition) {
// Ensure view has known extents
try {
const extentsJson = this._iModel.nativeDb.queryModelExtents({ id: viewDefinitionElement.baseModelId }).modelExtents;
viewStateData.modelExtents = Range3d.fromJSON(extentsJson);
} catch {
//
}
if (drawingExtents && !drawingExtents.isNull)
viewStateData.modelExtents = drawingExtents.toJSON();

// Include information about the associated [[SectionDrawing]], if any.
// NB: The SectionDrawing ECClass may not exist in the iModel's version of the BisCore ECSchema.
Expand All @@ -2055,6 +2080,31 @@ export namespace IModelDb { // eslint-disable-line no-redeclare
return viewStateData;
}

/** @deprecated use [[getViewStateProps]]. */
public getViewStateData(viewDefinitionId: string, options?: ViewStateLoadProps): ViewStateProps {
const view = this._iModel.elements.getElement<ViewDefinition>(viewDefinitionId);
let drawingExtents;
if (view instanceof DrawingViewDefinition) {
try {
drawingExtents = Range3d.fromJSON(this._iModel.nativeDb.queryModelExtents({ id: view.baseModelId }).modelExtents);
} catch {
//
}
}

return this.loadViewStateProps(view, options, drawingExtents);
}

/** Obtain a [ViewStateProps]($common) for a [[ViewDefinition]] specified by element Id. */
public async getViewStateProps(viewDefinitionId: string, options?: ViewStateLoadProps): Promise<ViewStateProps> {
const view = this._iModel.elements.getElement<ViewDefinition>(viewDefinitionId);
let drawingExtents;
if (view instanceof DrawingViewDefinition)
drawingExtents = (await this._iModel.models.queryRange(view.baseModelId));

return this.loadViewStateProps(view, options, drawingExtents);
}

private getViewThumbnailArg(viewDefinitionId: Id64String): FilePropertyProps {
return { namespace: "dgn_View", name: "Thumbnail", id: viewDefinitionId };
}
Expand Down
9 changes: 8 additions & 1 deletion core/backend/src/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,18 @@ export class GeometricModel extends Model {
/** @internal */
constructor(props: GeometricModelProps, iModel: IModelDb) { super(props, iModel); }

/** Query for the union of the extents of the elements contained by this model. */
/** Query for the union of the extents of the elements contained by this model.
* @note This function blocks the JavaScript event loop. Consider using [[queryRange]] instead.
*/
public queryExtents(): AxisAlignedBox3d {
const extents = this.iModel.nativeDb.queryModelExtents({ id: this.id }).modelExtents;
return Range3d.fromJSON(extents);
}

/** Query for the union of the extents of all elements contained within this model. */
public async queryRange(): Promise<AxisAlignedBox3d> {
return this.iModel.models.queryRange(this.id);
}
}

/** A container for persisting 3d geometric elements.
Expand Down
Loading

0 comments on commit d529a84

Please sign in to comment.