Files
speckle-server/docs/viewer/large-model-streaming-plan.md
T

31 KiB

Large Model Streaming Plan

Muc tieu: nang cap pipeline tu IFC converter den browser viewer de co the mo va tuong tac muot voi cac file IFC nang hang tram MB, voi trai nghiem gan voi Autodesk Forge: render nhanh, khong OOM, load chi tiet theo camera/selection, va khong bat browser tai toan bo geometry.

1. Boi canh hien tai

File IFC gan day:

  • File: 25.025_z01_p002_r03.ifc
  • Size: khoang 250 MB
  • Elements: 265,739
  • Meshes: 344,548
  • WebIFC vertexData scalar count: 321,017,100
  • Estimated vertex count: khoang 53,502,850 neu moi vertex gom 6 scalar position/normal
  • Convert duration: 458.59s
  • Parse duration: 450.24s
  • Converter peak RAM quan sat: khoang 30 GB

Van de:

  • Converter hien tai co xu huong load va giu qua nhieu geometry/object trong RAM.
  • Browser viewer khong the load full model geometry cung luc.
  • Native log hien tai ghi totalVerts=321,017,100, nhung audit code cho thay day co kha nang la scalar count trong vertexData, khong phai vertex count. Neu chia theo 6 scalar/vertex thi van la khoang 53.5M vertices. Chi rieng vertexData float32 da khoang 1.28 GB; them indices, colors, BVH, metadata, object tree, JS staging va GPU buffers co the day browser vao OOM.
  • Neu tiep tuc render bang Speckle JSON object graph full model, server co RAM lon van khong giai quyet browser OOM.

Ket luan: can them mot tang "viewer derivative" rieng cho large model, khong dung full Speckle object graph lam payload render chinh.

1.0 Tien do implement hien tai

