/
Viewport.ts
3781 lines (3249 loc) · 167 KB
/
Viewport.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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Views
*/
import {
asInstanceOf, assert, BeDuration, BeEvent, BeTimePoint, Constructor, dispose, Id64, Id64Arg, Id64Set, Id64String, IDisposable, isInstanceOf,
StopWatch,
} from "@itwin/core-bentley";
import {
Angle, AngleSweep, Arc3d, Geometry, LowAndHighXY, LowAndHighXYZ, Map4d, Matrix3d, Plane3dByOriginAndUnitNormal, Point2d, Point3d, Point4d, Range1d,
Range3d, Ray3d, Transform, Vector3d, XAndY, XYAndZ, XYZ,
} from "@itwin/core-geometry";
import {
AnalysisStyle, BackgroundMapProps, BackgroundMapProviderProps, BackgroundMapSettings, Camera, CartographicRange, ClipStyle, ColorDef, DisplayStyleSettingsProps,
Easing, ElementProps, FeatureAppearance, Frustum, GlobeMode, GridOrientationType, Hilite, ImageBuffer,
Interpolation, isPlacement2dProps, LightSettings, ModelMapLayerSettings, Npc, NpcCenter, Placement,
Placement2d, Placement3d, PlacementProps, SolarShadowSettings, SubCategoryAppearance, SubCategoryOverride, ViewFlags,
} from "@itwin/core-common";
import { AuxCoordSystemState } from "./AuxCoordSys";
import { BackgroundMapGeometry } from "./BackgroundMapGeometry";
import { ChangeFlag, ChangeFlags, MutableChangeFlags } from "./ChangeFlags";
import { CoordSystem } from "./CoordSystem";
import { DecorationsCache } from "./DecorationsCache";
import { DisplayStyleState } from "./DisplayStyleState";
import { ElementPicker, LocateOptions } from "./ElementLocateManager";
import { FeatureOverrideProvider } from "./FeatureOverrideProvider";
import { FrustumAnimator } from "./FrustumAnimator";
import { GlobeAnimator } from "./GlobeAnimator";
import { HitDetail, SnapDetail } from "./HitDetail";
import { IModelApp } from "./IModelApp";
import { IModelConnection } from "./IModelConnection";
import { linePlaneIntersect } from "./LinePlaneIntersect";
import { ToolTipOptions } from "./NotificationManager";
import { PerModelCategoryVisibility } from "./PerModelCategoryVisibility";
import { Decorations } from "./render/Decorations";
import { FeatureSymbology } from "./render/FeatureSymbology";
import { FrameStats, FrameStatsCollector } from "./render/FrameStats";
import { GraphicType } from "./render/GraphicBuilder";
import { AnimationBranchStates } from "./render/GraphicBranch";
import { Pixel } from "./render/Pixel";
import { GraphicList } from "./render/RenderGraphic";
import { RenderMemory } from "./render/RenderMemory";
import { createRenderPlanFromViewport } from "./render/RenderPlan";
import { RenderTarget } from "./render/RenderTarget";
import { StandardView, StandardViewId } from "./StandardView";
import { SubCategoriesCache } from "./SubCategoriesCache";
import {
DisclosedTileTreeSet, MapCartoRectangle, MapFeatureInfo, MapFeatureInfoOptions, MapLayerFeatureInfo, MapLayerImageryProvider, MapLayerIndex, MapLayerInfoFromTileTree, MapTiledGraphicsProvider,
MapTileTreeReference, MapTileTreeScaleRangeVisibility, TileBoundingBoxes, TiledGraphicsProvider, TileTreeLoadStatus, TileTreeReference, TileUser,
} from "./tile/internal";
import { EventController } from "./tools/EventController";
import { ToolSettings } from "./tools/ToolSettings";
import { Animator, MarginOptions, OnViewExtentsError, ViewAnimationOptions, ViewChangeOptions } from "./ViewAnimation";
import { DecorateContext, SceneContext } from "./ViewContext";
import { GlobalLocation, viewGlobalLocation, ViewGlobalLocationConstants } from "./ViewGlobalLocation";
import { ViewingSpace } from "./ViewingSpace";
import { ViewPose } from "./ViewPose";
import { ViewRect } from "./common/ViewRect";
import { ModelDisplayTransformProvider, ViewState } from "./ViewState";
import { ViewStatus } from "./ViewStatus";
import { queryVisibleFeatures, QueryVisibleFeaturesCallback, QueryVisibleFeaturesOptions } from "./render/VisibleFeature";
import { FlashSettings } from "./FlashSettings";
import { GeometricModelState } from "./ModelState";
// cSpell:Ignore rect's ovrs subcat subcats unmounting UI's
/** Interface for drawing [[Decorations]] into, or on top of, a [[ScreenViewport]].
* @public
*/
export interface ViewportDecorator {
/** Override to enable cached decorations for this decorator.
* By default, a decorator is asked to recreate its decorations from scratch via its [[decorate]] method whenever the viewport's decorations are invalidated.
* Decorations become invalidated for a variety of reasons, including when the scene changes and when the mouse moves.
* Most decorators care only about when the scene changes, and may create decorations that are too expensive to recreate on every mouse motion.
* If `useCachedDecorations` is true, then the viewport will cache the most-recently-created decorations for this decorator, and only invoke its [[decorate]] method if it has no cached decorations for it.
* The cached decorations are discarded:
* - Whenever the scene changes; and
* - When the decorator explicitly requests it via [[Viewport.invalidateCachedDecorations]] or [[ViewManager.invalidateCachedDecorationsAllViews]].
* The decorator should invoke the latter when the criteria governing its decorations change.
*/
readonly useCachedDecorations?: true;
/** Implement this method to add [[Decorations]] into the supplied DecorateContext.
* @see [[useCachedDecorations]] to avoid unnecessarily recreating decorations.
*/
decorate(context: DecorateContext): void;
}
/** Source of depth point returned by [[Viewport.pickDepthPoint]].
* @public
*/
export enum DepthPointSource {
/** Depth point from geometry within specified radius of pick point */
Geometry, // eslint-disable-line @typescript-eslint/no-shadow
/** Depth point from reality model within specified radius of pick point */
Model,
/** Depth point from ray projection to background map plane */
BackgroundMap,
/** Depth point from ray projection to ground plane */
GroundPlane,
/** Depth point from ray projection to grid plane */
Grid,
/** Depth point from ray projection to acs plane */
ACS,
/** Depth point from plane passing through view target point */
TargetPoint,
/** Depth point from map/terrain within specified radius of pick point */
Map,
}
/** Options to control behavior of [[Viewport.pickDepthPoint]].
* @public
*/
export interface DepthPointOptions {
/** If true, geometry with the "non-locatable" flag set will not be selected. */
excludeNonLocatable?: boolean;
/** If true, geometry from pickable decorations will not be selected. */
excludeDecorations?: boolean;
/** If true, geometry from an IModelConnection other than the one associated with the Viewport will not be selected. */
excludeExternalIModels?: boolean;
}
/** The minimum and maximum values for the z-depth of a rectangle of screen space.
* Values are in [[CoordSystem.Npc]] so they will be between 0 and 1.0.
* @public
*/
export interface DepthRangeNpc {
/** The value closest to the back. */
minimum: number;
/** The value closest to the front. */
maximum: number;
}
/** Options to allow changing the view rotation with zoomTo methods and ensure minimum bounding box dimensions for zoomToElements.
* @public
*/
export interface ZoomToOptions {
/** Set view rotation from standard view identifier. */
standardViewId?: StandardViewId;
/** Set view rotation relative to placement of first element or props entry. */
placementRelativeId?: StandardViewId;
/** Set view rotation from Matrix3d. */
viewRotation?: Matrix3d;
/** Ensure minimum element-aligned bounding box dimensions in meters (3d only). */
minimumDimension?: number;
}
/** Options for changing the viewed Model of a 2d view via [[Viewport.changeViewedModel2d]]
* @public
*/
export interface ChangeViewedModel2dOptions {
/** If true, perform a "fit view" operation after changing to the new 2d model. */
doFit?: boolean;
}
/** Describes an undo or redo event for a [[Viewport]].
* @see [[Viewport.onViewUndoRedo]].
* @public
*/
export enum ViewUndoEvent { Undo = 0, Redo = 1 }
/** @internal */
export const ELEMENT_MARKED_FOR_REMOVAL = Symbol.for("@bentley/imodeljs/Viewport/__element_marked_for_removal__");
declare global {
interface Element {
[ELEMENT_MARKED_FOR_REMOVAL]?: boolean;
}
}
/** Payload for the [[Viewport.onFlashedIdChanged]] event indicating Ids of the currently- and/or previously-flashed objects.
* @public
*/
export type OnFlashedIdChangedEventArgs = {
readonly current: Id64String;
readonly previous: Id64String;
} | {
readonly current: Id64String;
readonly previous: undefined;
} | {
readonly previous: Id64String;
readonly current: undefined;
};
/** Arguments to [[Viewport.getPixelDataWorldPoint]].
* @public
*/
export interface GetPixelDataWorldPointArgs {
/** The buffer containing the pixel data. @see [[Viewport.readPixels]]. */
pixels: Pixel.Buffer;
/** The x coordinate of the pixel of interest, in view coordinates. */
x: number;
/** The y coordinate of the pixel of interest, in view coordinates. */
y: number;
/** If true, then the world point of a pixel associated with a model will preserve any transforms applied to the model at display time,
* such as those supplied by a [[ModelDisplayTransformProvider]] or [PlanProjectionSettings.elevation]($common).
* Otherwise, the world point will be multiplied by the inverse of any such transforms to correlate it with the model's true coordinate space.
*/
preserveModelDisplayTransforms?: boolean;
/** If supplied, this point will be modified to store the returned point, instead of allocating a new point. */
out?: Point3d;
}
/** Arguments supplied to [[Viewport.readImageBuffer]].
* @public
*/
export interface ReadImageBufferArgs {
/** The region of the viewport's [[ViewRect]] to capture. It must be fully contained within [[Viewport.viewRect]].
* If unspecified, the entirety of the viewport's view rect is captured.
*/
rect?: ViewRect;
/** Optional dimensions to which to resize the captured image. If the aspect ratio of these dimensions does not match that of the captured image,
* horizontal or vertical bars will be added to the resized image using the viewport's background color.
* If unspecified, the image will not be resized.
*/
size?: XAndY;
/** The image captured by WebGL appears "upside-down" and must be flipped to appear right-side-up; if true, this flipping will not be performed.
* This provides a performance optimization for uncommon cases in which an upside-down image is actually preferred.
*/
upsideDown?: boolean;
}
/** MapLayer visibility based on its scale range definition.
* @beta
*/
export interface MapLayerScaleRangeVisibility {
/** True if map-layer is part of [[DisplayStyleState]]'s overlay map, otherwise map-layer is part of [[DisplayStyleState]]'s background map
* @see [[DisplayStyleState.mapLayerAtIndex]].
*/
isOverlay: boolean;
/** Index of the map-layer in [[DisplayStyleState]]'s background/overlay map
* @see [[DisplayStyleState.mapLayerAtIndex]].
*/
index: number;
/** Scale range visibility value of the map-layer */
visibility: MapTileTreeScaleRangeVisibility;
}
/** A Viewport renders the contents of one or more [GeometricModel]($backend)s onto an `HTMLCanvasElement`.
*
* It holds a [[ViewState]] object that defines its viewing parameters; the ViewState in turn defines the [[DisplayStyleState]],
* [[CategorySelectorState]], and - for [[SpatialViewState]]s - the [[ModelSelectorState]]. While a ViewState is being displayed by a Viewport,
* it is considered to be "attached" to that viewport; it remains attached until the Viewport is disposed of or becomes attached to a different ViewState.
* While the ViewState is attached to a Viewport, any changes made to the ViewState or its display style or category/model selectors will be automatically
* reflected in the Viewport. A ViewState can be attached to no more than one Viewport at a time.
*
* As changes to ViewState are made, Viewports also hold a stack of *previous copies* of it, to allow
* for undo/redo (i.e. *View Previous* and *View Next*) of viewing tools.
*
* Changes to a Viewport's state can be monitored by attaching an event listener to a variety of specific events. Most such events are
* triggered only once per frame, just before the Viewport's contents are rendered. For example, if the following sequence of events occurs:
*
* * First frame is rendered
* * ViewFlags are modified
* * ViewFlags are modified again
* * Second frame is rendered
*
* The [[Viewport.onDisplayStyleChanged]] event will be invoked exactly once, when the second frame is rendered.
*
* @see [[ScreenViewport]] for a viewport that can render onto the screen.
* @see [[OffScreenViewport]] for a viewport that can render into an off-screen buffer.
* @public
* @extensions
*/
export abstract class Viewport implements IDisposable, TileUser {
/** Event called whenever this viewport is synchronized with its [[ViewState]].
* @note This event is invoked *very* frequently. To avoid negatively impacting performance, consider using one of the more specific Viewport events;
* otherwise, avoid performing excessive computations in response to this event.
* @see [[onViewportChanged]] for receiving events at more regular intervals with more specific information about what changed.
* @see [[onChangeView]] for an event raised specifically when a different [[ViewState]] becomes associated with the viewport.
*/
public readonly onViewChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called after reversing the most recent change to the Viewport from the undo stack or reapplying the
* most recently undone change to the Viewport from the redo stack.
*/
public readonly onViewUndoRedo = new BeEvent<(vp: Viewport, event: ViewUndoEvent) => void>();
/** Event called on the next frame after this viewport's set of always-drawn elements changes. */
public readonly onAlwaysDrawnChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after this viewport's set of never-drawn elements changes. */
public readonly onNeverDrawnChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after this viewport's [[DisplayStyleState]] or its members change.
* Aspects of the display style include [ViewFlags]($common), [SubCategoryOverride]($common)s, and [[Environment]] settings.
*/
public readonly onDisplayStyleChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after this viewport's set of displayed categories changes. */
public readonly onViewedCategoriesChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after this viewport's set of [[PerModelCategoryVisibility.Overrides]] changes. */
public readonly onViewedCategoriesPerModelChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after this viewport's set of displayed models changes. */
public readonly onViewedModelsChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after this viewport's [[FeatureOverrideProvider]] changes,
* or the internal state of the provider changes such that the overrides needed to be recomputed.
*/
public readonly onFeatureOverrideProviderChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after this viewport's [[FeatureSymbology.Overrides]] change. */
public readonly onFeatureOverridesChanged = new BeEvent<(vp: Viewport) => void>();
/** Event called on the next frame after any of the viewport's [[ChangeFlags]] changes. */
public readonly onViewportChanged = new BeEvent<(vp: Viewport, changed: ChangeFlags) => void>();
/** Event invoked immediately when [[changeView]] is called to replace the current [[ViewState]] with a different one. */
public readonly onChangeView = new BeEvent<(vp: Viewport, previousViewState: ViewState) => void>();
/** Event invoked immediately when the viewport is disposed.
* @see [[Viewport.dispose]].
*/
public readonly onDisposed = new BeEvent<(vp: Viewport) => void>();
/** Event invoked after [[renderFrame]] detects that the dimensions of the viewport's [[ViewRect]] have changed.
*/
public readonly onResized = new BeEvent<(vp: Viewport) => void>();
/** Event dispatched immediately after [[flashedId]] changes, supplying the Ids of the previously and/or currently-flashed objects.
* @note Attempting to assign to [[flashedId]] from within the event callback will produce an exception.
*/
public readonly onFlashedIdChanged = new BeEvent<(vp: Viewport, args: OnFlashedIdChangedEventArgs) => void>();
/** Event indicating when a map-layer scale range visibility change for the current viewport scale.
* @beta
*/
public readonly onMapLayerScaleRangeVisibilityChanged = new BeEvent<(layerIndexes: MapLayerScaleRangeVisibility[]) => void>();
/** Event invoked every time [[invalidateScene]] is called.
* @note This event will be raised **very** frequently. Avoid doing significant work inside of your event listener.
* @beta
*/
public readonly onSceneInvalidated = new BeEvent<(vp: Viewport) => void>();
/** @internal */
protected _hasMissingTiles = false;
/** This is initialized by a call to [[changeView]] sometime shortly after the constructor is invoked.
* During that time it can be undefined. DO NOT assign directly to this member - use `setView()`.
*/
private _view!: ViewState;
/** A function executed by `setView()` when `this._view` changes. */
private readonly _detachFromView: VoidFunction[] = [];
private readonly _detachFromDisplayStyle: VoidFunction[] = [];
private readonly _viewportId: number;
private _doContinuousRendering = false;
/** @internal */
protected _inViewChangedEvent = false;
/** If false, indicates that [[Decorations]] should be recreated when rendering the next frame.
* @note prefer to invoke [[invalidateDecorations]] rather than directly assigning to this property.
*/
protected _decorationsValid = false;
/** @internal */
protected _sceneValid = false;
/** @internal */
public get sceneValid() { return this._sceneValid; }
/** @internal */
protected _renderPlanValid = false;
/** @internal */
public get renderPlanValid() { return this._renderPlanValid; }
/** @internal */
public setRenderPlanValid() { this._renderPlanValid = true; }
/** @internal */
protected _controllerValid = false;
/** @internal */
public get controllerValid() { return this._controllerValid; }
private _redrawPending = false;
private _analysisFractionValid = false;
/** @internal */
public get analysisFractionValid() { return this._analysisFractionValid; }
private _timePointValid = false;
/** @internal */
public get timePointValid() { return this._timePointValid; }
/** Strictly for tests. @internal */
public setAllValid(): void {
this._sceneValid = this._decorationsValid = this._renderPlanValid = this._controllerValid = this._redrawPending
= this._analysisFractionValid = this._timePointValid = true;
}
/** Mark the current set of decorations invalid, so that they will be recreated on the next render frame.
* This can be useful, for example, if an external event causes one or more current decorations to become invalid and you wish to force
* them to be recreated to show the changes.
* @note On the next frame, the `decorate` method of all [[ViewManager.decorators]] will be called. There is no way (or need) to
* invalidate individual decorations.
*/
public invalidateDecorations(): void {
this._decorationsValid = false;
IModelApp.requestNextAnimation();
}
/** Mark the viewport's scene as having changed, so that the next call to [[renderFrame]] will recreate it.
* This method is not typically invoked directly - the scene is automatically invalidated in response to events such as moving the viewing frustum,
* changing the set of viewed models, new tiles being loaded, etc.
*/
public invalidateScene(): void {
this._sceneValid = false;
this._timePointValid = false;
this.onSceneInvalidated.raiseEvent(this);
this.invalidateDecorations();
}
/** Mark the viewport's "render plan" as having changed, so that the next call to [[renderFrame]] will recreate it.
* This method is not typically invoked directly - the render plan is automatically invalidated in response to events such as changing aspects
* of the viewport's [[displayStyle]].
*/
public invalidateRenderPlan(): void {
this._renderPlanValid = false;
this.invalidateScene();
}
/** Mark the viewport's [[ViewState]] as having changed, so that the next call to [[renderFrame]] will invoke [[setupFromView]] to synchronize with the view.
* This method is not typically invoked directly - the controller is automatically invalidated in response to events such as a call to [[changeView]].
*/
public invalidateController(): void {
this._controllerValid = this._analysisFractionValid = false;
this.invalidateRenderPlan();
}
/** @internal */
public setValidScene() {
this._sceneValid = true;
}
/** Request that the Viewport redraw its contents on the next frame. This is useful when some state outside of the Viewport's control but affecting its display has changed.
* For example, if the parameters affecting a screen-space effect applied to this Viewport are modified, the Viewport's contents should be redrawn to reflect the change.
* @note This does not necessarily cause the viewport to recreate its scene, decorations, or anything else - it only guarantees that the contents will be repainted.
*/
public requestRedraw(): void {
this._redrawPending = true;
IModelApp.requestNextAnimation();
}
private _animator?: Animator;
/** @internal */
protected _changeFlags = new MutableChangeFlags();
private _selectionSetDirty = true;
private readonly _perModelCategoryVisibility: PerModelCategoryVisibility.Overrides;
private _tileSizeModifier?: number;
/** @internal */
public readonly subcategories = new SubCategoriesCache.Queue();
/** Time the current flash started. */
private _flashUpdateTime?: BeTimePoint;
/** Current flash intensity from [0..this.flashSettings.maxIntensity] */
private _flashIntensity = 0;
/** Id of the currently flashed element. */
private _flashedElem?: string;
/** Id of last flashed element. */
private _lastFlashedElem?: string;
/** The Id of the most recently flashed element, if any. */
public get lastFlashedElementId(): Id64String | undefined {
return this._lastFlashedElem;
}
private _wantViewAttachments = true;
/** For debug purposes, controls whether or not view attachments are displayed in sheet views.
* @internal
*/
public get wantViewAttachments() { return this._wantViewAttachments; }
public set wantViewAttachments(want: boolean) {
if (want !== this._wantViewAttachments) {
this._wantViewAttachments = want;
this.invalidateScene();
}
}
private _wantViewAttachmentBoundaries = false;
/** For debug purposes, controls whether or not the boundary of each view attachment is displayed in a sheet view.
* @internal
*/
public get wantViewAttachmentBoundaries() { return this._wantViewAttachmentBoundaries; }
public set wantViewAttachmentBoundaries(want: boolean) {
if (want !== this._wantViewAttachmentBoundaries) {
this._wantViewAttachmentBoundaries = want;
this.invalidateScene();
}
}
private _wantViewAttachmentClipShapes = false;
/** For debug purposes, controls whether or not graphics representing the clipping shapes of each view attachment are displayed in a sheet view.
* @internal
*/
public get wantViewAttachmentClipShapes() { return this._wantViewAttachmentClipShapes; }
public set wantViewAttachmentClipShapes(want: boolean) {
if (want !== this._wantViewAttachmentClipShapes) {
this._wantViewAttachmentClipShapes = want;
this.invalidateScene();
}
}
/** Don't allow entries in the view undo buffer unless they're separated by more than this amount of time. */
public static undoDelay = BeDuration.fromSeconds(.5);
private _debugBoundingBoxes: TileBoundingBoxes = TileBoundingBoxes.None;
private _freezeScene = false;
private _viewingSpace!: ViewingSpace;
private _target?: RenderTarget;
private _fadeOutActive = false;
private _neverDrawn?: Id64Set;
private _alwaysDrawn?: Id64Set;
private _alwaysDrawnExclusive: boolean = false;
private readonly _featureOverrideProviders: FeatureOverrideProvider[] = [];
private readonly _tiledGraphicsProviders = new Set<TiledGraphicsProvider>();
private _mapTiledGraphicsProvider?: MapTiledGraphicsProvider;
private _hilite = new Hilite.Settings();
private _emphasis = new Hilite.Settings(ColorDef.black, 0, 0, Hilite.Silhouette.Thick);
private _flash = new FlashSettings();
/** See [DisplayStyle3dSettings.lights]($common) */
public get lightSettings(): LightSettings | undefined {
return this.displayStyle.is3d() ? this.displayStyle.settings.lights : undefined;
}
public setLightSettings(settings: LightSettings) {
if (this.displayStyle.is3d())
this.displayStyle.settings.lights = settings;
}
/** See [DisplayStyle3dSettings.solarShadows]($common) */
public get solarShadowSettings(): SolarShadowSettings | undefined {
return this.view.displayStyle.is3d() ? this.view.displayStyle.settings.solarShadows : undefined;
}
public setSolarShadowSettings(settings: SolarShadowSettings) {
if (this.view.displayStyle.is3d())
this.view.displayStyle.solarShadows = settings;
}
/** @public */
public get viewingSpace(): ViewingSpace { return this._viewingSpace; }
/** This viewport's rotation matrix. */
public get rotation(): Matrix3d { return this._viewingSpace.rotation; }
/** The vector between the opposite corners of this viewport's extents. */
public get viewDelta(): Vector3d { return this._viewingSpace.viewDelta; }
/** Provides conversions between world and view coordinates. */
public get worldToViewMap(): Map4d { return this._viewingSpace.worldToViewMap; }
/** Provides conversions between world and Npc (non-dimensional perspective) coordinates. */
public get worldToNpcMap(): Map4d { return this._viewingSpace.worldToNpcMap; }
/** @internal */
public get frustFraction(): number { return this._viewingSpace.frustFraction; }
/** See [DisplayStyleSettings.analysisFraction]($common). */
public get analysisFraction(): number {
return this.displayStyle.settings.analysisFraction;
}
public set analysisFraction(fraction: number) {
this.displayStyle.settings.analysisFraction = fraction;
}
/** See [DisplayStyleSettings.timePoint]($common) */
public get timePoint(): number | undefined {
return this.displayStyle.settings.timePoint;
}
public set timePoint(time: number | undefined) {
this.displayStyle.settings.timePoint = time;
}
/** @internal */
protected readonly _viewRange: ViewRect = new ViewRect();
/** Get the rectangle of this Viewport in [[CoordSystem.View]] coordinates.
* @note Do not modify the ViewRect's properties.
*/
public abstract get viewRect(): ViewRect;
/** @internal */
public get isAspectRatioLocked(): boolean { return false; }
/** @internal */
public get target(): RenderTarget {
assert(undefined !== this._target, "Accessing RenderTarget of a disposed Viewport");
return this._target;
}
/** Returns true if this Viewport's [[dispose]] method has been invoked. It is an error to attempt to interact with a disposed Viewport.
* Typically a [[ScreenViewport]] becomes disposed as a result of a call to [[ViewManager.dropViewport]], often indirectly through the unmounting of a nine-zone UI's [[ViewportComponent]] when, e.g., switching front-stages.
* @public
*/
public get isDisposed(): boolean {
return undefined === this._target;
}
/** The settings that control how elements are hilited in this Viewport. */
public get hilite(): Hilite.Settings { return this._hilite; }
public set hilite(hilite: Hilite.Settings) {
this._hilite = hilite;
this.invalidateRenderPlan();
}
/** The settings that control how emphasized elements are displayed in this Viewport. The default settings apply a thick black silhouette to the emphasized elements.
* @see [FeatureAppearance.emphasized]($common).
*/
public get emphasisSettings(): Hilite.Settings { return this._emphasis; }
public set emphasisSettings(settings: Hilite.Settings) {
this._emphasis = settings;
this.invalidateRenderPlan();
}
/** The settings that control how elements are flashed in this viewport. */
public get flashSettings(): FlashSettings {
return this._flash;
}
public set flashSettings(settings: FlashSettings) {
this._flash = settings;
this.invalidateRenderPlan();
}
/** Determine whether the Grid display is currently enabled in this Viewport.
* @return true if the grid display is on.
*/
public get isGridOn(): boolean { return this.viewFlags.grid; }
/** Flags controlling aspects of how the contents of this viewport are rendered.
* @see [DisplayStyleSettings.viewFlags]($common).
*/
public get viewFlags(): ViewFlags { return this.view.viewFlags; }
public set viewFlags(viewFlags: ViewFlags) {
this.view.displayStyle.viewFlags = viewFlags;
}
/** See [[ViewState.displayStyle]] */
public get displayStyle(): DisplayStyleState { return this.view.displayStyle; }
public set displayStyle(style: DisplayStyleState) {
this.view.displayStyle = style;
}
/** Selectively override aspects of this viewport's display style.
* @see [DisplayStyleSettings.applyOverrides]($common)
*/
public overrideDisplayStyle(overrides: DisplayStyleSettingsProps): void {
this.displayStyle.settings.applyOverrides(overrides);
}
/** See [DisplayStyleSettings.clipStyle]($common) */
public get clipStyle(): ClipStyle { return this.displayStyle.settings.clipStyle; }
public set clipStyle(style: ClipStyle) {
this.displayStyle.settings.clipStyle = style;
}
/** Sets the number of [MSAA]($docs/learning/display/MSAA.md) samples for this viewport.
* The number of samples is a power of two. Values of 1 or less indicates anti-aliasing should be disabled. Non-power-of-two values are rounded
* down to the nearest power of two. The maximum number of samples supported depends upon the client's graphics hardware capabilities. Higher values produce
* a higher-quality image but also may also reduce framerate.
* @see [[ViewManager.setAntialiasingAllViews]] to adjust the number of samples for all viewports.
*/
public get antialiasSamples(): number {
return undefined !== this._target ? this._target.antialiasSamples : 1;
}
public set antialiasSamples(numSamples: number) {
if (undefined !== this._target) {
this._target.antialiasSamples = numSamples;
this.invalidateRenderPlan();
}
}
/** return true if viewing globe (globeMode is 3D and eye location is far above globe
* @alpha
*/
public get viewingGlobe() {
const view = this.view;
if (!view.is3d())
return false;
return this.displayStyle.globeMode === GlobeMode.Ellipsoid && view.isGlobalView;
}
/** Remove any [[SubCategoryOverride]] for the specified subcategory.
* @param id The Id of the subcategory.
* @see [[overrideSubCategory]]
*/
public dropSubCategoryOverride(id: Id64String): void {
this.view.displayStyle.dropSubCategoryOverride(id);
}
/** Override the symbology of geometry belonging to a specific subcategory when rendered within this viewport.
* @param id The Id of the subcategory.
* @param ovr The symbology overrides to apply to all geometry belonging to the specified subcategory.
* @see [[dropSubCategoryOverride]]
*/
public overrideSubCategory(id: Id64String, ovr: SubCategoryOverride): void {
this.view.displayStyle.overrideSubCategory(id, ovr);
}
/** Query the symbology overrides applied to geometry belonging to a specific subcategory when rendered within this viewport.
* @param id The Id of the subcategory.
* @return The symbology overrides applied to all geometry belonging to the specified subcategory, or undefined if no such overrides exist.
* @see [[overrideSubCategory]]
*/
public getSubCategoryOverride(id: Id64String): SubCategoryOverride | undefined {
return this.view.displayStyle.getSubCategoryOverride(id);
}
/** Query the symbology with which geometry belonging to a specific subcategory is rendered within this viewport.
* Every [[SubCategory]] defines a base symbology independent of any [[Viewport]].
* If a [[SubCategoryOverride]] has been applied to the subcategory within the context of this [[Viewport]], it will be applied to the subcategory's base symbology.
* @param id The Id of the subcategory.
* @return The symbology of the subcategory within this viewport, including any overrides.
* @see [[overrideSubCategory]]
*/
public getSubCategoryAppearance(id: Id64String): SubCategoryAppearance {
const app = this.iModel.subcategories.getSubCategoryAppearance(id);
if (undefined === app)
return SubCategoryAppearance.defaults;
const ovr = this.getSubCategoryOverride(id);
return undefined !== ovr ? ovr.override(app) : app;
}
/** Determine whether geometry belonging to a specific SubCategory is visible in this viewport, assuming the containing Category is displayed.
* @param id The Id of the subcategory
* @returns true if the subcategory is visible in this viewport.
* @note Because this function does not know the Id of the containing Category, it does not check if the Category is enabled for display. The caller should check that separately if he knows the Id of the Category.
*/
public isSubCategoryVisible(id: Id64String): boolean { return this.view.isSubCategoryVisible(id); }
/** Override the appearance of a model when rendered within this viewport.
* @param id The Id of the model.
* @param ovr The symbology overrides to apply to all geometry belonging to the specified subcategory.
* @see [DisplayStyleSettings.overrideModelAppearance]($common)
*/
public overrideModelAppearance(id: Id64String, ovr: FeatureAppearance): void {
this.view.displayStyle.settings.overrideModelAppearance(id, ovr);
}
/** Remove any model appearance override for the specified model.
* @param id The Id of the model.
* @see [DisplayStyleSettings.dropModelAppearanceOverride]($common)
*/
public dropModelAppearanceOverride(id: Id64String): void {
this.view.displayStyle.settings.dropModelAppearanceOverride(id);
}
/** Some changes may or may not require us to invalidate the scene.
* Specifically, when shadows are enabled or we are displaying view attachments, the following changes may affect the visibility or transparency of elements or features:
* - Viewed categories and subcategories;
* - Always/never drawn elements
* - Symbology overrides.
*/
private maybeInvalidateScene(): void {
// When shadows are being displayed and the set of displayed categories changes, we must invalidate the scene so that shadows will be regenerated.
// Same occurs when changing feature symbology overrides (e.g., always/never-drawn element sets, transparency override)
if (!this._sceneValid)
return;
if (this.view.displayStyle.wantShadows || this.view.isSheetView())
this.invalidateScene();
}
/** Enable or disable display of elements belonging to a set of categories specified by Id.
* Visibility of individual subcategories belonging to a category can be controlled separately through the use of [[SubCategoryOverride]]s.
* By default, enabling display of a category does not affect display of subcategories thereof which have been overridden to be invisible.
* @param categories The Id(s) of the categories to which the change should be applied. No other categories will be affected.
* @param display Whether or not elements on the specified categories should be displayed in the viewport.
* @param enableAllSubCategories Specifies that when enabling display for a category, all of its subcategories should also be displayed even if they are overridden to be invisible.
*/
public changeCategoryDisplay(categories: Id64Arg, display: boolean, enableAllSubCategories: boolean = false): void {
if (!display) {
this.view.categorySelector.dropCategories(categories);
return;
}
this.view.categorySelector.addCategories(categories);
const categoryIds = Id64.toIdSet(categories);
this.updateSubCategories(categoryIds, enableAllSubCategories);
}
private updateSubCategories(categoryIds: Id64Arg, enableAllSubCategories: boolean): void {
this.subcategories.push(this.iModel.subcategories, categoryIds, () => {
if (enableAllSubCategories)
this.enableAllSubCategories(categoryIds);
this._changeFlags.setViewedCategories();
});
}
private enableAllSubCategories(categoryIds: Id64Arg): void {
if (this.displayStyle.enableAllLoadedSubCategories(categoryIds))
this.maybeInvalidateScene();
}
/** @internal */
public getSubCategories(categoryId: Id64String): Id64Set | undefined { return this.iModel.subcategories.getSubCategories(categoryId); }
/** Change the visibility of geometry belonging to the specified subcategory when displayed in this viewport.
* @param subCategoryId The Id of the subcategory
* @param display: True to make geometry belonging to the subcategory visible within this viewport, false to make it invisible.
*/
public changeSubCategoryDisplay(subCategoryId: Id64String, display: boolean): void {
if (this.displayStyle.setSubCategoryVisible(subCategoryId, display))
this.maybeInvalidateScene();
}
/** The settings controlling how a background map is displayed within a view.
* @see [[ViewFlags.backgroundMap]] for toggling display of the map on or off.
* @see [DisplayStyleSettings.backgroundMap]($common)
*/
public get backgroundMapSettings(): BackgroundMapSettings { return this.displayStyle.backgroundMapSettings; }
public set backgroundMapSettings(settings: BackgroundMapSettings) {
this.displayStyle.backgroundMapSettings = settings;
}
/** See [[DisplayStyleState.changeBackgroundMapProps]] */
public changeBackgroundMapProps(props: BackgroundMapProps): void {
this.displayStyle.changeBackgroundMapProps(props);
}
/** See [[DisplayStyleState.changeBackgroundMapProvider]] */
public changeBackgroundMapProvider(props: BackgroundMapProviderProps): void {
this.displayStyle.changeBackgroundMapProvider(props);
}
/** @internal */
public get backgroundMap(): MapTileTreeReference | undefined { return this._mapTiledGraphicsProvider?.backgroundMap; }
/** @internal */
public get overlayMap(): MapTileTreeReference | undefined { return this._mapTiledGraphicsProvider?.overlayMap; }
/** @internal */
public get backgroundDrapeMap(): MapTileTreeReference | undefined { return this._mapTiledGraphicsProvider?.backgroundDrapeMap; }
/** Return the imagery provider for the provided map-layer index.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer.
* @beta
*/
public getMapLayerImageryProvider(mapLayerIndex: MapLayerIndex): MapLayerImageryProvider | undefined { return this._mapTiledGraphicsProvider?.getMapLayerImageryProvider(mapLayerIndex); }
/** Return the map-layer scale range visibility for the provided map-layer index.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer.
* @see [[DisplayStyleState.mapLayerAtIndex]].
* @beta
*/
public getMapLayerScaleRangeVisibility(mapLayerIndex: MapLayerIndex): MapTileTreeScaleRangeVisibility {
const treeRef = (mapLayerIndex.isOverlay ? this._mapTiledGraphicsProvider?.overlayMap : this._mapTiledGraphicsProvider?.backgroundMap);
if (treeRef) {
return treeRef.getMapLayerScaleRangeVisibility(mapLayerIndex.index);
}
return MapTileTreeScaleRangeVisibility.Unknown;
}
/** Return a list of map-layers indexes matching a given MapTile tree Id and a layer imagery tree id.
* Note: A imagery tree can be shared for multiple map-layers.
* @internal
*/
public getMapLayerIndexesFromIds(mapTreeId: Id64String, layerTreeId: Id64String): MapLayerIndex[] {
if (this._mapTiledGraphicsProvider)
return this._mapTiledGraphicsProvider?.getMapLayerIndexesFromIds(mapTreeId, layerTreeId);
return [];
}
/** Returns the cartographic range of a map layer.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer.
*/
public async getMapLayerRange(mapLayerIndex: MapLayerIndex): Promise<MapCartoRectangle | undefined> {
const mapLayerSettings = this.view.displayStyle.mapLayerAtIndex(mapLayerIndex);
if (undefined === mapLayerSettings)
return undefined;
if (mapLayerSettings instanceof ModelMapLayerSettings) {
const ecefTransform = this.iModel.ecefLocation?.getTransform();
if (!ecefTransform)
return undefined;
const model = this.iModel.models.getLoaded(mapLayerSettings.modelId);
if (!model || !(model instanceof GeometricModelState))
return undefined;
const modelRange = await model.queryModelRange();
const cartoRange = new CartographicRange(modelRange, ecefTransform).getLongitudeLatitudeBoundingBox();
return MapCartoRectangle.fromRadians(cartoRange.low.x, cartoRange.low.y, cartoRange.high.x, cartoRange.high.y);
}
const imageryProvider = this.getMapLayerImageryProvider(mapLayerIndex);
if (undefined === imageryProvider)
return undefined;
const tileTreeRef = mapLayerIndex.isOverlay ? this.overlayMap : this.backgroundMap;
const imageryTreeRef = tileTreeRef?.getLayerImageryTreeRef(mapLayerIndex.index);
if (imageryTreeRef?.treeOwner.loadStatus === TileTreeLoadStatus.Loaded) {
return imageryProvider.cartoRange;
} else {
return undefined;
}
}
/** Changes viewport to include range of a map layer.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer.
* @param vp the viewport.
*/
public async viewMapLayerRange(mapLayerIndex: MapLayerIndex, vp: ScreenViewport): Promise<boolean> {
const range = await this.getMapLayerRange(mapLayerIndex);
if (!range)
return false;
if (range.xLength() > 1.5 * Angle.piRadians)
viewGlobalLocation(vp, true, ViewGlobalLocationConstants.satelliteHeightAboveEarthInMeters, undefined, undefined);
else
viewGlobalLocation(vp, true, undefined, undefined, range.globalLocation);
return true;
}
/** Fully reset a map-layer tile tree; by calling this, the map-layer will to go through initialize process again, and all previously fetched tile will be lost.
* @beta
*/
public resetMapLayer(mapLayerIndex: MapLayerIndex) { this._mapTiledGraphicsProvider?.resetMapLayer(mapLayerIndex); }
/** Returns true if this Viewport is currently displaying the model with the specified Id. */
public viewsModel(modelId: Id64String): boolean { return this.view.viewsModel(modelId); }
/** Attempt to change the 2d Model this Viewport is displaying, if its ViewState is a ViewState2d.
* @param baseModelId The Id of the new 2d Model to be displayed.
* @param options options that determine how the new view is displayed
* @note This function *only works* if the viewport is viewing a [[ViewState2d]], otherwise it does nothing. Also note that
* the Model of baseModelId should be the same type (Drawing or Sheet) as the current view.
* @note this method clones the current ViewState2d and sets its baseModelId to the supplied value. The DisplayStyle and CategorySelector remain unchanged.
*/
public async changeViewedModel2d(baseModelId: Id64String, options?: ChangeViewedModel2dOptions & ViewChangeOptions & MarginOptions): Promise<void> {
if (!this.view.is2d())
return;
// Clone the current ViewState, change its baseModelId, and ensure the new model is loaded.
const newView = this.view.clone(); // start by cloning the current ViewState
await newView.changeViewedModel(baseModelId);
this.changeView(newView, options); // switch this viewport to use new ViewState2d
if (options && options.doFit) { // optionally fit view to the extents of the new model
const range = await this.iModel.models.queryExtents([baseModelId]);
this.zoomToVolume(Range3d.fromJSON(range[0]?.extents), options);
}
}
/** Attempt to replace the set of models currently viewed by this viewport, if it is displaying a SpatialView
* @param modelIds The Ids of the models to be displayed.
* @returns false if this Viewport is not viewing a [[SpatialViewState]]
* @note This function *only works* if the viewport is viewing a [[SpatialViewState]], otherwise it does nothing.
* @note This function *does not load* any models. If any of the supplied `modelIds` refers to a model that has not been loaded, no graphics will be loaded+displayed in the viewport for that model.
* @see [[replaceViewedModels]] for a similar function that also ensures the requested models are loaded.
*/
public changeViewedModels(modelIds: Id64Arg): boolean {
if (!this.view.isSpatialView())
return false;
this.view.modelSelector.models.clear();
this.view.modelSelector.addModels(modelIds);
return true;
}
/** Attempt to replace the set of models currently viewed by this viewport, if it is displaying a SpatialView
* @param modelIds The Ids of the models to be displayed.
* @note This function *only works* if the viewport is viewing a [[SpatialViewState]], otherwise it does nothing.
* @note If any of the requested models is not yet loaded this function will asynchronously load them before updating the set of displayed models.
*/
public async replaceViewedModels(modelIds: Id64Arg): Promise<void> {
if (this.view.isSpatialView()) {
this.view.modelSelector.models.clear();
return this.addViewedModels(modelIds);
}
}
/** Add or remove a set of models from those models currently displayed in this viewport.
* @param modelIds The Ids of the models to add or remove.
* @param display Whether or not to display the specified models in the viewport.
* @returns false if this Viewport is not viewing a [[SpatialViewState]]
* @note This function *only works* if the viewport is viewing a [[SpatialViewState]], otherwise it does nothing.
* @note This function *does not load* any models. If `display` is `true` and any of the supplied `models` refers to a model that has not been loaded, no graphics will be loaded+displayed in the viewport for that model.
* @see [[addViewedModels]] for a similar function that also ensures the requested models are loaded.
*/
public changeModelDisplay(models: Id64Arg, display: boolean): boolean {
if (!this.view.isSpatialView())
return false;
if (display)
this.view.modelSelector.addModels(models);
else
this.view.modelSelector.dropModels(models);
return true;
}
/** Adds a set of models to the set of those currently displayed in this viewport.
* @param modelIds The Ids of the models to add or remove.
* @param display Whether or not to display the specified models in the viewport.
* @note This function *only works* if the viewport is viewing a [[SpatialViewState]], otherwise it does nothing.
* @note If any of the requested models is not yet loaded this function will asynchronously load them before updating the set of displayed models.
*/
public async addViewedModels(models: Id64Arg): Promise<void> {
// NB: We want the model selector to update immediately, to avoid callers repeatedly requesting we load+display the same models while we are already loading them.
// This will also trigger scene invalidation and changed events.
if (!this.changeModelDisplay(models, true))
return; // means it's a 2d model - this function can do nothing useful in 2d.
const unloaded = this.iModel.models.filterLoaded(models);
if (undefined === unloaded)
return;
// Need to redraw once models are available. Don't want to trigger events again.
await this.iModel.models.load(models);
this.invalidateScene();
assert(this.view.isSpatialView());
this.view.markModelSelectorChanged();
}