Phase 4 follow-on: registry-driven floor-plan rendering + shelf port

Adds the floor-plan side of the three-checkbox composition model
documented in wiki/architecture/node-definitions.md. Mirrors the 3D
side (def.geometry → <GeometrySystem> → <ParametricNodeRenderer>) but
emits SVG primitives instead of three.js Object3Ds, and runs inside
the floor-plan panel.

Type-side (packages/core/src/registry/types.ts):
 - New FloorplanGeometry tagged union covering path / polygon /
   polyline / rect / circle / line / group. FloorplanStyle props map
   straight to SVG attributes. Coordinates are level-local meters;
   rotations are radians (three.js convention).
 - New `def.floorplan?: (node, ctx) => FloorplanGeometry | null` field
   on NodeDefinition, independent of `geometry` and `renderer`. Re-
   exported via packages/core/src/registry/index.ts.

Runtime (packages/editor):
 - <FloorplanGeometryRenderer> walks the FloorplanGeometry tree and
   emits the matching React-SVG elements. Pure data → DOM; no per-kind
   logic.
 - <FloorplanRegistryLayer> reads the active levelId, walks the level
   subtree, looks up each node's def.floorplan, builds a
   GeometryContext, calls the builder, and renders the output via
   FloorplanGeometryRenderer.
 - Mounted in floorplan-panel.tsx just before <FloorplanMarqueeLayer>
   so registry-driven kinds layer above legacy inline content.

Shelf migration (proof port):
 - New nodes/src/shelf/floorplan.ts — buildShelfFloorplan(node) emits
   a group with the rotation/translation transform and a width × depth
   rectangle in the shelf's color. Brackets omitted (hidden under top
   board from above).
 - Wired to shelfDefinition.floorplan. Shelf now appears in the floor
   plan view for the first time (was missing from the legacy panel's
   inline switch).

Pattern proven; every future kind migrating in Phase 5 follows the
same shape: a pure (node, ctx) => FloorplanGeometry function. As kinds
register their floor-plan builders, the corresponding inline branches
in floorplan-panel.tsx become dead code and can be deleted in the
same PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-15 10:34:42 -04:00
co-authored by Claude Opus 4.7
parent 3f3818f3b0
commit ac36297e7e
7 changed files with 385 additions and 7 deletions
+8 -6
View File
@@ -1,4 +1,5 @@
import type { NodeDefinition } from '@pascal-app/core'
import { buildShelfFloorplan } from './floorplan'
import { buildShelfGeometry } from './geometry'
import { shelfParametrics } from './parametrics'
import { ShelfNode } from './schema'
@@ -43,14 +44,15 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
parametrics: shelfParametrics,
// Three-checkbox composition: shelf needs only a pure geometry function.
// The framework's <ParametricNodeRenderer> mounts an empty group + wires
// events / registry / dirty-on-mount; the global <GeometrySystem> calls
// `buildShelfGeometry(node)` on every dirty mark and swaps the group's
// children. No `renderer.tsx`, no `system.tsx` — see
// Three-checkbox composition: shelf needs only pure builder functions.
// The framework's <ParametricNodeRenderer> + <GeometrySystem> handle 3D
// mount and rebuild on dirty; the <FloorplanRegistryLayer> calls
// buildShelfFloorplan for the 2D top-down view. No renderer.tsx, no
// system.tsx, no inline floor-plan SVG — see
// `wiki/architecture/node-definitions.md`. Shelf is the reference port
// proving Phase 4's boilerplate collapse end-to-end.
// proving Phase 4's boilerplate collapse for both 3D and 2D.
geometry: buildShelfGeometry,
floorplan: buildShelfFloorplan,
preview: () => import('./preview'),
tool: () => import('./tool'),
+43
View File
@@ -0,0 +1,43 @@
import type { FloorplanGeometry } from '@pascal-app/core'
import type { ShelfNode } from './schema'
/**
* 2D floor-plan representation of a shelf. The top board (the largest
* visible surface from above) projects to a rectangle of `width × depth`
* centered on `(position.x, position.z)`, rotated by the shelf's Y angle.
*
* Brackets are intentionally omitted — they're hidden under the top
* board from a top-down view, and adding them as separate rects clutters
* the plan without conveying useful information at typical zoom levels.
*
* Coordinates are level-local meters; the floor-plan panel applies the
* world→SVG transform via its viewBox. Rotation is radians (three.js
* convention); the renderer converts to SVG degrees.
*
* Pairs with `buildShelfGeometry(node)` — the 3D builder. Same shape,
* different output projection.
*/
export function buildShelfFloorplan(node: ShelfNode): FloorplanGeometry {
const [px, , pz] = node.position
const ry = node.rotation[1] ?? 0
const halfW = node.width / 2
const halfD = node.depth / 2
return {
kind: 'group',
transform: { translate: [px, pz], rotate: ry },
children: [
{
kind: 'rect',
x: -halfW,
y: -halfD,
width: node.width,
height: node.depth,
fill: node.color,
stroke: '#1f2937',
strokeWidth: 0.015,
opacity: 0.9,
},
],
}
}