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>
49 lines
1.9 KiB
TypeScript
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
|
|
}
|