/** * BIMLayer — a Tile3DLayer for BIM SOURCE files, not pre-baked tilesets. * * `` expects a 3D Tiles * root someone already produced (by hand, by `dev/tools/gen-ifc-fixture.ts`, * by any other converter). `BIMLayer` takes the BIM file itself — today an * `.ifc` — and runs it through `loadIfc` (the SAME parser the BIM workbench's * `ifc-loader` widget uses) the moment it loads, so a page never needs a * separate conversion step or a pre-built tileset checked into the repo. * * Two things fall out of using the loader rather than a static tileset: * * - No `site-origin`/`site-heading`/`site-scale` needed. `loadIfc` already * reads the file's own georeference and bakes the HORIZONTAL placement * (eastings/northings/heading/scale) into the tileset it returns (or * falls back honestly when the file declares none). `site-origin` itself * is NOT wired up on `BIMLayer` (an authored one is ignored) — it rides * runtime-core.ts's `tilesetBakedFrame`/`tilesetPlacementMatrix` * machinery, which reads a tileset.json's baked root transform * SYNCHRONOUSLY off the layer's `data` prop at construction time, and * BIMLayer's tileset is an in-browser blob URL that doesn't exist until * `loadIfc` resolves asynchronously inside `updateState` below — an * honest, documented gap rather than a half-built override path. * - The VERTICAL placement, by contrast, IS automatic — matching how the * BIM workbench's `ifc-loader` WIDGET already behaves, not a separate * opt-in. `IfcMapConversion`'s `OrthogonalHeight` (this model's real * absolute elevation) is computed by `loadIfc` but left out of the * tileset's own baked transform (see that field's doc comment in * src/ifc.ts) — correct on a flat, no-`terrain` basemap (a local Z≈0 * model sits right on a Z=0 ground plane), wrong once a REAL terrain * surface is active (whose elevation at any inhabited location is * essentially never 0 — the model ends up buried inside the mesh). * `runtime-core.ts` reports whether terrain is active via a * `terrainActive` prop (the same `carriesGeoreference`-gated channel * `onGeoreference` already rides); `renderLayers` below applies * `OrthogonalHeight` as an ENU re-placement AT the model's own already- * correct lon/lat (same site point, just a non-zero height component) * only when that prop is true — reusing `tilesetPlacementMatrix`, the * exact mechanism `ifc-loader`'s own manual `site-origin` override rides, * just triggered automatically instead of requiring the author (or a * widget) to compute and restate a number that's already known. * - The outline overlay is automatic. `loadIfc` already returns an * `edgesUrl` alongside the tileset; a separate `` was only ever there because a plain Tile3DLayer * has no way to render one itself. * * The outline overlay follows `isolate-features`/`hide-features` the same way * the mesh does: `loadModel` parses the edges blob into memory (it is a * same-origin blob URL it just created, not a real network round trip), and * `renderLayers` filters those rows with the SAME `matchToken`/ * `normalizeMatchList` helpers `FeatureMeshLayer` uses for the mesh, keyed on * `feature-filter-field`. It does NOT follow `ghost-features` — ghosting only * dims the mesh's opacity, never the outline, and that is not a BIMLayer gap: * the OLDER two-layer pattern (`ifc-loader`'s own `Tile3DLayer` + companion * `-edges` PathLayer, still used by the widget — see * src/widgets/ifc.ts) never ghosted its outlines either, since its * `filter-category`/`filter-categories` pair is a plain keep-list with no * partial-opacity concept. Matching that, not inventing new behaviour. * * Implementation: a CompositeLayer wrapping a real `Tile3DLayer`, not a * Tile3DLayer subclass — `renderLayers` fetches+parses on `src` change, then * spreads `this.props` onto the inner Tile3DLayer verbatim (dropping `src` * and the runtime's own harmless `data: []` baseline, setting `data` to the * resolved tileset URL instead). That spread is load-bearing: the * runtime injects `_subLayerProps`/`onTileLoad`/`onTilesetLoad` onto * whatever top-level props a `supportsFeaturePicking`/`carriesTileset` * layer receives (see runtime-core.ts's `pickFeaturesHook`/`featureTableHook`), * and forwarding them unchanged is what makes picking, `feature-styles`, * `ctx.features()`, and `om-tileset-load` all keep working without BIMLayer * having to know anything about them itself. * * Other BIM formats: the loader dispatch (currently a straight call to * `loadIfc`) is the one seam a future format would extend — nothing else * here is IFC-specific. */ import { CompositeLayer } from "@deck.gl/core"; import type { Layer } from "@deck.gl/core"; /** One outline polyline plus the same category fields the mesh filters on — see `readGeoreference`'s edges row shape in `../ifc`. */ interface EdgeRow { path: [number, number, number][]; [field: string]: unknown; } interface LoadedBim { tilesetUrl: string; edgeRows: EdgeRow[] | null; lonLat: [number, number]; heading: number; scale: number; /** The file's own `IfcMapConversion.OrthogonalHeight`, undefined when it never declared one — see renderLayers' `terrainActive` handling. */ orthogonalHeight?: number; revoke(): void; } export interface BIMLayerProps { /** * URL of the BIM source file (an `.ifc` today). * * Deliberately NOT `data`: deck.gl's own `Layer` base class auto-fetches * any string `data` prop through loaders.gl's generic loader registry — * fine for a ScatterplotLayer's rows, fatal here ("No valid loader found" * for a `.ifc`, confirmed by running this in a real browser). Tile3DLayer * avoids this because deck.gl gives Tile3DLayer itself custom handling for * `data`; a plain CompositeLayer like this one does not inherit that. */ src: string; [prop: string]: unknown; } export declare class BIMLayer extends CompositeLayer { static layerName: string; static defaultProps: { pickFeatures: boolean; }; state: { loaded: LoadedBim | null; loadingFor: string | null; destroyed: boolean; abort: AbortController | null; }; initializeState(): void; updateState({ props, oldProps }: { props: BIMLayerProps; oldProps: BIMLayerProps; }): void; finalizeState(): void; private loadModel; renderLayers(): Layer[]; } export {};