@itwin/core-frontend
Version:
iTwin.js frontend components
297 lines • 12 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 { BeEvent, dispose, expectDefined } from "@itwin/core-bentley";
import { Angle, Constant, Point2d, Point3d, Range2d, Range3d } from "@itwin/core-geometry";
import { ColorDef, Gradient, GraphicParams, } from "@itwin/core-common";
import { CategorySelectorState } from "./CategorySelectorState";
import { DisplayStyle2dState } from "./DisplayStyleState";
import { Frustum2d } from "./Frustum2d";
import { ViewState2d } from "./ViewState";
import { GraphicType } from "./common/render/GraphicType";
import { SheetViewAttachments } from "./internal/SheetViewAttachments";
// cSpell:ignore ovrs
/** Describes the geometry and styling of a sheet border decoration.
* The sheet border decoration mimics a sheet of paper with a drop shadow.
*/
class SheetBorder {
_rect;
_shadow;
_gradient;
constructor(rect, shadow, gradient) {
this._rect = rect;
this._shadow = shadow;
this._gradient = gradient;
}
/** Create a new sheet border. If a context is supplied, points are transformed to view coordinates. */
static create(width, height, context) {
// Rect
const rect = [
Point3d.create(0, height),
Point3d.create(0, 0),
Point3d.create(width, 0),
Point3d.create(width, height),
Point3d.create(0, height)
];
if (context) {
context.viewport.worldToViewArray(rect);
}
// Shadow
const shadowWidth = .01 * Math.sqrt(width * width + height * height);
const shadow = [
Point3d.create(shadowWidth, 0),
Point3d.create(shadowWidth, -shadowWidth),
Point3d.create(width + shadowWidth, -shadowWidth),
Point3d.create(width + shadowWidth, height - shadowWidth),
Point3d.create(width, height - shadowWidth),
Point3d.create(width, 0),
Point3d.create(shadowWidth, 0),
];
if (context) {
context.viewport.worldToViewArray(shadow);
}
// Gradient
const gradient = new Gradient.Symb();
gradient.mode = Gradient.Mode.Linear;
gradient.angle = Angle.createDegrees(-45);
gradient.keys = [{ value: 0, color: ColorDef.from(25, 25, 25) }, { value: 0.5, color: ColorDef.from(150, 150, 150) }];
// Copy over points
const rect2d = [];
for (const point of rect)
rect2d.push(Point2d.createFrom(point));
const shadow2d = [];
for (const point of shadow)
shadow2d.push(Point2d.createFrom(point));
return new SheetBorder(rect2d, shadow2d, gradient);
}
getRange() {
const range = Range2d.createArray(this._rect);
const shadowRange = Range2d.createArray(this._shadow);
range.extendRange(shadowRange);
return range;
}
/** Add this border to the given GraphicBuilder. */
addToBuilder(builder) {
const lineColor = ColorDef.black;
const fillColor = ColorDef.black;
const params = new GraphicParams();
params.fillColor = fillColor;
params.gradient = this._gradient;
builder.activateGraphicParams(params);
builder.addShape2d(this._shadow, Frustum2d.minimumZDistance);
builder.setSymbology(lineColor, fillColor, 2);
builder.addLineString2d(this._rect, 0);
}
}
/** A view of a [SheetModel]($backend).
* @public
* @extensions
*/
export class SheetViewState extends ViewState2d {
/** The width and height of the sheet in world coordinates. */
sheetSize;
_viewAttachments;
_viewedExtents;
_onViewAttachmentsReloaded = () => undefined;
/** Strictly for tests. */
onViewAttachmentsReloaded = new BeEvent();
get attachmentIds() {
return this._viewAttachments.attachmentIds;
}
static get className() { return "SheetViewDefinition"; }
static createFromProps(viewStateData, iModel) {
const cat = new CategorySelectorState(viewStateData.categorySelectorProps, iModel);
const displayStyleState = new DisplayStyle2dState(viewStateData.displayStyleProps, iModel);
// use "new this" so subclasses are correct
return new this(viewStateData.viewDefinitionProps, iModel, cat, displayStyleState, expectDefined(viewStateData.sheetProps), expectDefined(viewStateData.sheetAttachments));
}
toProps() {
const props = super.toProps();
props.sheetAttachments = [...this.attachmentIds];
// For sheetProps all that is actually used is the size, so just null out everything else.
const codeProps = { spec: "", scope: "", value: "" };
props.sheetProps = {
model: "",
code: codeProps,
classFullName: "",
width: this.sheetSize.x,
height: this.sheetSize.y,
scale: 1,
};
return props;
}
/** Strictly for testing. @internal */
get viewAttachmentProps() {
return this._viewAttachments.attachmentProps;
}
/** Strictly for testing. @internal */
get viewAttachmentInfos() {
return this._viewAttachments.attachmentInfos;
}
/** Strictly for testing. @internal */
get attachments() {
return this._viewAttachments.attachments;
}
isDrawingView() { return false; }
isSheetView() { return true; }
constructor(props, iModel, categories, displayStyle, sheetProps, attachments) {
super(props, iModel, categories, displayStyle);
if (categories instanceof SheetViewState) {
// we are coming from clone...
this.sheetSize = categories.sheetSize.clone();
this._viewAttachments = categories._viewAttachments.clone(iModel);
this._viewedExtents = categories._viewedExtents.clone();
}
else {
this.sheetSize = Point2d.create(sheetProps.width, sheetProps.height);
this._viewAttachments = SheetViewAttachments.create(attachments);
const extents = new Range3d(0, 0, 0, this.sheetSize.x, this.sheetSize.y, 0);
const margin = 1.1;
extents.scaleAboutCenterInPlace(margin);
this._viewedExtents = extents;
}
if (iModel.isBriefcaseConnection()) {
iModel.txns.onElementsChanged.addListener(async (changes) => {
let reload = false;
for (const change of changes.filter({ includeMetadata: (meta) => meta.is("BisCore:ViewAttachment") })) {
if (change.type === "inserted" || this._viewAttachments.attachmentIds.includes(change.id)) {
reload = true;
break;
}
}
if (reload) {
await this._viewAttachments.reload(this.baseModelId, iModel);
this._onViewAttachmentsReloaded();
this.onViewAttachmentsReloaded.raiseEvent();
}
});
}
}
getOrigin() {
const origin = super.getOrigin();
origin.z = -this._viewAttachments.maxDepth;
return origin;
}
getExtents() {
const extents = super.getExtents();
extents.z = this._viewAttachments.maxDepth + Frustum2d.minimumZDistance;
return extents;
}
/** Overrides [[ViewState.discloseTileTrees]] to include tile trees associated with [ViewAttachment]($backend)s displayed on this sheet. */
discloseTileTrees(trees) {
super.discloseTileTrees(trees);
trees.disclose(this._viewAttachments);
}
/** @internal */
collectNonTileTreeStatistics(stats) {
super.collectNonTileTreeStatistics(stats);
this._viewAttachments.collectStatistics(stats);
}
get defaultExtentLimits() {
return { min: Constant.oneMillimeter, max: this.sheetSize.magnitude() * 10 };
}
getViewedExtents() {
return this._viewedExtents;
}
/** @internal */
preload(hydrateRequest) {
super.preload(hydrateRequest);
this._viewAttachments.preload(hydrateRequest);
}
/** @internal */
async postload(hydrateResponse) {
const promises = [];
promises.push(super.postload(hydrateResponse));
promises.push(this._viewAttachments.postload(hydrateResponse, this.iModel));
await Promise.all(promises);
}
/** @internal */
createScene(context) {
super.createScene(context);
this._viewAttachments.addToScene(context);
}
/** @internal */
get secondaryViewports() {
return this._viewAttachments.getSecondaryViewports();
}
/** @internal */
async queryAttachmentIds() {
const ecsql = `SELECT ECInstanceId as attachmentId FROM bis.ViewAttachment WHERE model.Id=${this.baseModelId}`;
const ids = [];
for await (const row of this.iModel.createQueryReader(ecsql))
ids.push(row[0]);
return ids;
}
async changeViewedModel(modelId) {
await super.changeViewedModel(modelId);
const attachmentIds = await this.queryAttachmentIds();
dispose(this._viewAttachments);
this._viewAttachments = SheetViewAttachments.create(attachmentIds);
}
/** See [[ViewState.attachToViewport]]. */
attachToViewport(args) {
super.attachToViewport(args);
this._viewAttachments.attachToViewport({
backgroundColor: this.displayStyle.backgroundColor,
sheetModelId: this.baseModelId,
});
this._onViewAttachmentsReloaded = () => args.invalidateController();
}
/** See [[ViewState.detachFromViewport]]. */
detachFromViewport() {
super.detachFromViewport();
this._viewAttachments.detachFromViewport();
this._onViewAttachmentsReloaded = () => undefined;
}
get areAllTileTreesLoaded() {
if (!super.areAllTileTreesLoaded) {
return false;
}
let displayedExtents = this._viewedExtents;
const frustum = this.calculateFrustum();
if (frustum) {
displayedExtents = frustum.toRange();
}
return this._viewAttachments.areAllTileTreesLoaded(displayedExtents);
}
/** @internal Strictly for testing */
areAllAttachmentsLoaded() {
return this._viewAttachments.areAllAttachmentsLoaded();
}
/** Create a sheet border decoration graphic. */
createBorder(width, height, context) {
const border = SheetBorder.create(width, height, context);
const builder = context.createGraphicBuilder(GraphicType.ViewBackground);
border.addToBuilder(builder);
return builder.finish();
}
/** @internal */
decorate(context) {
super.decorate(context);
if (this.sheetSize !== undefined) {
const border = this.createBorder(this.sheetSize.x, this.sheetSize.y, context);
context.setViewBackground(border);
}
}
computeFitRange() {
const size = this.sheetSize;
if (0 >= size.x || 0 >= size.y)
return super.computeFitRange();
return new Range3d(0, 0, -1, size.x, size.y, 1);
}
/** @internal */
getAttachmentViewport(args) {
return this._viewAttachments.getAttachmentViewport(args);
}
/** @beta */
computeDisplayTransform(args) {
// ###TODO we're currently ignoring model and element Id in args, assuming irrelevant for sheets.
// Should probably call super or have super call us.
return this._viewAttachments.computeDisplayTransform(args);
}
}
//# sourceMappingURL=SheetViewState.js.map