UNPKG

@itwin/core-frontend

Version:
419 lines • 18.4 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 { assert, dispose, Id64 } from "@itwin/core-bentley"; import { Frustum, QueryRowFormat, } from "@itwin/core-common"; import { Constant, Range3d, Transform } from "@itwin/core-geometry"; import { CategorySelectorState } from "./CategorySelectorState"; import { CoordSystem } from "./CoordSystem"; import { DisplayStyle2dState } from "./DisplayStyleState"; import { Frustum2d } from "./Frustum2d"; import { IModelApp } from "./IModelApp"; import { FeatureSymbology } from "./render/FeatureSymbology"; import { GraphicBranch } from "./render/GraphicBranch"; import { MockRender } from "./internal/render/MockRender"; import { TileGraphicType } from "./tile/internal"; import { OffScreenViewport } from "./Viewport"; import { ViewRect } from "./common/ViewRect"; import { ViewState2d, ViewState3d } from "./ViewState"; /** The information required to instantiate a [[SectionAttachment]]. This information is supplied to DrawingViewState constructor via ViewStateProps. * The spatial view is obtained asynchronously in DrawingViewState.load(). The SectionAttachment is created in DrawingViewState.attachToViewport and * disposed of in DrawingViewState.detachFromViewport. */ class SectionAttachmentInfo { _spatialView; _drawingToSpatialTransform; _displaySpatialView; get spatialView() { return this._spatialView; } get wantDisplayed() { return this._displaySpatialView || DrawingViewState.alwaysDisplaySpatialView; } constructor(spatialView, drawingToSpatialTransform, displaySpatialView) { this._spatialView = spatialView; this._drawingToSpatialTransform = drawingToSpatialTransform; this._displaySpatialView = displaySpatialView; } static fromJSON(props) { if (!props) return new SectionAttachmentInfo(Id64.invalid, Transform.createIdentity(), false); return new SectionAttachmentInfo(props.spatialView, Transform.fromJSON(props.drawingToSpatialTransform), true === props.displaySpatialView); } toJSON() { if ("string" === typeof this._spatialView && !Id64.isValidId64(this._spatialView)) return undefined; return { spatialView: (this._spatialView instanceof ViewState3d) ? this._spatialView.id : this._spatialView, drawingToSpatialTransform: this._drawingToSpatialTransform.isIdentity ? undefined : this._drawingToSpatialTransform.toJSON(), displaySpatialView: this._displaySpatialView, }; } clone(iModel) { let spatialView = this._spatialView; if (spatialView instanceof ViewState3d) spatialView = spatialView.clone(iModel); return new SectionAttachmentInfo(spatialView, this._drawingToSpatialTransform, this._displaySpatialView); } preload(options) { if (!this.wantDisplayed) return; if (this._spatialView instanceof ViewState3d) return; if (!Id64.isValidId64(this._spatialView)) return; options.spatialViewId = this._spatialView; options.viewStateLoadProps = { displayStyle: { omitScheduleScriptElementIds: !IModelApp.tileAdmin.enableFrontendScheduleScripts, compressExcludedElementIds: true, }, }; } async load(iModel) { if (!this.wantDisplayed) return; if (this._spatialView instanceof ViewState3d) return; if (!Id64.isValidId64(this._spatialView)) return; const spatialView = await iModel.views.load(this._spatialView); if (spatialView instanceof ViewState3d) this._spatialView = spatialView; } async postload(options, iModel) { let spatialView; if (options.spatialViewProps) { spatialView = await iModel.views.convertViewStatePropsToViewState(options.spatialViewProps); } if (spatialView instanceof ViewState3d) this._spatialView = spatialView; } createAttachment(toSheet) { if (!this.wantDisplayed || !(this._spatialView instanceof ViewState3d)) return undefined; const spatialToDrawing = this._drawingToSpatialTransform.inverse(); return spatialToDrawing ? new SectionAttachment(this._spatialView, spatialToDrawing, this._drawingToSpatialTransform, toSheet) : undefined; } get sectionDrawingInfo() { return { drawingToSpatialTransform: this._drawingToSpatialTransform, spatialView: this._spatialView instanceof ViewState3d ? this._spatialView.id : this._spatialView, }; } } /** A mostly no-op [[RenderTarget]] for a [[SectionAttachment]]. It allocates no webgl resources. */ class SectionTarget extends MockRender.OffScreenTarget { _attachment; constructor(attachment) { super(IModelApp.renderSystem, new ViewRect(0, 0, 1, 1)); this._attachment = attachment; } changeScene(scene) { this._attachment.scene = scene; } overrideFeatureSymbology(ovrs) { this._attachment.symbologyOverrides = ovrs; } } /** Draws the contents of an orthographic [[ViewState3d]] directly into a [[DrawingViewState]], if the associated [SectionDrawing]($backend) * specifies it should be. We select tiles for the view in the context of a lightweight offscreen viewport with a no-op [[RenderTarget]], then * add the resultant graphics to the drawing view's scene. The attachment is created in DrawingViewState.attachToViewport and disposed of in * DrawingViewState.detachFromViewport. */ class SectionAttachment { _viewFlagOverrides; toDrawing; _fromDrawing; _viewRect = new ViewRect(0, 0, 1, 1); _originalFrustum = new Frustum(); _drawingExtents; viewport; _branchOptions; scene; symbologyOverrides; get view() { assert(this.viewport.view instanceof ViewState3d); return this.viewport.view; } get zDepth() { return this._drawingExtents.z; } get drawingRange() { const frustum3d = this._originalFrustum.transformBy(this.toDrawing); return frustum3d.toRange(); } constructor(view, toDrawing, fromDrawing, toSheet) { // Save the input for clone(). Attach a copy to the viewport. this.toDrawing = toDrawing; this._fromDrawing = fromDrawing; this.viewport = OffScreenViewport.createViewport(view, new SectionTarget(this), true); this.symbologyOverrides = new FeatureSymbology.Overrides(view); let clipVolume; let clip = this.view.getViewClip(); if (clip) { clip = clip.clone(); const clipTransform = toSheet ? toSheet.multiplyTransformTransform(this.toDrawing) : this.toDrawing; clip.transformInPlace(clipTransform); clipVolume = IModelApp.renderSystem.createClipVolume(clip); } this._branchOptions = { clipVolume, hline: view.getDisplayStyle3d().settings.hiddenLineSettings, inSectionDrawingAttachment: true, frustum: { is3d: true, scale: { x: 1, y: 1 }, }, contours: view.getDisplayStyle3d().settings.contours }; this._viewFlagOverrides = { ...view.viewFlags, lighting: false, shadows: false }; // Save off the original frustum (potentially adjusted by viewport). this.viewport.setupFromView(); this.viewport.viewingSpace.getFrustum(CoordSystem.World, true, this._originalFrustum); const drawingFrustum = this._originalFrustum.transformBy(this.toDrawing); const drawingRange = drawingFrustum.toRange(); this._drawingExtents = drawingRange.diagonal(); this._drawingExtents.z = Math.abs(this._drawingExtents.z); } [Symbol.dispose]() { this.viewport[Symbol.dispose](); } addToScene(context) { if (context.viewport.freezeScene) return; const pixelSize = context.viewport.getPixelSizeAtPoint(); if (0 === pixelSize) return; // Adjust offscreen viewport's frustum based on intersection with drawing view frustum. const frustum3d = this._originalFrustum.transformBy(this.toDrawing); const frustumRange3d = frustum3d.toRange(); const frustum2d = context.viewport.getWorldFrustum(); const frustumRange2d = frustum2d.toRange(); const intersect = frustumRange3d.intersect(frustumRange2d); if (intersect.isNull) return; frustum3d.initFromRange(intersect); frustum3d.transformBy(this._fromDrawing, frustum3d); this.viewport.setupViewFromFrustum(frustum3d); // Adjust view rect based on size of attachment on screen so tiles of appropriate LOD are selected. const width = this._drawingExtents.x * intersect.xLength() / frustumRange3d.xLength(); const height = this._drawingExtents.y * intersect.yLength() / frustumRange3d.yLength(); this._viewRect.width = Math.max(1, Math.round(width / pixelSize)); this._viewRect.height = Math.max(1, Math.round(height / pixelSize)); this.viewport.setRect(this._viewRect); // Propagate settings from drawing viewport. this.viewport.debugBoundingBoxes = context.viewport.debugBoundingBoxes; this.viewport.setTileSizeModifier(context.viewport.tileSizeModifier); // Create the scene. this.viewport.renderFrame(); const scene = this.scene; if (!scene) return; // Extract graphics and insert into drawing's scene context. const outputGraphics = (source) => { if (0 === source.length) return; const graphics = new GraphicBranch(); graphics.setViewFlagOverrides(this._viewFlagOverrides); graphics.symbologyOverrides = this.symbologyOverrides; for (const graphic of source) graphics.entries.push(graphic); const branch = context.createGraphicBranch(graphics, this.toDrawing, this._branchOptions); context.outputGraphic(branch); }; outputGraphics(scene.foreground); context.withGraphicType(TileGraphicType.BackgroundMap, () => outputGraphics(scene.background)); context.withGraphicType(TileGraphicType.Overlay, () => outputGraphics(scene.overlay)); // Report tile statistics to drawing viewport. const tileAdmin = IModelApp.tileAdmin; const selectedAndReady = tileAdmin.getTilesForUser(this.viewport); const requested = tileAdmin.getRequestsForUser(this.viewport); tileAdmin.addExternalTilesForUser(context.viewport, { requested: requested?.size ?? 0, selected: selectedAndReady?.selected.size ?? 0, ready: selectedAndReady?.ready.size ?? 0, }); } } /** A view of a [DrawingModel]($backend) * @public * @extensions */ export class DrawingViewState extends ViewState2d { static get className() { return "DrawingViewDefinition"; } /** Exposed strictly for testing and debugging. Indicates that when loading the view, the spatial view should be displayed even * if `SectionDrawing.displaySpatialView` is not `true`. * @internal */ static alwaysDisplaySpatialView = false; /** Exposed strictly for testing and debugging. Indicates that the 2d graphics should not be displayed. * @internal */ static hideDrawingGraphics = false; _viewedExtents; _attachmentInfo; _attachment; /** Strictly for testing. @internal */ get sectionDrawingProps() { return this._attachmentInfo.toJSON(); } /** Strictly for testing. @internal */ get sectionDrawingInfo() { return this._attachmentInfo.sectionDrawingInfo; } /** Strictly for testing. @internal */ get attachment() { return this._attachment; } /** Strictly for testing. @internal */ get attachmentInfo() { return this._attachmentInfo; } constructor(props, iModel, categories, displayStyle, extents, sectionDrawing) { super(props, iModel, categories, displayStyle); if (categories instanceof DrawingViewState) { this._viewedExtents = categories._viewedExtents.clone(); this._attachmentInfo = categories._attachmentInfo.clone(iModel); } else { this._viewedExtents = extents; this._attachmentInfo = SectionAttachmentInfo.fromJSON(sectionDrawing); } } /** See [[ViewState.attachToViewport]]. */ attachToViewport(args) { super.attachToViewport(args); assert(undefined === this._attachment); this._attachment = this._attachmentInfo.createAttachment(args.drawingToSheetTransform); } /** See [[ViewState.detachFromViewport]]. */ detachFromViewport() { super.detachFromViewport(); this._attachment = dispose(this._attachment); } async changeViewedModel(modelId) { await super.changeViewedModel(modelId); const props = await this.querySectionDrawingProps(); this._attachmentInfo = SectionAttachmentInfo.fromJSON(props); // super.changeViewedModel() throws if attached to viewport, and attachment only allocated while attached to viewport assert(undefined === this._attachment); } async querySectionDrawingProps() { let spatialView = Id64.invalid; let drawingToSpatialTransform; let displaySpatialView = false; try { const ecsql = ` SELECT spatialView, json_extract(jsonProperties, '$.drawingToSpatialTransform') as drawingToSpatialTransform, CAST(json_extract(jsonProperties, '$.displaySpatialView') as BOOLEAN) as displaySpatialView FROM bis.SectionDrawing WHERE ECInstanceId=${this.baseModelId}`; for await (const row of this.iModel.createQueryReader(ecsql, undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames })) { spatialView = Id64.fromJSON(row.spatialView?.id); displaySpatialView = !!row.displaySpatialView; try { drawingToSpatialTransform = JSON.parse(row.drawingToSpatialTransform); } catch { // We'll use identity transform. } break; } } catch { // The version of BisCore ECSchema in the iModel is probably too old to contain the SectionDrawing ECClass. } return { spatialView, displaySpatialView, drawingToSpatialTransform }; } /** @internal */ preload(hydrateRequest) { assert(!this.isAttachedToViewport); super.preload(hydrateRequest); this._attachmentInfo.preload(hydrateRequest); } /** @internal */ async postload(hydrateResponse) { const promises = []; promises.push(super.postload(hydrateResponse)); promises.push(this._attachmentInfo.postload(hydrateResponse, this.iModel)); await Promise.all(promises); } static createFromProps(props, iModel) { const cat = new CategorySelectorState(props.categorySelectorProps, iModel); const displayStyleState = new DisplayStyle2dState(props.displayStyleProps, iModel); const extents = props.modelExtents ? Range3d.fromJSON(props.modelExtents) : new Range3d(); // use "new this" so subclasses are correct return new this(props.viewDefinitionProps, iModel, cat, displayStyleState, extents, props.sectionDrawing); } toProps() { const props = super.toProps(); props.modelExtents = this._viewedExtents.toJSON(); props.sectionDrawing = this._attachmentInfo.toJSON(); return props; } getViewedExtents() { const extents = this._viewedExtents.clone(); if (this._attachment) { extents.extendRange(this._attachment.drawingRange); } return extents; } get defaultExtentLimits() { return { min: Constant.oneMillimeter, max: 3 * Constant.diameterOfEarth, }; } isDrawingView() { return true; } /** See [[ViewState.getOrigin]]. */ getOrigin() { const origin = super.getOrigin(); if (this._attachment) origin.z = -this._attachment.zDepth; return origin; } /** See [[ViewState.getExtents]]. */ getExtents() { const extents = super.getExtents(); if (this._attachment) extents.z = this._attachment.zDepth + Frustum2d.minimumZDistance; return extents; } /** @internal */ discloseTileTrees(trees) { super.discloseTileTrees(trees); if (this._attachment) trees.disclose(this._attachment.viewport); } /** @internal */ createScene(context) { if (!DrawingViewState.hideDrawingGraphics) super.createScene(context); if (this._attachment) this._attachment.addToScene(context); } get areAllTileTreesLoaded() { return super.areAllTileTreesLoaded && (!this._attachment || this._attachment.view.areAllTileTreesLoaded); } /** @internal */ get secondaryViewports() { return this._attachment ? [this._attachment.viewport] : super.secondaryViewports; } /** @internal */ getAttachmentViewport(args) { const attach = args.inSectionDrawingAttachment ? this._attachment : undefined; return attach?.viewport; } /** @beta */ computeDisplayTransform(args) { // ###TODO we're currently ignoring model and element Id in args, assuming irrelevant for drawings. // Should probably call super or have super call us. const attach = args.inSectionDrawingAttachment ? this._attachment : undefined; return attach?.toDrawing.clone(args.output); } } //# sourceMappingURL=DrawingViewState.js.map