import { AcDbEntity, AcDbLayerTableRecord, AcDbLayerTableRecordAttrs, AcDbLayout, AcDbObjectId, AcGeBox2d, AcGeBox3d, AcGePoint2d, AcGePoint2dLike } from '@mlightcad/data-model'; import { AcTrHtmlTransientManager, AcTrRenderer } from '@mlightcad/three-renderer'; import * as THREE from 'three'; import { AcEdBaseView, AcEdCalculateSizeCallback, AcEdSpatialQueryResultItemEx, AcEdViewMode } from '../editor'; import { AcTrLayoutView } from './AcTrLayoutView'; import { AcTrScene } from './AcTrScene'; /** * Options to customize view */ export interface AcTrView2dOptions { /** * Container HTML element used by renderer */ container?: HTMLElement; /** * Callback function used to calculate size of canvas when window resized */ calculateSizeCallback?: AcEdCalculateSizeCallback; /** * Background color */ background?: number; } /** * Default view option values */ export declare const DEFAULT_VIEW_2D_OPTIONS: AcTrView2dOptions; /** * A 2D CAD viewer component that renders CAD drawings using Three.js. * * This class extends {@link AcEdBaseView} and provides functionality for: * - Rendering 2D CAD drawings with Three.js WebGL renderer * - Handling user interactions (pan, zoom, select) * - Managing layouts, layers, and entities * - Supporting various CAD file formats (DWG, DXF) * * @example * ```typescript * const viewer = new AcTrView2d({ * canvas: document.getElementById('canvas') as HTMLCanvasElement, * background: 0x000000, * calculateSizeCallback: () => ({ * width: window.innerWidth, * height: window.innerHeight * }) * }); * ``` */ export declare class AcTrView2d extends AcEdBaseView { /** The Three.js renderer wrapper for CAD rendering */ private _renderer; /** * ID of the currently scheduled requestAnimationFrame callback. * * This value is used to: * - Track whether the animation loop is currently running * - Prevent scheduling multiple animation loops * - Cancel the animation loop when the view is paused, hidden, or disposed * * A value of `null` indicates that no animation frame is currently scheduled. */ private _rafId; /** Manager for layout views and viewport handling */ private _layoutViewManager; /** The 3D scene containing all CAD entities organized by layouts and layers */ private _scene; /** Flag indicating if the view needs to be re-rendered */ private _isDirty; /** Performance monitoring statistics display */ private _stats; /** Map of missing raster images during rendering */ private _missedImages; /** The number of entities waiting for processing */ private _numOfEntitiesToProcess; /** CSS2D renderer for HTML transient overlays */ private _css2dRenderer; /** * Block table record ids of layouts whose entities are currently being * batch-converted into the scene. Used by * {@link AcTrView2d.loadLayoutEntitiesIfNeeded} to guard against * re-entrant calls before the `setTimeout` callback flips * `AcTrLayout.isLoaded` to `true`, which would otherwise duplicate * entities when the same layout tab is clicked twice in quick succession. */ private _loadingLayouts; /** * Block table record ids of layouts that have already received an * initial zoom-to-fit. Used by the `layoutSwitched` handler to apply * the auto-zoom **only on the first user visit** to each layout, and * to preserve the camera state on subsequent visits (matches AutoCAD's * per-tab view persistence). * * Cannot be inferred from `_layoutViewManager.has(btrId)` because * `addLayout` pre-creates an `AcTrLayoutView` for every layout in the * DWG at document load time — by the time the user clicks a layout * tab the view already exists, so "first existence of view" is * always false. This set tracks the orthogonal question "has the user * actually focused on this layout before?". * * Marked from two entry points: * - `onAfterOpenDocument` (via `markLayoutAsInitialized`): the * document's startup layout is initialized externally, so we don't * auto-zoom again when the user clicks back to it. * - `layoutSwitched` handler: after the first user-driven switch * completes its initial zoom-to-fit. */ private _initializedLayouts; /** * Creates a new 2D CAD viewer instance. * * @param options - Configuration options for the viewer * @param options.container - Optional HTML container element. If not provided, a new container will be created * @param options.calculateSizeCallback - Optional callback function to calculate canvas size on window resize * @param options.background - Optional background color as hex number (default: 0x000000) */ constructor(options?: AcTrView2dOptions); private getPointerSelectionAction; /** * Initializes the viewer after renderer and camera are created. * * This method sets up the initial cursor and can be overridden by child classes * to add custom initialization logic. * * @protected */ initialize(): void; /** * Gets the current view mode (selection or pan). * * @returns The current view mode * @inheritdoc */ get mode(): AcEdViewMode; /** * Sets the view mode (selection or pan). * * @param value - The view mode to set */ set mode(value: AcEdViewMode); /** * Gets the Three.js renderer wrapper used for CAD rendering. * * @returns The renderer instance */ get renderer(): AcTrRenderer; /** * Gets whether the view needs to be re-rendered. * * @returns True if the view is dirty and needs re-rendering */ get isDirty(): boolean; /** * Sets whether the view needs to be re-rendered. * * @param value - True to mark the view as needing re-rendering */ set isDirty(value: boolean); /** * Gets information about missing data during rendering (fonts and images). * * @returns Object containing maps of missing fonts and images */ get missedData(): { fonts: Record; images: Map; }; get center(): AcGePoint2d; set center(value: AcGePoint2d); /** * Gets the background color of the view. * * The color is represented as a 24-bit hexadecimal RGB number, e.g., * `0x000000` for black. */ get backgroundColor(): number; /** * Sets the background color of the view. * * @param value - The background color as a 24-bit hexadecimal RGB number */ set backgroundColor(value: number); /** * The block table record id of the model space */ get modelSpaceBtrId(): AcDbObjectId; set modelSpaceBtrId(value: AcDbObjectId); /** * The block table record id associated with the active layout */ get activeLayoutBtrId(): string; set activeLayoutBtrId(value: string); /** * The active layout view */ get activeLayoutView(): AcTrLayoutView; /** * The statistics of the current scene */ get stats(): import("./AcTrScene").AcTrSceneStats; /** * CAD scene graph used for rendering and HTML export. */ get cadScene(): AcTrScene; /** * The internal THREE scene used by this view. */ get internalScene(): THREE.Scene; /** * The HTML transient elements manager for placing overlays anchored to world coordinates. */ get htmlTransientManager(): AcTrHtmlTransientManager; /** * The internal THREE camera used by current active layout. */ get internalCamera(): THREE.OrthographicCamera; /** * Sets global ltscale */ set ltscale(scale: number); /** * Sets global celtscale */ set celtscale(scale: number); /** * @inheritdoc */ screenToWorld(point: AcGePoint2dLike): AcGePoint2d; /** * @inheritdoc */ worldToScreen(point: AcGePoint2dLike): AcGePoint2d; /** * @inheritdoc */ zoomTo(box: AcGeBox2d, margin?: number): void; /** * Re-render points with latest point style settings * @param displayMode Input display mode of points */ rerenderPoints(displayMode: number): void; /** * @inheritdoc */ zoomToFitDrawing(timeout?: number): void; /** * @inheritdoc */ zoomToFitLayer(layerName: string): boolean; /** * @inheritdoc */ flyTo(point: AcGePoint2dLike, scale: number): void; private openPickedMTextEditor; private editMTextEntity; private resolveMTextEditorWidth; private resolveMTextEditorTextHeight; private pixelsToWorldY; private getMTextToolbarFontFamilies; /** * @inheritdoc * * In **paper space** layouts the selection pipeline supports * "drill-through": clicks inside a viewport rectangle resolve against * the model-space entities that are visually rendered through that * viewport, rather than picking the viewport's border. Clicks **near** * the border still pick the `AcDbViewport` entity itself so the user * can grip, move, lock or delete the viewport. * * This mirrors AutoCAD **web** behaviour (single-click selection of * model content through the viewport). The desktop ARX behaviour * (explicit MSPACE/PSPACE modes, CVPORT system variable, double-click * to enter mspace) is a separate, larger feature — tracked in * `.claude/plans/next_14_viewports_full.md` PR-γ Option A. We * intentionally do **not** implement it here. * * The border vs interior decision uses a tolerance derived from * `selectionBoxSize` (the same pixel-sized hit radius used elsewhere * in pick) converted to paper-space WCS via `pointToBox`. This keeps * the gesture consistent with how other entity edges behave — you * don't have to land pixel-perfect on the viewport line to grab it. */ pick(point?: AcGePoint2dLike, hitRadius?: number, pickOneOnly?: boolean): AcEdSpatialQueryResultItemEx[]; /** * Resolves hits against the model-space layout for each viewport the * click drills through. Appends the matches into `results` (caller * sorts/dedups). Kept private and separate from `pick` so the main * pick path stays a single straight read. * * Each viewport gets its own raycaster shot (using the viewport view's * own camera, which is zoomed to `viewport.viewBox` in model WCS), so * a click that lands in overlapping viewports correctly resolves * against each viewport's particular model framing. * * `pickThroughViewports` does NOT consult the active (paper) layout's * spatial index — that work is already done by the caller. It only * adds model-space results that would otherwise be invisible to the * paper-space pick. */ private pickThroughViewports; /** * @inheritdoc */ search(box: AcGeBox2d | AcGeBox3d): AcEdSpatialQueryResultItemEx[]; /** * @inheritdoc */ select(point?: AcGePoint2dLike): void; /** * @inheritdoc */ selectByBox(box: AcGeBox2d): void; /** * @inheritdoc */ addLayer(layer: AcDbLayerTableRecord): void; /** * @inheritdoc */ updateLayer(layer: AcDbLayerTableRecord, changes: Partial): void; /** * Add the specified transient entity or entities into this view * @param entity Input one or multiple transient entities */ addTransientEntity(entity: AcDbEntity | AcDbEntity[]): void; /** * Remove the specified transient entity from this view * @param objectId Input the object id of the transient entity to remove */ removeTransientEntity(objectId: AcDbObjectId): void; /** * @inheritdoc */ addEntity(entity: AcDbEntity | AcDbEntity[]): void; /** * @inheritdoc */ removeEntity(entity: AcDbEntity | AcDbEntity[]): void; /** * @inheritdoc */ updateEntity(entity: AcDbEntity | AcDbEntity[]): void; /** * @inheritdoc */ addLayout(layout: AcDbLayout): void; /** * Marks a layout as already framed by an external caller (typically * `AcApDocManager.onAfterOpenDocument`, which zooms the startup * layout right after parsing). Subsequent `layoutSwitched` events * for this btrId will skip their initial zoom-to-fit so the user's * camera state on the startup layout is preserved when they click * back to that tab. * * This is the public counterpart of the `_initializedLayouts` set — * exposed so the application layer can stay in sync with the view's * notion of "which layouts have been framed already" without * needing access to private state. */ markLayoutAsInitialized(layoutBtrId: AcDbObjectId): void; /** * Applies the initial zoom-to-fit for a layout the user just switched * into for the first time. Picks the best available "what should the * camera frame?" signal in this order: * * 1. **`AcDbLayout.limits`** (LIMMIN/LIMMAX) — only when it actually * contains the layout's viewports. Many real DWGs ship with garbage * limits (e.g. `(0,0)-(12,9)` from a legacy template setup) that * don't reflect the actual paper sheet. We reject those by * checking containment against `viewportsBoundingBox`. * * 2. **`AcTrLayoutView.viewportsBoundingBox`** — bounding box of all * real user viewports in the layout. In production sheets viewports * typically span 70-90% of the paper, so this is a great proxy for * the printable area and (crucially) ignores outliers like title * blocks authored in a different unit/scale. * * 3. **`AcDbLayout.extents`** — the layout's own EXTMIN/EXTMAX, if * populated. Many parsers leave this empty (we've seen `(0,0)-(0,0)`), * so it sits below the viewport-based heuristic. * * 4. **`zoomToFitDrawing`** (entity extents from spatial index) — * last-resort fallback for layouts with no viewports and no * sensible limits/extents (e.g. a freshly created empty paper). * Vulnerable to scale-mismatch outliers, but better than no zoom. * * **Critically, this runs through `AcEdConditionWaiter`**: at the * moment `layoutSwitched` fires, the layout's entities (including its * `AcDbViewport`s) have not yet been batch-converted into the scene * — `loadLayoutEntitiesIfNeeded` chunked-converts via `setTimeout`. * Without the waiter, `viewportsBoundingBox` returns undefined and * the strategy degrades into (1) zooming to garbage `limits`, or * (4) zooming to an empty scene box. The waiter polls * `_numOfEntitiesToProcess` and only fires the heuristic once the * conversion is done. */ private applyInitialZoom; /** * @inheritdoc */ clear(): void; /** * @inheritdoc */ highlight(ids: AcDbObjectId[]): void; /** * @inheritdoc */ unhighlight(ids: AcDbObjectId[]): void; stopAnimationLoop(): void; /** * @inheritdoc */ onHover(id: AcDbObjectId): void; /** * @inheritdoc */ onUnhover(id: AcDbObjectId): void; protected createScene(): AcTrScene; private createStats; protected onWindowResize(): void; private animate; private startAnimationLoop; /** * Create the layout view with the specified block table record id. * @param layoutBtrId Input the block table record id associated with the layout view. */ private createLayoutViewIfNeeded; /** * Load entities from the specified layout if they haven't been loaded yet. * This ensures that when switching to a layout, all its entities are available for rendering. * * Two non-obvious invariants are enforced here: * * 1. The layout is looked up by `layoutBtrId` (the argument), not by * `this._scene.activeLayout`. The active layout reference happens to * match in the current `layoutSwitched` handler call site, but relying * on it would silently miss layouts that are pre-loaded ahead of * becoming active (e.g. background prefetch). * 2. The `_loadingLayouts` guard prevents re-entrance while the * `setTimeout` chunked-convert callback is still in flight. Without it, * clicking the same layout tab twice in quick succession (or * `layoutSwitched` firing twice during the async window) would iterate * the block table record again and duplicate every entity in the * layout — visible as ghosted overdraw and double the spatial-index * weight. * * @param layoutBtrId Input the block table record id of the layout */ private loadLayoutEntitiesIfNeeded; /** * Show or hide stats component * @param show If it is true, show stats component. Otherwise, hide stats component. * Default value is false. */ private toggleStatsVisibility; private drawEntity; /** * Walks the given block table record once and creates one * `AcTrViewportView` for every real `AcDbViewport` entity it finds * (skipping the default paper-space viewport that is filtered * everywhere else by `AcTrViewportView.isDefaultPaperSpaceViewport`). * * This is the recovery pass for paper-space layouts whose viewport * entities reached `batchConvert` before the `AcTrLayoutView` was * created — those entities were drawn and added to the scene, but * the viewport-view creation step silently no-oped (lookup returned * undefined). Without this recovery, `viewportsBoundingBox` stays * `undefined` on first user visit, the initial-zoom strategy * degrades to the bogus `limits` branch, and the layout renders as * a "grain in the corner" with empty viewport scissors. See the * call site in `loadLayoutEntitiesIfNeeded` for the full context. * * Cheap operation: only AcDbViewport entities are inspected; for a * typical sheet that's a handful of entities even on 5000-entity * paper layouts. */ private ensureViewportViews; /** * Converts the specified database entities to three entities * @param entities - The database entities * @returns The converted three entities */ private batchConvert; private handleGroup; /** * Remaps layer metadata/material bindings from a source layer to the effective render layer. * * During block decomposition, one INSERT may be split into multiple layer buckets. For * children authored on layer "0", AutoCAD requires inheriting the INSERT's own layer. * This method applies that inheritance by mutating each child's `userData.layerName` and * re-binding materials via renderer cache, so subsequent layer-level style changes still * hit the correct material instances. * * @param objects - Root objects in the current layer bucket to traverse and remap. * @param sourceLayerName - Layer name found in block definition before inheritance. * @param effectiveLayerName - Final layer name used by rendering and style updates. */ private remapInheritedLayerObjects; /** * Some DXF conversion paths lose `isByLayerColor` on layer-0 block contents while still * retaining other ByLayer markers (lineType/lineWeight/transparency). For AutoCAD-compatible * INSERT inheritance, treat such colors as inheritable when remapping from layer "0". */ private promoteLayerZeroByLayerColor; /** * Builds the resolved layer traits used when layer-0 block content inherits an INSERT layer. */ private getEffectiveLayerTraits; private decreaseNumOfEntitiesToProcess; } //# sourceMappingURL=AcTrView2d.d.ts.map