Files
editor/packages/viewer
Wassim SAMADandClaude Opus 4.7 b6d77206b4 Phase 2 spike: spawn migration (flagged) + new shelf node
The first time registry-driven nodes actually run in the editor.

Spawn migration (under NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN flag):
- New packages/nodes/src/spawn/ folder with renderer, tool, schema
  (re-exported from core), parametrics, definition, index.
- Spawn definition appended to builtinPlugin.nodes only when the flag
  is set. With the flag off, the Phase 0 shims fall through and the
  legacy SpawnRenderer / SpawnTool keep ownership.
- New no-props SpawnTool reads activeLevelId from useViewer directly,
  matches legacy placement behavior (half-meter snap, singleton-per-
  level, replace-on-reclick).
- Structural parity test (9 cases) validates definition shape +
  schema identity. Pixel-diff defers to Phase 4 alongside more nodes.

New shelf node (no legacy — registered unconditionally):
- ShelfNode schema in core/schema/nodes/shelf.ts (hand-maintained
  AnyNode union for now; Phase 6 derives the union from the registry
  and moves the schema fully into nodes/shelf/).
- packages/nodes/src/shelf/ folder: pure geometry builder
  (buildShelfGeometry returns a Three.js Group of top board +
  brackets), R3F renderer that mounts the built group, no-props
  placement tool, parametrics descriptor (width/depth/thickness/
  height/bracketStyle/color), definition with surfaces.top stackable
  surface for future stacking, and presentation metadata for the
  palette.
- 13 unit tests across schema bounds and geometry behavior.
- Palette wiring: 'shelf' added to StructureTool union + an entry in
  the structure-tools array (placeholder icon, replaced in Phase 4
  when palette is registry-driven).

Framework changes:
- @pascal-app/viewer now exports useNodeEvents from its public barrel
  so node bundles in @pascal-app/nodes can subscribe to node-specific
  pointer events. (Used by spawn renderer; shelf renderer skips it
  for now since useNodeEvents has a hardcoded kind list — Phase 4
  generalizes it via the registry.)
- @pascal-app/nodes gains @pascal-app/viewer as a peer + dev dep so
  node bundles can import from it.

630 tests pass across 76 files (22 new this phase). Editor app
continues to ship green with both legacy spawn and the new shelf
node co-existing through the Phase 0 dispatch shims.

To validate end-to-end in dev:
- bun dev:community → open editor → click 'Shelf' in structure
  toolbar → click to place. Confirms full registry path
  (NodeRenderer dispatch + ToolManager dispatch + sceneRegistry
  byType Proxy).
- Set NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN=1, restart dev, place spawn
  → visually identical to legacy. Confirms parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:07:38 -04:00
..
2026-02-05 08:22:19 +09:00

@pascal-app/viewer

3D viewer component for Pascal building editor.

Installation

npm install @pascal-app/viewer @pascal-app/core

Peer Dependencies

npm install react three @react-three/fiber @react-three/drei

What's Included

  • Viewer Component - WebGPU-powered 3D viewer with camera controls
  • Node Renderers - React Three Fiber components for all node types
  • Post-Processing - SSGI (ambient occlusion + global illumination), TRAA (anti-aliasing), outline effects
  • Level System - Level visibility and positioning (stacked/exploded/solo modes)
  • Wall Cutout System - Dynamic wall hiding based on camera position
  • Asset URL Helpers - CDN URL resolution for models and textures

Usage

import { Viewer, useViewer } from '@pascal-app/viewer'
import { useScene } from '@pascal-app/core'

function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Viewer />
    </div>
  )
}

Custom Camera Controls

import { Viewer } from '@pascal-app/viewer'
import { CameraControls } from '@react-three/drei'

function App() {
  return (
    <Viewer selectionManager="custom">
      <CameraControls />
    </Viewer>
  )
}

Viewer State

import { useViewer } from '@pascal-app/viewer'

function ViewerControls() {
  const levelMode = useViewer(s => s.levelMode)
  const setLevelMode = useViewer(s => s.setLevelMode)
  const wallMode = useViewer(s => s.wallMode)
  const setWallMode = useViewer(s => s.setWallMode)

  return (
    <div>
      <button onClick={() => setLevelMode('stacked')}>Stacked</button>
      <button onClick={() => setLevelMode('exploded')}>Exploded</button>
      <button onClick={() => setWallMode('cutaway')}>Cutaway</button>
      <button onClick={() => setWallMode('up')}>Full Height</button>
    </div>
  )
}

Asset CDN Helpers

import { resolveCdnUrl, ASSETS_CDN_URL } from '@pascal-app/viewer'

// Resolves relative paths to CDN URLs
const url = resolveCdnUrl('/items/chair/model.glb')
// → 'https://pascal-cdn.wawasensei.dev/items/chair/model.glb'

// Handles external URLs and asset:// protocol
const externalUrl = resolveCdnUrl('https://example.com/model.glb')
// → 'https://example.com/model.glb' (unchanged)

Features

  • WebGPU Rendering - Hardware-accelerated rendering via Three.js WebGPU
  • Post-Processing - SSGI for realistic lighting, outline effects for selection
  • Level Modes - Stacked, exploded, or solo level display
  • Wall Cutaway - Automatic wall hiding for interior views
  • Camera Modes - Perspective and orthographic projection
  • Scan/Guide Support - 3D scans and 2D guide images

License

MIT