UNPKG

@itwin/core-frontend

Version:
297 lines • 12 kB
/*--------------------------------------------------------------------------------------------- * 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