Files
editor/packages/viewer/src/components/renderers/node-renderer.tsx
T
Wassim SAMADandClaude Opus 4.8 5938cb6805 feat(viewer): strip scans/guides from the bake, re-add from scene data
Scans (LiDAR meshes) and guides (floorplan images) are heavy reference assets
stored elsewhere, not part of the compiled building — and baking them into a
public static GLB would also bypass the per-project show_*_public flags. Strip
both from the export entirely (previously they leaked in as empty identity
nodes, timing-dependent).

The GLB viewer re-adds them at runtime from the scene graph, like lights:
GlbReferenceNodes resolves each scan/guide's registry renderer (no static nodes
import — same nodeRegistry path the viewer already uses) and portals it into its
parent level's baked node, so the node's level-local transform resolves to the
right world pose and rides level stacking. Uses the same GuideRenderer/
ScanRenderer + asset resolver as the parametric viewer, so http-backed assets
show for everyone and local asset:// ones for the owner — exact parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:36:02 -04:00

49 lines
1.9 KiB
TypeScript

'use client'
import { type AnyNode, nodeRegistry, type RendererSource, useScene } from '@pascal-app/core'
import { type ComponentType, lazy, Suspense } from 'react'
import { ParametricNodeRenderer } from './parametric-node-renderer'
// Cache lazy components by their RendererSource so React.lazy isn't re-invoked
// on every render — that would create a new Suspense boundary each time.
const lazyCache = new WeakMap<RendererSource<AnyNode>, ComponentType<{ node: AnyNode }>>()
export function getRegistryRenderer(
source: RendererSource<AnyNode>,
): ComponentType<{ node: AnyNode }> | null {
const cached = lazyCache.get(source)
if (cached) return cached
// GLB / instanced-GLB sources lower onto built-in renderers landed in
// Phase 5 — for now only parametric (lazy module) sources are honored.
if (source.kind !== 'parametric') return null
const Comp = lazy(source.module) as unknown as ComponentType<{ node: AnyNode }>
lazyCache.set(source, Comp)
return Comp
}
export const NodeRenderer = ({ nodeId }: { nodeId: AnyNode['id'] }) => {
const node = useScene((state) => state.nodes[nodeId])
if (!node) return null
const def = nodeRegistry.get(node.type)
if (!def) return null
// Two-checkbox dispatch (see wiki/architecture/node-definitions.md):
// 1. Custom renderer — JSX-side composition for kinds that need GLB,
// drei, <Html>, instancing, shader materials.
// 2. Else, if the kind ships `def.geometry`, the generic empty-group
// <ParametricNodeRenderer> is filled by <GeometrySystem> from the
// pure builder.
if (def.renderer) {
const Renderer = getRegistryRenderer(def.renderer as RendererSource<AnyNode>)
if (!Renderer) return null
return (
<Suspense fallback={null}>
<Renderer node={node} />
</Suspense>
)
}
if (def.geometry) {
return <ParametricNodeRenderer node={node} />
}
return null
}