-
Notifications
You must be signed in to change notification settings - Fork 208
/
HiliteSetProvider.ts
196 lines (173 loc) · 6.13 KB
/
HiliteSetProvider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module UnifiedSelection
*/
import { from, Observable, shareReplay } from "rxjs";
import { eachValueFrom } from "rxjs-for-await";
import { Id64String } from "@itwin/core-bentley";
import { IModelConnection } from "@itwin/core-frontend";
import { ContentFlags, DEFAULT_KEYS_BATCH_SIZE, DefaultContentDisplayTypes, DescriptorOverrides, Item, Key, KeySet, Ruleset } from "@itwin/presentation-common";
import { Presentation } from "../Presentation";
import { TRANSIENT_ELEMENT_CLASSNAME } from "./SelectionManager";
/** @internal */
// eslint-disable-next-line @typescript-eslint/no-var-requires
export const HILITE_RULESET: Ruleset = require("./HiliteRules.json");
/**
* A set of model, subcategory and element ids that can be used for specifying
* viewport hilite.
*
* @public
*/
export interface HiliteSet {
models?: Id64String[];
subCategories?: Id64String[];
elements?: Id64String[];
}
/**
* Properties for creating a `HiliteSetProvider` instance.
* @public
*/
export interface HiliteSetProviderProps {
imodel: IModelConnection;
}
/**
* Presentation-based provider which uses presentation ruleset to determine
* what `HiliteSet` should be hilited in the graphics viewport based on the
* supplied `KeySet`.
*
* @public
*/
export class HiliteSetProvider {
private _imodel: IModelConnection;
private _cache: undefined | { keysGuid: string; observable: Observable<HiliteSet> };
private constructor(props: HiliteSetProviderProps) {
this._imodel = props.imodel;
}
/**
* Create a hilite set provider for the specified iModel.
*/
public static create(props: HiliteSetProviderProps) {
return new HiliteSetProvider(props);
}
/**
* Get hilite set for instances and/or nodes whose keys are specified in the
* given KeySet.
*
* Note: The provider caches result of the last request, so subsequent requests
* for the same input doesn't cost.
*/
public async getHiliteSet(selection: Readonly<KeySet>): Promise<HiliteSet> {
const modelIds = new Array<Id64String>();
const subCategoryIds = new Array<Id64String>();
const elementIds = new Array<Id64String>();
const iterator = this.getHiliteSetIterator(selection);
for await (const set of iterator) {
modelIds.push(...(set.models ?? []));
subCategoryIds.push(...(set.subCategories ?? []));
elementIds.push(...(set.elements ?? []));
}
return {
models: modelIds.length ? modelIds : undefined,
subCategories: subCategoryIds.length ? subCategoryIds : undefined,
elements: elementIds.length ? elementIds : undefined,
};
}
/**
* Get hilite set iterator for provided keys. It loads content in batches and
* yields hilite set created from each batch after it is loaded.
*/
public getHiliteSetIterator(selection: Readonly<KeySet>) {
if (!this._cache || this._cache.keysGuid !== selection.guid) {
this._cache = {
keysGuid: selection.guid,
observable: from(this.createHiliteSetIterator(selection)).pipe(shareReplay({ refCount: true })),
};
}
return eachValueFrom(this._cache.observable);
}
private async *createHiliteSetIterator(selection: Readonly<KeySet>) {
const { keys, transientIds } = this.handleTransientKeys(selection);
const { options, keyBatches } = this.getContentOptions(keys);
if (transientIds.length !== 0) {
yield { elements: transientIds };
}
for (const batch of keyBatches) {
let loadedItems = 0;
while (true) {
const content = await Presentation.presentation.getContentIterator({
...options,
paging: { start: loadedItems, size: CONTENT_SET_PAGE_SIZE },
keys: batch,
});
if (!content) {
break;
}
const items = new Array<Item>();
for await (const item of content.items) {
items.push(item);
}
const result = this.createHiliteSet(items);
yield result;
loadedItems += items.length;
if (loadedItems >= content.total) {
break;
}
}
}
}
private createHiliteSet(records: Item[]): HiliteSet {
const modelIds = new Array<Id64String>();
const subCategoryIds = new Array<Id64String>();
const elementIds = new Array<Id64String>();
records.forEach((rec) => {
const ids = isModelRecord(rec) ? modelIds : isSubCategoryRecord(rec) ? subCategoryIds : elementIds;
rec.primaryKeys.forEach((pk) => ids.push(pk.id));
});
return {
models: modelIds.length ? modelIds : undefined,
subCategories: subCategoryIds.length ? subCategoryIds : undefined,
elements: elementIds.length ? elementIds : undefined,
};
}
private getContentOptions(keys: KeySet) {
const descriptor: DescriptorOverrides = {
displayType: DefaultContentDisplayTypes.Viewport,
contentFlags: ContentFlags.KeysOnly,
};
const options = {
imodel: this._imodel,
rulesetOrId: HILITE_RULESET,
descriptor,
};
const keyBatches = new Array<KeySet>();
keys.forEachBatch(DEFAULT_KEYS_BATCH_SIZE, (batch: KeySet) => {
keyBatches.push(batch);
});
return {
options,
keyBatches,
};
}
private handleTransientKeys(selection: Readonly<KeySet>) {
// need to create a new set without transients
const transientIds = new Array<Id64String>();
const keys = new KeySet();
keys.add(selection, (key: Key) => {
if (Key.isInstanceKey(key) && key.className === TRANSIENT_ELEMENT_CLASSNAME) {
transientIds.push(key.id);
return false;
}
return true;
});
return {
transientIds,
keys,
};
}
}
const CONTENT_SET_PAGE_SIZE = 1000;
const isModelRecord = (rec: Item) => rec.extendedData && rec.extendedData.isModel;
const isSubCategoryRecord = (rec: Item) => rec.extendedData && rec.extendedData.isSubCategory;