Da thuc hien:

  • Fix cache invalidation sau file import de tranh trang thai vao model ngay sau upload bi trang cho den khi reload.
  • Them viewer full-load guard de chan browser tai full object graph khi object count vuot nguong.
  • Fix P/Invoke ABI cho native WebIFC layer va them probe/stream geometry path.
  • Them converter large-model guard: file IFC tren nguong mac dinh se skip native geometry patch vao Speckle object graph de tranh peak RAM qua cao.
  • Them viewer derivative writer dang prototype:
    • ghi manifest speckle-large-model-manifest;
    • ghi binary tile SPKTILE1;
    • split multi-tile theo IFC_VIEWER_DERIVATIVE_TARGET_TILE_MB, mac dinh 16 MB;
    • stream mesh tu native callback vao file tile, khong giu toan bo derivative trong RAM managed.
  • Them DB/API backend cho viewer_derivatives:
    • migration viewer_derivatives;
    • repository/domain operations;
    • GET manifest;
    • PUT artifacts/*;
    • POST publish;
    • POST fail.
  • Them IFC worker upload/publish artifact:
    • converter ghi derivative vao temp dir;
    • worker stream artifact len server theo chunk 1 MB;
    • publish manifest khi upload thanh cong;
    • mark derivative failed neu upload/publish loi.
  • Them frontend derivative path:
    • neu derivative queued/processing/failed, UI khong full-load model va hien toast;
    • neu derivative ready, dung LargeModelStreamingLoader de fetch manifest/tile binary va render qua viewer pipeline.

Chua xong:

  • Tile culling theo camera/frustum/section box.
  • LOD generation that su, hien moi co LOD0.
  • Metadata/property streaming theo selection.
  • Identity mapping day du tu IFC expressId/GlobalId sang Speckle object id/selection id.
  • GPU memory budget, eviction, cache policy cho tiles.
  • Atomic cleanup/GC va retry worker rieng cho derivative.
  • Playwright/browser memory benchmark tren file IFC 250 MB.

1.1 Audit va phan bien plan

Audit boi 4 agent theo cac huong converter/native, viewer/browser, backend/API/storage, va phan bien kien truc cho ket luan:

  • Huong derivative/tile streaming la dung.
  • Plan ban dau qua rong cho MVP va danh gia thap rui ro converter, backend contract, viewer runtime.
  • Large-model viewer phai la mot runtime path rieng, khong chi la mot extension nho cua SpeckleLoader/WorldTree.
  • Converter large mode phai bypass duong hien tai dang build full geometry map, patch full Speckle object graph, roi upload lai.
  • Backend khong the co version "derivative only" ngay lap tuc vi commit/version hien yeu cau objectId; MVP can tao mot root object nho, on dinh, kem derivative metadata/status.
  • Must-have tu dau: feature flag, no-full-load guard, derivative status, atomic manifest publish, retry-safe generation, cleanup/GC, metrics.

Phan bien quan trong can bien thanh gate truoc khi implement:

  1. Chung minh native/WebIFC co the enumerate geometry ma khong materialize full geometry cache.
  2. Chung minh converter peak RSS duoi nguong muc tieu tren file 250 MB.
  3. Chung minh viewer co the add/remove GPU resources theo tile ma khong di qua full RenderTree.
  4. Chung minh identity mapping giua IFC expressId, GlobalId, Speckle object id, tile element id, selection id la on dinh.
  5. Chung minh tile binary round-trip duoc bbox, material, ids, selection mapping.

2. Muc tieu san pham

Muc tieu cho file IFC 250 MB hien tai:

  • Time to first render: duoi 5 giay sau khi vao trang viewer.
  • Browser RAM: duoi 2-3 GB trong luong su dung thong thuong.
  • FPS khi orbit/pan: 30+ FPS tren may client pho bien.
  • Initial geometry loaded: duoi 5% full geometry.
  • Chi tiet load dan theo camera, zoom, section box, selection.
  • Khong auto load full model neu model vuot nguong large-model.
  • Co metadata/properties khi select object, nhung load on-demand.

3. Kien truc dich

Tong quan:

IFC upload
  -> file import queue
  -> large model converter
      -> geometry streaming
      -> tile builder
      -> LOD builder
      -> metadata index builder
      -> manifest builder
  -> blob storage / object storage
  -> DB stores manifest reference + stats
  -> browser viewer
      -> load manifest
      -> stream visible tiles
      -> unload invisible/old tiles
      -> request metadata on selection

Nguyen tac:

  • Converter khong giu full geometry trong RAM.
  • Geometry render duoc luu thanh binary tiles, khong uu tien JSON cho geometry nang.
  • Viewer khong tai full model; chi tai visible tiles va LOD phu hop.
  • Metadata/properties tach rieng khoi geometry.
  • Postgres khong luu binary geometry lon; blob storage/MinIO/S3 luu tiles.
  • Redis/RabbitMQ chi dung cho queue, progress, locks; khong dung lam spill storage cho geometry.

4. Converter pipeline moi

4.1 Detect large model

Sau khi upload/start import, converter can lay quick stats:

  • IFC file size
  • element count
  • geometry/mesh count
  • vertex scalar count, estimated vertex count
  • estimated triangle count

Nguong de chuyen sang large-model mode:

  • fileSize > 100 MB, hoac
  • vertices > 20M, hoac
  • meshes > 50k, hoac
  • estimated browser payload > 1 GB

Output:

{
  "mode": "large-model",
  "reason": "estimated_vertices_above_threshold",
  "stats": {
    "fileSizeBytes": 250658364,
    "elements": 265739,
    "meshes": 344548,
    "vertexScalarCount": 321017100,
    "estimatedVertices": 53502850
  }
}

4.2 WebIFC/native geometry streaming

Hien tai NativeIfcGeometry.ExtractGeometry() tra ve Dictionary<uint, List<MeshData>>, lam RAM tang cao. Audit source cung chi ra duong native hien tai co the da build/copy geometry trong LoadModel truoc khi C# thay du lieu, nen viec boc GetGeometryFromIndex thanh iterator C# chua du de giam peak RAM.

Truoc khi build tile pipeline can co Phase -1 native spike:

  • Fix/verify native ABI giua C# P/Invoke va export C++ bang smoke test.
  • Report rieng vertexScalarCount, vertexCount, indexCount, triangleCount, buffer bytes, peak RSS.
  • Chung minh streaming API khong populate full geometryList/geometries.
  • Giu duoc instancing: shared geometry id + transform table, khong bake/copy moi placement thanh mesh rieng neu co the.

Doi sang callback API:

StreamAllMeshes(ifcPath, onMesh, userData)

Native callback contract:

typedef void (*OnMeshCallback)(
  uint32_t expressId,
  const double* vertices,
  int vertexCount,
  const uint32_t* indices,
  int indexCount,
  const double* transform,
  const float* color,
  void* userData
);

Trong native layer:

  1. Load IFC model.
  2. Enumerate flat meshes.
  3. For each mesh:
    • lay geometry
    • apply transform neu can
    • call callback
    • free/delete geometry ngay sau callback
  4. Khong append mesh vao global vector.

C# side:

NativeIfcGeometry.StreamMeshes(filePath, mesh =>
{
    tileBuilder.AddMesh(mesh);
    metadataBuilder.RecordGeometry(mesh.ExpressId, mesh.Bounds);
});

Dieu can xac minh:

  • WebIFC C++ co API tuong duong StreamAllMeshes nhu TS example hay khong.
  • Neu API hien tai chi co GetGeometryFromIndex, can xac minh no khong dua den full native geometry cache. Neu van cache full model, phai sua native geometry processor thay vi chi them iterator o C#.
  • Neu LoadModel tu no da build full geometry cache, can sua native geometry processor de lazy generate geometry theo element.

4.3 Tile builder

Muc tieu: gom mesh vao tile de viewer stream theo spatial visibility.

Tile split strategy giai doan dau:

  • Compute bbox tung element/mesh.
  • Dung octree hoac grid adaptive.
  • Builder phai out-of-core: ghi temp chunks/index xuong disk khi qua nguong, flush tile theo batch, khong giu full model mesh trong RAM.
  • Uu tien deterministic tiling de retry/rebuild khong doi tile id neu input khong doi.
  • Gioi han moi tile:
    • triangles target: 50k-200k cho LOD cao
    • vertex buffer target: 4-32 MB
    • bbox compact
    • material groups trong tile

Tile data:

{
  "tileId": "tile_000123",
  "bbox": [minX, minY, minZ, maxX, maxY, maxZ],
  "elementCount": 782,
  "triangleCount": 145000,
  "materialIds": ["mat_01", "mat_07"],
  "lods": [
    { "level": 0, "url": ".../tile_000123_lod0.bin", "bytes": 131072 },
    { "level": 1, "url": ".../tile_000123_lod1.bin", "bytes": 1048576 },
    { "level": 2, "url": ".../tile_000123_lod2.bin", "bytes": 8388608 }
  ]
}

4.4 LOD builder

Can tao LOD de first render nhe.

LOD levels:

  • LOD0: overview, 0.5-2% triangles, dung cho zoom xa.
  • LOD1: medium, 5-15% triangles.
  • LOD2: full or near-full, load khi zoom gan hoac selection.

Decimation options:

  • Short term: use meshoptimizer simplifier / glTF meshopt pipeline.
  • Medium term: custom tile-level simplification by material group.
  • Long term: error-metric decimation + preserve edges/important element boundaries.

Important:

  • LOD phai giu mapping tu primitive/draw range ve elementId de picking/highlight.
  • Neu decimation merge nhieu element, can co fallback: overview tile chi de visualize, detailed tile load khi selection/zoom.
  • MVP khong duoc coi LOD0 la element-accurate. LOD0 la coarse overview/read-only visual; selection chinh xac chi bat dau khi detailed tile da load.

4.5 Binary geometry format

Khong nen dung JSON cho vertices/faces lon.

Format de xuat cho MVP:

TileBinary v1
Header:
  magic: "SPKTILE"
  version: uint16
  lodLevel: uint8
  bounds: float32[6]
  materialGroupCount: uint32
  elementRangeCount: uint32
  positionFormat: enum
  normalFormat: enum
  indexFormat: enum

MaterialGroups:
  materialIdHash
  indexStart
  indexCount

ElementRanges:
  elementIdHash / expressId
  indexStart
  indexCount
  bbox

Instances optional but preferred:
  sharedGeometryId
  transform
  materialOverride
  elementIdHash

Buffers:
  positions
  normals optional
  indices
  colors optional

Encoding:

  • Positions:
    • MVP: float32.
    • Optimized: quantized uint16/int16 relative to tile bbox.
  • Normals:
    • MVP: float32 optional.
    • Optimized: oct-encoded uint16.
  • Indices:
    • uint16 when tile vertex count <= 65535.
    • uint32 otherwise.
  • Compression:
    • MVP: gzip/brotli via HTTP.
    • Optimized: meshopt/Draco. Meshopt is preferred for fast browser decode.

4.6 Metadata derivative

Metadata khong load full upfront.

Identity model phai ro ngay tu dau:

  • expressId: source-local id trong mot IFC parse, khong du de lam id public on dinh.
  • globalId: IFC GlobalId, uu tien cho cross-version/object identity neu co.
  • speckleObjectId: root/metadata object id toi thieu de version hop le trong Speckle.
  • tileElementId: compact integer/hash dung trong tile binary va GPU picking.
  • selectionId: id viewer dung de highlight, comments, saved views; phai map duoc nguoc ve GlobalId/Speckle resource khi co.

Khong nen de comment/saved view/filter dua truc tiep vao expressId neu muon on dinh qua retry/reconvert.

Indexes:

  1. Element index:
{
  "elementId": "ifc-global-id-or-express-id",
  "expressId": 12345,
  "globalId": "0LAVzkQQb2CQ05u6y7TlEm",
  "ifcType": "IFCBEAM",
  "name": "BEAM",
  "bbox": [...],
  "tileIds": ["tile_000123"],
  "propertyOffset": 987654
}
  1. Property store:
  • JSONL, SQLite, binary key-value, or blob chunks.
  • Load by element id.
  • Can use Postgres for small metadata, but blob/index is safer for huge models.
  1. Hierarchy index:
  • Project/site/building/storey/category/tree.
  • Lazy expand tree nodes.

4.7 Manifest

Manifest la entry point cho viewer.

{
  "schema": "speckle-large-model-manifest",
  "version": 1,
  "projectId": "20a00d4e0e",
  "modelId": "10db533311",
  "versionId": "bd21945129",
  "units": "m",
  "bounds": [0, 0, 0, 100, 100, 50],
  "stats": {
    "elements": 265739,
    "meshes": 344548,
    "vertexScalarCount": 321017100,
    "estimatedVertices": 53502850,
    "triangles": 0
  },
  "materialsUrl": ".../materials.json",
  "tileIndexUrl": ".../tiles.json",
  "metadataIndexUrl": ".../metadata-index.bin",
  "propertyStoreUrl": ".../properties.bin",
  "tiles": [
    {
      "id": "tile_000001",
      "bbox": [...],
      "lods": [
        { "level": 0, "url": "...", "bytes": 100000, "triangles": 10000 },
        { "level": 1, "url": "...", "bytes": 800000, "triangles": 80000 }
      ]
    }
  ]
}

5. Storage va API

5.1 Storage layout

Blob storage path:

viewer-derivatives/
  {projectId}/
    {versionId}/
      runs/
        {runId}/
          manifest.json.tmp
          tiles/
          metadata/
      published/
        manifest.json
        tiles.json
        materials.json
        metadata-index.bin
        properties.bin
        tiles/
          tile_000001_lod0.bin
          tile_000001_lod1.bin
          tile_000001_lod2.bin

Publish contract:

  • Write all artifacts under runs/{runId}.
  • Validate manifest references and content hashes.
  • Publish manifest last by copying/moving to immutable published/ prefix or by marking manifest_key active in DB.
  • Retry is idempotent: abandon failed runId and GC later, or clean that exact staging prefix before retry.
  • Never expose a run manifest until derivative row is ready.

Postgres:

viewer_derivatives
  id
  project_id
  model_id
  version_id
  root_object_id
  derivative_type
  schema_version
  run_id
  generation
  converter_version
  source_file_hash
  source_root_object_id
  status
  stage
  progress
  manifest_key
  manifest_hash
  tile_count
  tile_bytes
  metadata_bytes
  stats_json
  error_code
  error_message
  retry_count
  next_retry_at
  created_at
  updated_at
  published_at

Constraints:

unique(project_id, version_id, derivative_type, schema_version)

Important: khong nen reuse blob_storage user-visible uploads cho hang nghin tile. Derivative assets can metadata/storage rieng de khong lam ban blob listing/quota va de GC theo prefix.

5.2 API endpoints

MVP endpoints:

GET /api/viewer-derivatives/:projectId/:versionId/manifest
GET /api/viewer-derivatives/:projectId/:versionId/tile-url/:tileId/:lod
GET /api/viewer-derivatives/:projectId/:versionId/materials
POST /api/viewer-derivatives/:projectId/:versionId/metadata/batch
POST /api/viewer-derivatives/:projectId/:versionId/properties/batch
GET /api/viewer-derivatives/:projectId/:versionId/hierarchy/root
GET /api/viewer-derivatives/:projectId/:versionId/hierarchy/:nodeId/children

Auth:

  • Verify version belongs to project/model and apply same visibility/history-limit semantics as Version.referencedObject.
  • Enforce token scopes/resource limits and public project rules.
  • Prefer short-lived signed GET URLs or CDN object URLs for tiles; avoid proxying every tile through Express.
  • Cache-control cho tiles: immutable if version immutable.

Lifecycle hooks:

  • Version delete/project delete must enqueue derivative prefix cleanup.
  • Project region move/copy must include derivative asset prefixes and DB rows.
  • Orphaned staging prefixes must be discoverable and garbage-collected.

6. Viewer architecture moi

6.1 Loader selection

Viewer route can quyet dinh loader:

  • Normal model: Speckle object loader existing.
  • Large model: derivative tile loader.

Critical: selection phai dien ra truoc khi goi current object loading. Neu resource query chi tra {modelId, versionId, objectId} va FE luon tao SpeckleLoader, browser van se download/traverse full Speckle object iterator. GraphQL viewer resource can tra derivative status/manifest URL va FE auto-load phai branch sang derivative loader truoc viewer.loadObject().

Detection:

  • GraphQL version field co viewerDerivative.status = ready.
  • Hoac version stats indicates large model.
  • Neu derivative processing hoac failed, viewer hien state tuong ung va khong fallback sang full geometry khi model vuot nguong.

6.2 Manifest loader

Flow:

  1. Fetch manifest.
  2. Create tile tree.
  3. Render bounding box / LOD0 visible tiles.
  4. Start camera-driven tile scheduler.

6.3 Tile scheduler

Moi camera update hoac debounce khi camera stop:

  1. Frustum culling tile bbox.
  2. Compute screen-space error.
  3. Choose LOD per tile.
  4. Prioritize:
    • visible center tiles
    • lower LOD first
    • selected/hovered area
    • section box area
  5. Queue downloads with concurrency limit.
  6. Cancel deprioritized requests.

Pseudo:

const visibleTiles = tileTree.queryFrustum(camera.frustum)
const desired = visibleTiles.map((tile) => ({
  tile,
  lod: chooseLod(tile, camera, memoryBudget)
}))
tileCache.reconcile(desired)

6.4 Memory budget

Hard limits:

maxCpuGeometryMB: 1200
maxGpuGeometryMB: 1500
maxActiveTileCount: configurable

Budget phai tinh peak decoded, khong chi compressed tile bytes:

  • downloaded/compressed bytes
  • decoded positions/normals/indices/colors
  • worker transfer buffers
  • BVH/TAS or picking acceleration data
  • Three.js/WebGL GPU buffers
  • render targets, DPR, postprocessing overhead

When over budget:

  • unload LRU invisible tiles
  • downgrade far visible tiles
  • keep selected/highlighted tiles
  • keep LOD0 overview if possible

Metrics tracked:

  • bytes downloaded
  • decoded CPU bytes
  • estimated GPU bytes
  • active triangles
  • active draw calls
  • FPS

6.5 GPU batching

Tile renderer:

  • One BufferGeometry per tile/material group, or one merged tile geometry with material groups.
  • Avoid one Three.js Mesh per IFC element.
  • Selection mapping via element ranges:
    • indexStart/indexCount -> elementId
    • optional picking id buffer for GPU picking later.
  • Add tile-level lifetime API: load, attach, detach, dispose CPU buffers, dispose GPU buffers, dispose BVH, cancel request, release worker memory.

6.6 Picking and selection

MVP:

  • CPU raycast against loaded detailed tiles.
  • If only LOD0 loaded, selection is approximate or disabled; click can request detailed tile first.

Better:

  • GPU picking pass with encoded element id.
  • On click:
    • identify element id
    • fetch properties on demand
    • highlight all ranges for element id

6.7 Metadata and tree UI

Tree panel must not require full metadata upfront.

Existing tree/filter/selection features assume full WorldTree and loaded NodeRenderView. Large-model mode must either create synthetic selectable nodes or use a parallel derivative-backed API. Hide/isolate/color filters must be gated until async hierarchy/property indexes exist.

APIs:

GET /hierarchy/root
GET /hierarchy/node/:nodeId/children
GET /properties/:elementId
GET /search?ifcType=IFCBEAM

MVP tree:

  • Storey/category grouping.
  • Lazy expand.
  • Selecting a group loads/isolates relevant tiles.

7. Backward compatibility

Keep existing Speckle version commit:

  • The version still exists in project/model timeline.
  • The version still needs a small valid root_object_id because current version creation validates objectId.
  • For small models, current viewer behavior unchanged.
  • For large models, viewer uses derivative path.
  • Existing object graph can be optional or reduced:
    • metadata-only Speckle objects
    • simplified preview object
    • no full displayValue geometry for huge models

Important: comments/saved views need use concrete resource ids and camera state, but geometry payload comes from derivative.

8. Implementation phases

Phase -1: Feasibility gates

Deliverables:

  • Native ABI smoke tests giua C# va C++ exports.
  • Streaming geometry prototype khong build full Dictionary<uint, List<MeshData>>.
  • Converter RSS benchmark tren file 250 MB.
  • Tile binary round-trip test: bbox, material, element ids, ranges, optional instance transforms.
  • Viewer prototype add/remove tile GPU resources khong di qua full SpeckleLoader/WorldTree.
  • Identity mapping spec: expressId, GlobalId, Speckle root object id, tileElementId, selectionId.

Acceptance:

  • Peak RSS converter duoi 8 GB tren sample 250 MB, hoac co bang chung native layer chua the stream va can patch WebIFC.
  • Viewer load/unload 1000 tile synthetic khong tang JS heap/GPU memory lien tuc.
  • Browser khong request Speckle displayValue cho model marked large.

Decision gate:

  • Neu fail native streaming, khong build Phase 1 tren duong C# iterator; phai sua native/WebIFC layer truoc.

Phase 0: Instrumentation and guardrails

Deliverables:

  • Log peak memory in converter.
  • Store import stats: elements, meshes, vertices, triangles, file size.
  • Detect large model and mark it.
  • Large-model branch before NativeIfcGeometry.ExtractGeometry() and before full Speckle geometry patch path.
  • Prevent browser auto-load full geometry if too large.
  • Show "large model processing" state.
  • Feature flag and kill switch for derivative viewer.
  • Derivative statuses: queued, processing, ready, failed.
  • Minimal root object strategy for large versions.

Acceptance:

  • File 250 MB does not crash browser; viewer shows a large-model placeholder instead of full load.
  • Failed/processing derivative never falls back to full JSON geometry for large models.

Phase 1: Narrow derivative MVP

Deliverables:

  • Converter writes a minimal valid Speckle root object plus derivative manifest and tile binaries.
  • Large mode bypasses GeometryInjector full-object patch/recommit path.
  • Basic out-of-core spatial tiling with LOD0 overview and optional LOD1; no element-accurate picking guarantee.
  • Separate derivative storage metadata, staging run prefix, atomic manifest publish.
  • Retry-safe generation for one failed run; orphan staging GC.
  • API endpoint for manifest and signed tile URLs.
  • Viewer resource query returns derivative status/manifest before normal object loading.
  • Viewer derivative loader loads visible tiles without calling SpeckleLoader.
  • Memory budget and tile unload.
  • Debug overlay for tile count, decoded bytes, estimated GPU bytes, active triangles, draw calls, FPS.

Acceptance:

  • File 250 MB first visual under 10s after derivative is ready.
  • Browser RAM under 3 GB.
  • Orbit works without OOM.
  • No property tree, full filter, comments, saved-view parity, or exact selection requirement yet.

Phase 2: LOD and progressive loading

Deliverables:

  • LOD0/LOD1/LOD2 generation.
  • Screen-space LOD selection.
  • Manifest fields for hierarchy, geometric error, parent/child refinement, byte/triangle cost, hysteresis.
  • Request cancellation and prioritization.
  • Basic metadata/property on click.
  • Tile cache metrics in debug panel.
  • DPR caps, reduced render passes, context-loss recovery.

Acceptance:

  • First visual under 5s.
  • Orbit 30 FPS on common workstation.
  • Detail appears when zooming in.
  • Tile eviction verified under a fixed memory budget.

Phase 3: Selection, metadata, filters

Deliverables:

  • Element-level picking.
  • Property panel fetch on demand.
  • Storey/category tree lazy loading.
  • Hide/isolate by element/group.
  • Section box affects tile selection.
  • Batch metadata/property endpoints and chunked property store.
  • Synthetic viewer nodes or parallel selection API for derivative mode.

Acceptance:

  • User can select object and see IFC properties.
  • Isolate a floor/category without loading entire model.
  • Selection id maps back to GlobalId/Speckle resource id where available.

Phase 4: Optimization

Deliverables:

  • Quantized positions.
  • Meshopt/Draco compression.
  • GPU picking.
  • Instancing repeated geometry.
  • Worker-thread decode.
  • Persistent browser cache.

Acceptance:

  • Reduced tile bytes by 50%+ from MVP.
  • Stable viewer memory under configured budget.
  • Smooth interaction on large files.

Phase 5: Production hardening

Deliverables:

  • Advanced retry/resume derivative generation.
  • Partial failure reporting and admin repair tools.
  • Backfill command for existing versions.
  • Monitoring dashboard.
  • Feature flag rollout.
  • Migration docs.
  • CDN/cache policy, region copy/delete hooks, derivative quota/cost controls.

Acceptance:

  • Failed derivative jobs can be retried safely.
  • Users do not lose access to old small-model behavior.

9. Testing and benchmarks

Test files:

  • Small IFC under 10 MB.
  • Medium IFC 50-100 MB.
  • Large IFC 250 MB current sample.
  • Synthetic stress file with many repeated instances.

Converter metrics:

  • peak RSS
  • duration per stage
  • tile count
  • tile size distribution
  • LOD triangle counts
  • derivative total bytes

Viewer metrics:

  • time to manifest loaded
  • time to first tile rendered
  • time to first meaningful render
  • peak JS heap
  • estimated GPU memory
  • active tiles
  • active triangles
  • FPS during orbit
  • tile cache hit/miss

Benchmark contract:

  • Define browser version, OS, GPU, RAM, screen resolution, DPR cap, network profile.
  • Use fixed camera/orbit script and fixed derivative-ready state.
  • Record cold-cache and warm-cache results separately.
  • Record max requested bytes before first render.
  • Record peak decoded bytes, not only compressed tile bytes.

Target for current 250 MB sample:

first render: < 5s
JS heap: < 1500 MB
GPU estimate: < 1500 MB
active triangles overview: < 5M
orbit FPS: >= 30

10. Risks

WebIFC cannot truly stream geometry

If native LoadModel still builds all geometry in memory, converter RAM remains high.

Mitigation:

  • Patch WebIFC geometry processor to generate per element and release.
  • Or run derivative generation on larger worker temporarily.

LOD loses element selection fidelity

Simplified geometry may merge elements.

Mitigation:

  • Keep element ranges where possible.
  • For overview LOD, selection triggers detailed tile load.

Metadata index becomes too large

Properties for 265k elements can be large.

Mitigation:

  • Store properties compressed by chunk.
  • Lazy load per element/group.
  • Separate searchable fields from full property payload.

Viewer complexity grows

Tile streaming introduces cache, scheduler, cancellation, and memory management.

Mitigation:

  • Keep large-model loader separate from existing Speckle loader.
  • Feature flag.
  • Start MVP with limited feature set.

Existing viewer APIs assume complete WorldTree

Selection, filtering, tree, property search, comments, and saved views may assume all objects/nodes are present client-side.

Mitigation:

  • Gate unsupported features in derivative mode.
  • Add synthetic nodes or derivative-backed async APIs.
  • Keep exact compatibility out of MVP unless proven.

Tile/object storage cost explosion

Thousands of tile objects can increase object storage cost, list latency, cleanup complexity, and region migration time.

Mitigation:

  • Bundle small tiles where useful.
  • Track tile count/bytes per derivative.
  • Use immutable prefixes and prefix-level GC.
  • Add quotas and dashboards before broad rollout.

Long skinny BIM elements break simple tiling

Elements crossing many spatial cells can inflate duplication or produce poor culling.

Mitigation:

  • Support overflow tiles for large/long elements.
  • Use adaptive split plus per-element bbox heuristics.
  • Track duplication ratio as converter metric.

Browser GPU limits and driver instability

Even if JS heap is controlled, GPU buffers, render targets, high DPR, or BVHs can trigger context loss.

Mitigation:

  • Cap DPR and active GPU bytes in large mode.
  • Disable expensive passes by default.
  • Handle WebGL context loss and tile reload.
  • Keep per-tile vertex/index sizes below conservative limits.

11. Suggested code areas

Converter:

  • packages/fileimport-service/src/ifc-dotnet/NativeIfcGeometry.cs
  • packages/fileimport-service/src/ifc-dotnet/GeometryInjector.cs
  • packages/fileimport-service/src/ifc-dotnet/Program.cs
  • native libweb-ifc wrapper/export layer
  • packages/ifc-import-service/src/ifc_importer/process_job.py

Viewer:

  • packages/viewer/src/modules/loaders
  • packages/frontend-2/lib/viewer/composables/setup
  • packages/frontend-2/lib/viewer/graphql/queries.ts
  • new derivative API client/loader

Server/API:

  • file upload/import result handling
  • project/model/version GraphQL fields for derivative status
  • blob storage integration
  • new REST endpoints for derivative manifest/tiles/metadata

MVP nen lam it nhat:

  1. Large model detection and no-full-load guard.
  2. Minimal valid Speckle root object + derivative status/manifest reference.
  3. Converter output LOD0 overview + LOD1 tiled binary through out-of-core path.
  4. Atomic manifest publish and retry-safe staging prefix.
  5. Viewer derivative loader with frustum culling and memory budget.
  6. Debug overlay for tile/memory/FPS.
  7. Explicit processing/failed UI with no fallback to full geometry.

Khong nen lam trong MVP:

  • Full Forge-level property search.
  • Perfect element-level selection on simplified LOD.
  • Full tree/filter/hide/isolate parity.
  • Comments/saved-view compatibility beyond camera restore.
  • Large multi-model federation.
  • Mobile/low-memory guarantee.
  • Advanced compression beyond gzip/brotli.

13. Forge-like gap matrix

Forge-like trai nghiem khong chi la tile renderer. Can theo doi cac gap sau:

Area Required capability MVP stance
Translation lifecycle queued/processing/ready/failed, progress, retry, manifest publish Required
Viewable manifest versioned schema, hashes, byte counts, tile tree Required
Fragment-object mapping triangle/range/tile id to element/global id Required basic
Object tree/property DB lazy hierarchy and batch properties Basic later
CDN/range/cache immutable tile URLs, cache headers, signed access Required basic
Schema migration derivative schema version and backfill path Later
Failure semantics no full-load fallback, explicit failed state Required
Feature parity selection, filters, comments, saved views Not MVP

14. Decision summary

Dung huong:

  • Out-of-core converter.
  • Binary tiled geometry.
  • LOD.
  • Viewer streaming and memory budget.
  • Metadata lazy loading.

Khong dung huong:

  • Redis as geometry spill storage.
  • Browser load full Speckle JSON graph/full display geometry for large IFC, especially when native logs show hundreds of millions of vertex scalars.
  • Increasing server RAM only, while browser still loads everything.
  • One Three.js mesh per IFC element for huge models.

This plan turns large IFC viewing into a derivative streaming problem, which is the same class of solution used by Forge-like systems.