IFClite TypeScript API
    Preparing search index...

    Main renderer class

    Index
    • Parameters

      • canvas: HTMLCanvasElement

      Returns Renderer

    • Add multiple meshes to the scene (convenience method for streaming)

      Parameters

      • meshes: MeshData[]

        Array of MeshData to add

      • OptionalisStreaming: boolean

        If true, throttles batch rebuilding for better streaming performance

      Returns GpuUploadOutcome<void>

    • Append additional point clouds without clearing existing ones.

      Parameters

      Returns void

    • Streaming entry: open an empty asset that will receive chunks via appendPointCloudChunk. Call endPointCloudStream when no more chunks will arrive (currently a no-op but kept for symmetry).

      Parameters

      • meta: { expressId: number; ifcType?: string; modelIndex?: number }

      Returns PointCloudAssetHandle

    • Returns Promise<string | null>

    • Clear all caches (call when geometry changes)

      Returns void

    • Drop all point cloud GPU resources.

      Returns void

    • Clear the caps of every cutting plane beyond the first whose id is not in keep (#7545).

      Parameters

      • Optionalkeep: readonly string[]

      Returns void

    • Clear the 2D section overlay: the primary plane's cap, or with planeId the cap of that cutting plane beyond the first (#7545).

      Parameters

      • OptionalplaneId: string

      Returns void

    • Compute BIM ↔ scan deviation for every loaded point cloud asset.

      Delegates to the DeviationComputer collaborator, which owns the compute pipeline and the BVH-reuse fingerprint; see deviation/deviation-computer.ts for the full contract.

      Parameters

      • Optionalopts: DeviationComputeOptions

      Returns Promise<DeviationComputeResult>

    • Consume the render request flag. Returns true (and resets the flag) if a render was requested since the last call. Used by the animation loop to decide whether to render.

      Returns boolean

    • Destroy the renderer and release all GPU resources.

      Cleans up scene buffers, render pipeline textures, picking resources, post-processing buffers, section-plane renderers, and snap caches. After calling this method the renderer is no longer usable. Safe to call multiple times (idempotent).

      An init() still in flight is invalidated too. It is parked on await device.init(...), and without the generation bump below it resumes after this returns, allocates a complete replacement GPU stack that nothing references, and re-publishes ready — resolving whenReady() waiters against a renderer the host has already torn down (#2465). The bump is what initOnce() re-checks after its await, and what makes markReady() refuse the stale completion.

      This is why the teardown itself lives in teardown(): initOnce() runs it on the PREVIOUS init's objects as part of its own re-init, and routing that through here would have every init invalidate its own generation, leaving whenReady() pending forever.

      Anyone parked in whenReady() is FAILED rather than left pending. Nothing after this call can make the wait true — the invalidation above is exactly what stops the in-flight init from publishing readiness, and a host that remounts builds a new Renderer rather than re-initialising this one — so leaving the promise unsettled suspends the caller's async frame for the lifetime of the page. apps/viewer's point-cloud drop is one of those frames: it captured this instance before the teardown, and would stop mid-load with the spinner still up and no error to report. See whenReady() for the rejection contract.

      Returns void

    • Probe the quantized pipeline variants and, when all exist, enable 12-byte quantized batch vertices (issue #1682 phase 6). Returns whether quantization is active — on failure (e.g. a fragile backend rejecting pipeline creation) batches stay on the f32 path.

      Returns Promise<boolean>

    • Ensure all meshes have GPU resources (call after adding meshes if pipeline wasn't ready)

      Returns GpuUploadOutcome<void>

    • Fit camera to view all loaded geometry

      Returns void

    • Vendor/architecture identity of the GPU adapter, snapshotted during init() (issue #2624 device-loss telemetry), or null when the runtime does not expose GPUAdapter.info. Safe to call after a device loss: the snapshot is plain strings copied at init, not a live GPU object.

      Returns AdapterInfoSnapshot | null

    • Get the canvas element

      Returns HTMLCanvasElement

    • Get diagnostic info for mobile debugging

      Returns {
          batches: number;
          bounds: string;
          calls: number;
          camPos: string;
          camTgt: string;
          contextOk: boolean;
          errors: number;
          gpuErrors: number;
          lastError: string;
          lastGpuError: string;
          meshes: number;
          skips: number;
      }

    • Statistics of the last COMPLETED render() call (draw calls issued, batches drawn / frustum-culled / contribution-culled), or null before the first frame. Pair with getScene().getResidentGpuBytes() for the load-complete telemetry snapshot (issue #1682 observability).

      Returns FrameStats | null

    • Get the GPU device (returns null if not initialized, or if the device has been lost).

      The device-lost check is load-bearing, not defensive tidiness. A lost device is NOT torn down: WebGPUDevice.destroy() is the only thing that nulls the handle and it is never called for an involuntary loss (TDR / GPU-process crash / driver reset), so isInitialized() stays true and this used to keep handing out a zombie GPUDevice. Every caller then called createBuffer() on it, which throws — bypassing both render()'s own deviceLost guard and the onDeviceLost listeners entirely.

      Returning null instead routes into the if (!device) return check that every call site already has, so a lost device degrades to "stop uploading" rather than an uncaught throw.

      DELIBERATELY NOT NARROWED, unlike getScene(): the one external GPUDevice member measured is queue; every other call site hands the device back to a SceneContents method. See isDeviceLost() / onDeviceLost() for the recovery contract.

      Returns GPUDevice | null

    • Returns RendererGpuTimingSnapshot

    • Get model bounds (used for section planes, fitToView, etc.)

      Returns
          | {
              max: { x: number; y: number; z: number };
              min: { x: number; y: number; z: number };
          }
          | null

    • Parameters

      • modelIndex: number
      • OptionalpointCloudHandle: { id: number }

      Returns
          | {
              max: { x: number; y: number; z: number };
              min: { x: number; y: number; z: number };
          }
          | null

    • Get render pipeline (for batching). DELIBERATELY NOT NARROWED, unlike getScene(): the measurement found ZERO external RenderPipeline members — all 12 call sites pass the handle straight back into a SceneContents upload method typed for the real class.

      Returns RenderPipeline | null

    • Total number of point cloud assets currently uploaded.

      Returns number

    • Exact float64 placement copy for CPU math; the f32 transform rounds map-grid origins (#6894).

      Parameters

      • handle: { id: number }

      Returns Float64Array<ArrayBufferLike> | undefined

    • Total number of points across all point cloud assets.

      Returns number

    • Parameters

      • handle: { id: number }

      Returns Float32Array<ArrayBufferLike> | undefined

    • MEASURED external surface, not the 4400-line Scene: widening SceneContents is a published-API decision, not a detail — see scene-contents.ts.

      Returns SceneContents

    • Every section plane's world frame (#7545), for the UI's tags and handle: the last frame's bounds and rotation, and planes (the caller's newest) when given.

      Parameters

      Returns SectionPlaneFrame[]

    • Whether the last rendered frame clipped surfaces (section, terrain or box).

      Returns boolean

    • Check if 2D section overlay has geometry to render

      Returns boolean

    • Initialize renderer.

      Safe to call on an already-initialised instance: the previous GPU objects are released first. The comment below advertises a destroy() + init() re-init flow, and the obvious device-loss auto-recovery is to call init() on the live instance — which, without this, silently orphaned two render pipelines, the picker, the post passes, the point-cloud and deviation pipelines and the overlay layer's glyph atlas, per recovery (#2448). Making the method self-safe is cheaper than trusting every future caller to remember.

      Concurrent calls are SERIALISED, not coalesced: the second waits for the first to settle and then runs in full. Without that, pipeline — which only ever marks a COMPLETED init — is still null while the first call is awaiting device.init(), so both calls sail past the guard above and both allocate a full set of GPU objects, orphaning the first. Queueing turns the concurrent case into the sequential one the guard already handles, rather than adding a second, differently-shaped rule.

      Returns Promise<void>

    • Invalidate BVH cache (call when geometry changes)

      Returns void

    • True once the GPU device has been lost for a non-intentional reason.

      Returns boolean

    • Check if renderer is fully initialized and ready to use.

      ready is part of the test, not decoration: between init() being called and its queued body running, the device and pipeline still belong to the PREVIOUS init and are about to be destroyed, so the other two conditions alone would report a renderer that is on its way out as usable.

      So is the device-loss check. A lost device is never torn down — WebGPUDevice.destroy() is the only thing that nulls the handle and an involuntary loss (driver reset / VRAM exhaustion / GPU-process crash) never calls it — so isInitialized() stays true, the pipeline stays non-null, and ready stays set from the init that completed before the loss. All three conditions therefore still hold while render() is a no-op and getGPUDevice() returns null: the renderer would report itself usable through this third door alone. Unlike the two revocations above this one needs no generation scoping — an init() clears ready synchronously, so a latch left standing until the queued body clears it cannot make this method spuriously false in the meantime.

      Returns boolean

    • Subscribe to non-intentional GPU device loss (driver reset / VRAM exhaustion — NOT an intentional destroy()). Fired at most once per device. After it fires, render() is a no-op and the renderer reports itself un-ready (isReady() false, whenReady() rejecting with RendererDeviceLostError) until it is re-initialised, so the typical response is to dispose this renderer and reload the model. Returns an unsubscribe function.

      Camera and model state live on the CPU (JS) and survive device loss, so a reload restores the model at its current orientation — the loss is a GPU event, not a data loss.

      Parameters

      • listener: (info: { message: string; reason: string }) => void

      Returns () => void

    • Subscribe to the renderer having degraded frame after frame without recovering (issue #2417). Distinct from onDeviceLost: the device is still alive by every signal available, which is exactly why render() refuses to latch on these throws — but the user is looking at a viewport that has stopped updating, and until this callback existed nothing said so. Fired at most once per renderer, once PERSISTENT_DEGRADATION_FRAMES frames have degraded CONSECUTIVELY — any frame that completes resets the run, so a session that failed occasionally and recovered every time never reports. Returns an unsubscribe function.

      No replay for a late subscriber, unlike onDeviceLost — a loss can latch during init(), before any subscriber can exist, but a degraded frame cannot: renderFrame() returns early while pipeline is null, so the count only moves once the host is driving frames, which is strictly after init() resolved and the host subscribed.

      Parameters

      Returns () => void

    • Check whether a render has been requested without clearing the flag. Used by the animation loop to test the dirty flag before committing to render (e.g. when throttling may skip the frame).

      Returns boolean

    • Pick object at screen coordinates Respects visibility filtering so users can only select visible elements Returns PickResult with expressId and modelIndex for multi-model support

      Note: x, y are CSS pixel coordinates relative to the canvas element. These are scaled internally to match the actual canvas pixel dimensions.

      Resolves null once the device is gone — see pickPathAlive().

      Parameters

      Returns Promise<PickResult | null>

    • GPU-based rectangle pick. Drag-select returns the set of expressIds touched by any pixel inside [x0,y0]..[x1,y1] (CSS pixels, canvas-relative). Both meshes and point clouds participate.

      See PickingManager.pickRect for the visibility-filter + limitation notes.

      Resolves an empty set once the device is gone — see pickPathAlive().

      Parameters

      • x0: number
      • y0: number
      • x1: number
      • y1: number
      • Optionaloptions: PickOptions

      Returns Promise<Set<number>>

    • Stage one new owner; borrowed mesh buffers must remain immutable until disposal.

      Parameters

      Returns { commit: () => void; dispose: () => void }

    • Parameters

      Returns { commit: () => void; dispose: () => void }

    • Raycast with magnetic edge snapping behavior This provides the "stick and slide along edges" experience

      Note: x, y are CSS pixel coordinates relative to the canvas element. These are scaled internally to match the actual canvas pixel dimensions.

      Parameters

      Returns MagneticSnapResult & { intersection: Intersection | null }

    • Bind a streamed point cloud to its federation identity after upload: the post-offset globalId picking and deviation readback report, and, when given, the model index they attribute the asset to (#6887). Takes effect on the next render — no GPU buffer rewrite needed.

      Parameters

      Returns void

    • Free the chunks appended under key; returns the points removed (0 for a stale handle).

      Parameters

      Returns number

    • Draw a frame through the shared submission path, preserving the void API.

      Parameters

      Returns void

    • Draw one frame; true only when it submits without a contained synchronous failure (#6709).

      Never throws, so callers never need to guard this call to keep their animation loop alive.

      What a throw MEANS depends on its type (isDeviceLossThrow), and since issue #2417 that holds for the WHOLE frame — both this catch and the encode region's own, which share containFrameThrow:

      • a DOMException is the device reporting its own death synchronously (Safari 26.5, issue #2229). It latches the same deviceLost state the async device.lost promise would: later frames become quiet skips and onDeviceLost listeners fire exactly once.
      • anything else (a RangeError from a buffer the host cannot allocate, say) costs only this frame: the swap-chain config is invalidated so the next frame reconfigures, a frame is re-requested within a bounded budget, the failure is counted in getDiagnostics(), and rendering carries on. Once enough such frames have degraded without recovering, onPersistentRenderDegradation fires once.

      Frame setup and encoding through submit share containFrameThrow (#2417); the encode catch also balances the validation error scope. Submission is synchronous, distinct from GPU completion/validation.

      Parameters

      Returns boolean

    • Request a render on the next animation frame. Safe to call many times per frame — only one render will happen.

      Returns void

    • Resize canvas

      Parameters

      • width: number
      • height: number

      Returns void

    • Draw the focused clash's CONTACT geometry as 3D line segments — the real shared-face polygon outlines / intersection lines, not the AABB box. vertices is a flat line-list (x,y,z per endpoint, 2 endpoints per segment) in world frame. Omit color to follow the overlay theme's clashOverlap, as for setClashOverlapBox. Pass null to clear. Shares the clash-box line buffer, so only one of this / setClashOverlapBox is shown at a time.

      Parameters

      • lines:
            | {
                color?: [number, number, number, number];
                vertices:
                    | Float32Array<ArrayBufferLike>
                    | { localVertices: Float32Array; origin: [number, number, number] }
                    | readonly {
                        localVertices: Float32Array;
                        origin: [number, number, number];
                    }[];
            }
            | null

      Returns void

    • Draw the focused clash's TRUE INTERSECTION VOLUME — the actual overlap mesh from clashIntersectionSolid — as an opaque solid, so the clash reads as a shape rather than a wireframe box or contact line (the BIMcollab Zoom / Solibri presentation). Pass null to clear. Independent of setClashOverlapBox / setClashContactLines: the caller decides which one is current for a given clash (solid when the kernel resolved one, box/lines as the fallback when it didn't). Omit color to follow the overlay theme's clashOverlap, as for setClashOverlapBox.

      Parameters

      • solid:
            | {
                color?: [number, number, number, number];
                indices: Uint32Array;
                origin?: [number, number, number];
                positions: Float64Array<ArrayBufferLike> | Float32Array<ArrayBufferLike>;
            }
            | null

      Returns void

    • Show (or clear) the clash-overlap box: the wireframe AABB of a focused clash, drawn in color so the overlap region reads as a distinct third colour next to the two glowing clash elements (#1277). Omit color to draw it in the overlay theme's clashOverlap and keep following it across setOverlayTheme calls (#5490). Pass null to clear. min/max are world-space corners (clash works in world frame).

      Parameters

      • box:
            | {
                color?: [number, number, number, number];
                max: [number, number, number];
                min: [number, number, number];
            }
            | null

      Returns void

    • Toggle Eye-Dome Lighting and tune its strength.

      EDL adds depth perception to point clouds (and meshes) via screen- space depth gradient — silhouette pixels get a soft black halo. Cheap: ~9 texture taps per pixel. Only runs when point clouds are loaded.

      Parameters

      • opts: {
            enabled?: boolean;
            highQuality?: boolean;
            radiusPx?: number;
            strength?: number;
        }

      Returns void

    • Parameters

      • enabled: boolean

      Returns void

    • Set one standalone 3D line overlay, or clear it by passing null.

      vertices is a flat world-space line-list — [x1,y1,z1, x2,y2,z2, …], one segment per six floats. The vertices are already lifted to world space, so these overlays draw whether or not a section plane is active. A trailing partial segment is dropped rather than rejecting the array. An empty array clears too, but null skips building the pipelines.

      Every channel is an independent buffer with its own visibility, so setting one leaves the other three untouched. All four share the colour set by Renderer.setOverlayTheme's overlayLine field; label colour is per-text via SymbolicTextInput.color on uploadAnnotationTexts3D.

      The channels differ in exactly one way — whether they grow the scene bounds. annotation (#653) and alignment DO: a file holding only them has no IfcProduct meshes to frame, so Home / fit-to-view would have nothing to aim at and the near/far range would clip the lines away. grid (IfcGridAxis, #967) and dxf (the DXF reference layer, #2043) do NOT: they are reference layers that routinely extend past the model envelope, so growing the bounds would reframe the camera whenever one was ticked on. The rule is "does this content DEFINE the model's extent", NOT "is it behind a visibility toggle" — annotations sit behind ifcAnnotationsVisible too.

      Parameters

      • channel: "annotation" | "alignment" | "grid" | "dxf" | "terrain" | "centreline"
      • vertices:
            | Float32Array<ArrayBufferLike>
            | { localVertices: Float32Array; origin: [number, number, number] }
            | readonly {
                localVertices: Float32Array;
                origin: [number, number, number];
            }[]
            | null

      Returns void

    • Cap the drawing buffer's device-pixel ratio, trading sharpness for fill on HiDPI screens; see computeDrawingBufferSize.

      Parameters

      • ratio: number

      Returns void

    • Set model bounds (used when computing bounds from batches)

      Parameters

      • bounds: {
            max: { x: number; y: number; z: number };
            min: { x: number; y: number; z: number };
        }

      Returns void

    • Absolute workspace yaw about the render-frame pivot; GPU-instanced only (#4890). Skips invalidation on a no-op (#4890 review).

      Parameters

      • modelIndex: number
      • angle: number
      • pivot: readonly [number, number, number]

      Returns boolean

    • Absolute workspace translation in renderer Y-up metres (#4226).

      Parameters

      • modelIndex: number
      • translation: readonly [number, number, number]

      Returns void

    • Set the one colour vocabulary every overlay draws in (#5484): the selection highlight, the section-plane preview (one accent for every axis), every overlay line channel (section-cut outline, IfcAnnotation, alignment, IfcGrid, DXF / LandXML) and the clash pair / overlap tints. Call this on theme change and once after init(); the underlying GPU uniforms are written only here, never per frame. Supersedes setOverlayLineColor (deleted — the app never called it with a themed colour). See OverlayTheme for which fields want linear-light RGBA and which want sRGB-direct RGBA. The matching label colour is per-text via SymbolicTextInput.color on uploadAnnotationTexts3D.

      Parameters

      Returns void

    • Replace all loaded point clouds with assets.

      Phase 0 entry point — single-chunk inline assets from IFCx (pcd::base64, points::array, points::base64). Future phases accept streaming sources via a different overload.

      Parameters

      Returns void

    • Set/clear a streamed cloud's column-major model matrix (16 floats) for IfcMapConversion alignment (#1804). Applied on the next frame's uniform write without rewriting vertex buffers.

      Parameters

      • handle: PointCloudAssetHandle
      • matrix: Float64Array<ArrayBufferLike> | Float32Array<ArrayBufferLike> | null

      Returns void

    • Streamed clouds are addressed by durable asset handle.

      Parameters

      • handle: { id: number }
      • translation: readonly [number, number, number]

      Returns void

    • Toggle one resident streamed scan without releasing its GPU buffers.

      Parameters

      • handle: { id: number }
      • visible: boolean

      Returns void

    • Opt in the currently selected authored curves for exact magnetic picking.

      Parameters

      Returns void

    • Upload filled IfcAnnotation regions for the symbolic overlay (issue #653). Pass an empty array to clear.

      Parameters

      Returns void

    • Upload IfcAnnotation text labels for the symbolic overlay (issue #653). Pass an empty array to clear.

      Parameters

      Returns void

    • Upload 2D section drawing data for 3D overlay rendering.

      Cardinal-axis call site: pass axis + position percentage and the upload computes the plane offset along the cardinal axis using the model bounds (or sectionRange override). 2D points are then lifted to 3D via the cardinal-axis swap.

      Custom-plane call site (issue #243): pass customPlane = { origin, tangent, bitangent }. The 2D points are lifted via the explicit basis, exactly inverting the projection SectionCutter applied when generating the polygons. Without this the cap silhouette lands off the actual cutting plane (the bug PR #581 hid by suppressing the cap entirely for non-cardinal planes).

      Parameters

      • polygons: CutPolygon2D[]
      • lines: DrawingLine2D[]
      • axis: "front" | "down" | "side"
      • position: number
      • OptionalsectionRange: { max?: number; min?: number }
      • Optionalflipped: boolean
      • OptionalcustomPlane: {
            bitangent: [number, number, number];
            origin: [number, number, number];
            tangent: [number, number, number];
        }
      • OptionalplaneId: string

      Returns void

    • Resolves once init() has finished and the GPU device + point-cloud renderer are usable. Callers that may run before init completes — e.g. dropping a point cloud immediately after the viewport mounts, before the async WebGPU init resolves — should await renderer.whenReady() before beginPointCloudStream, which otherwise throws "Renderer not initialized".

      REJECTS (with an Error whose name is RendererDestroyedError) if destroy() runs while the caller is waiting, or if it already ran and no init() has been started since. It never resolves against a destroyed renderer — that is what this method exists to prevent — so the only alternative would be a promise that never settles, which suspends the caller's async frame permanently and takes everything the frame captured with it. The viewer reaches that state on an ordinary path: Viewport builds a NEW Renderer per mount and destroys the old one in its effect cleanup, so a destroy() there is FINAL for the instance a consumer captured — a point-cloud drop that straddles a layout swap or a StrictMode remount would otherwise hang mid-load, with no error, forever. Callers should handle the rejection as "the target went away", not as a load failure.

      It also REJECTS (RendererDeviceLostError) while the GPU device is lost. The device is what this method promises, and a lost one cannot serve the call the caller is waiting to make — getGPUDevice() returns null, so beginPointCloudStream throws "Renderer not initialized" the moment the wait resolves. The third outcome is the one destroy() already ruled out: parking a waiter that only a host-initiated init() could ever settle, and that the viewer's usual response to a loss (drop this renderer, build a new one) guarantees will never come. Unlike the destroyed case this is NOT final — a later init() on the same instance re-arms the wait synchronously, so "retry after re-init" is a contract callers can act on, which is why the two rejections carry different names.

      Returns Promise<void>