Phase 5 batch: door + window migrate to registry (always-on)
Both kinds share traits — hosted on walls, cuttable, animated open/
close state via a geometry system + animation system. Stage A
migration: register + wrap-export the legacy renderer + bundle both
per-kind systems. Pure geometry + floor-plan ports are later
milestones.
Files added (packages/nodes/src/door/, packages/nodes/src/window/):
- schema.ts: re-export from core.
- parametrics.ts: minimal — dimensions only. Door has 29 sliders +
segmented controls + presets in its legacy panel; window has 15+
sliders. Auto-inspector can't cover them at Stage A — legacy
panel keeps rendering via panel-manager.tsx case fall-through.
Stage E may extend parametrics or use parametrics.customPanel
escape hatch.
- definition.ts: capabilities (no `movable` — wall-bound drag is
bespoke; capability-driven dispatch keeps legacy MoveDoorTool /
MoveWindowTool), parametrics, renderer, system. defaults() uses
`DoorNode.parse({...stub})` to leverage zod's schema-level
`.default()` annotations — door has 40+ fields, window has 20+;
listing them inline duplicates the schema.
- renderer.tsx: wrap-export of legacy DoorRenderer / WindowRenderer
(thin 33-36 lines each).
- system.tsx: bundles each kind's TWO systems — DoorSystem +
DoorAnimationSystem, WindowSystem + WindowAnimationSystem. Both
per-kind systems mount via RegisteredSystems when the kind is
registry-driven; `<LegacySystem kind="door|window">` wrappers
around each individual system short-circuit.
- index.ts: barrel.
Files changed:
- packages/viewer/src/index.ts: new public exports for DoorRenderer,
DoorSystem, DoorAnimationSystem, WindowRenderer, WindowSystem,
WindowAnimationSystem.
- packages/nodes/src/index.ts: appends doorDefinition + windowDefinition.
- packages/editor/src/components/ui/panels/door-panel.tsx + window-
panel.tsx: panel slider-drag fix recipe applied. Drop the
subscribed `updateNode` action, drop the `node` dep from
handleUpdate / previewDoorUpdate / commitDoorPreview useCallbacks.
Use useScene.getState() inside. Door panel has 29 SliderControls,
window 15+ — both at high risk of the Maximum update depth
cascade without the fix.
Phase 5 progress: shelf ✅, spawn ✅, wall ✅, fence ✅, slab ✅, ceiling ✅,
door ✅, window ✅. Eight kinds on the registry. Item / stair / roof /
zone / containers remain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2dd50fa5be
commit
9eced06f32
@@ -109,7 +109,6 @@ function isSameDoorValue(current: unknown, next: unknown): boolean {
|
|||||||
export function DoorPanel() {
|
export function DoorPanel() {
|
||||||
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
||||||
const setSelection = useViewer((s) => s.setSelection)
|
const setSelection = useViewer((s) => s.setSelection)
|
||||||
const updateNode = useScene((s) => s.updateNode)
|
|
||||||
const deleteNode = useScene((s) => s.deleteNode)
|
const deleteNode = useScene((s) => s.deleteNode)
|
||||||
const setMovingNode = useEditor((s) => s.setMovingNode)
|
const setMovingNode = useEditor((s) => s.setMovingNode)
|
||||||
const previewRef = useRef<{
|
const previewRef = useRef<{
|
||||||
@@ -124,9 +123,11 @@ export function DoorPanel() {
|
|||||||
selectedId ? (s.nodes[selectedId as AnyNode['id']] as DoorNode | undefined) : undefined,
|
selectedId ? (s.nodes[selectedId as AnyNode['id']] as DoorNode | undefined) : undefined,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Panel slider-drag fix recipe (plans/editor-node-registry.md). Without
|
||||||
|
// it, the 29+ SliderControls in this panel would loop on drag.
|
||||||
const handleUpdate = useCallback(
|
const handleUpdate = useCallback(
|
||||||
(updates: Partial<DoorNode>) => {
|
(updates: Partial<DoorNode>) => {
|
||||||
if (!(selectedId && node)) return
|
if (!selectedId) return
|
||||||
const liveNode = useScene.getState().nodes[selectedId as AnyNodeId]
|
const liveNode = useScene.getState().nodes[selectedId as AnyNodeId]
|
||||||
if (liveNode?.type !== 'door') return
|
if (liveNode?.type !== 'door') return
|
||||||
|
|
||||||
@@ -139,12 +140,12 @@ export function DoorPanel() {
|
|||||||
if ('operationState' in updates || 'swingAngle' in updates || 'doorType' in updates) {
|
if ('operationState' in updates || 'swingAngle' in updates || 'doorType' in updates) {
|
||||||
useInteractive.getState().removeDoorOpenState(selectedId as AnyNodeId)
|
useInteractive.getState().removeDoorOpenState(selectedId as AnyNodeId)
|
||||||
}
|
}
|
||||||
updateNode(selectedId as AnyNode['id'], updates)
|
useScene.getState().updateNode(selectedId as AnyNode['id'], updates)
|
||||||
const scene = useScene.getState()
|
const scene = useScene.getState()
|
||||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||||
if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId)
|
if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId)
|
||||||
},
|
},
|
||||||
[selectedId, node, updateNode],
|
[selectedId],
|
||||||
)
|
)
|
||||||
|
|
||||||
const previewDoorUpdate = useCallback(
|
const previewDoorUpdate = useCallback(
|
||||||
@@ -188,10 +189,10 @@ export function DoorPanel() {
|
|||||||
}
|
}
|
||||||
previewRef.current = null
|
previewRef.current = null
|
||||||
|
|
||||||
updateNode(selectedId as AnyNode['id'], { [key]: value } as Partial<DoorNode>)
|
useScene.getState().updateNode(selectedId as AnyNode['id'], { [key]: value } as Partial<DoorNode>)
|
||||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||||
},
|
},
|
||||||
[selectedId, updateNode],
|
[selectedId],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ const silllessWindowTypes = new Set<WindowNode['windowType']>(['bay', 'bow'])
|
|||||||
export function WindowPanel() {
|
export function WindowPanel() {
|
||||||
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
||||||
const setSelection = useViewer((s) => s.setSelection)
|
const setSelection = useViewer((s) => s.setSelection)
|
||||||
const updateNode = useScene((s) => s.updateNode)
|
|
||||||
const deleteNode = useScene((s) => s.deleteNode)
|
const deleteNode = useScene((s) => s.deleteNode)
|
||||||
const setMovingNode = useEditor((s) => s.setMovingNode)
|
const setMovingNode = useEditor((s) => s.setMovingNode)
|
||||||
const previewRef = useRef<{
|
const previewRef = useRef<{
|
||||||
@@ -108,9 +107,11 @@ export function WindowPanel() {
|
|||||||
selectedId ? (s.nodes[selectedId as AnyNode['id']] as WindowNode | undefined) : undefined,
|
selectedId ? (s.nodes[selectedId as AnyNode['id']] as WindowNode | undefined) : undefined,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Panel slider-drag fix recipe (plans/editor-node-registry.md). Without
|
||||||
|
// it, the 15+ SliderControls in this panel would loop on drag.
|
||||||
const handleUpdate = useCallback(
|
const handleUpdate = useCallback(
|
||||||
(updates: Partial<WindowNode>) => {
|
(updates: Partial<WindowNode>) => {
|
||||||
if (!(selectedId && node)) return
|
if (!selectedId) return
|
||||||
const liveNode = useScene.getState().nodes[selectedId as AnyNodeId]
|
const liveNode = useScene.getState().nodes[selectedId as AnyNodeId]
|
||||||
if (liveNode?.type !== 'window') return
|
if (liveNode?.type !== 'window') return
|
||||||
|
|
||||||
@@ -120,12 +121,12 @@ export function WindowPanel() {
|
|||||||
})
|
})
|
||||||
if (!hasChange) return
|
if (!hasChange) return
|
||||||
|
|
||||||
updateNode(selectedId as AnyNode['id'], updates)
|
useScene.getState().updateNode(selectedId as AnyNode['id'], updates)
|
||||||
const scene = useScene.getState()
|
const scene = useScene.getState()
|
||||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||||
if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId)
|
if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId)
|
||||||
},
|
},
|
||||||
[selectedId, node, updateNode],
|
[selectedId],
|
||||||
)
|
)
|
||||||
|
|
||||||
const previewWindowUpdate = useCallback(
|
const previewWindowUpdate = useCallback(
|
||||||
@@ -169,10 +170,12 @@ export function WindowPanel() {
|
|||||||
}
|
}
|
||||||
previewRef.current = null
|
previewRef.current = null
|
||||||
|
|
||||||
updateNode(selectedId as AnyNode['id'], { [key]: value } as Partial<WindowNode>)
|
useScene.getState().updateNode(selectedId as AnyNode['id'], {
|
||||||
|
[key]: value,
|
||||||
|
} as Partial<WindowNode>)
|
||||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||||
},
|
},
|
||||||
[selectedId, updateNode],
|
[selectedId],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import type { NodeDefinition } from '@pascal-app/core'
|
||||||
|
import { doorParametrics } from './parametrics'
|
||||||
|
import { DoorNode } from './schema'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Door — Phase 5 batch kind. Hosted on walls, cuts holes in them,
|
||||||
|
* animated open/close state.
|
||||||
|
*
|
||||||
|
* Capabilities:
|
||||||
|
* - **No `movable`**: door's move is bespoke wall-bound drag (slide
|
||||||
|
* along the wall, snap to wall start/end). Capability-driven dispatch
|
||||||
|
* keeps legacy `MoveDoorTool`.
|
||||||
|
* - `selectable`, `duplicable`, `deletable` standard.
|
||||||
|
*
|
||||||
|
* Relations:
|
||||||
|
* - `parentId` references a wall — re-anchors on wall move (handled by
|
||||||
|
* `DoorSystem`'s cascade to parent wall).
|
||||||
|
* - `cascadeDelete: 'children'` — door has no children in v1.
|
||||||
|
*
|
||||||
|
* Renderer + system: wrap-export legacy `DoorRenderer` + bundle
|
||||||
|
* `DoorSystem` + `DoorAnimationSystem`.
|
||||||
|
*
|
||||||
|
* Tool field absent: door placement / move tools wired through editor
|
||||||
|
* state, not registry dispatch. Legacy DoorTool / MoveDoorTool continue.
|
||||||
|
*/
|
||||||
|
export const doorDefinition: NodeDefinition<typeof DoorNode> = {
|
||||||
|
kind: 'door',
|
||||||
|
schemaVersion: 1,
|
||||||
|
schema: DoorNode,
|
||||||
|
category: 'structure',
|
||||||
|
|
||||||
|
// Leverage the schema's zod `.default()` annotations to compute the
|
||||||
|
// full default shape — door has 40+ fields, listing them inline would
|
||||||
|
// duplicate the schema. Parse a minimal stub, drop id/type, return rest.
|
||||||
|
defaults: () => {
|
||||||
|
const stub = DoorNode.parse({ id: 'door_default' as never, type: 'door' })
|
||||||
|
const { id: _id, type: _type, ...rest } = stub
|
||||||
|
return rest
|
||||||
|
},
|
||||||
|
|
||||||
|
capabilities: {
|
||||||
|
selectable: { hitVolume: 'bbox' },
|
||||||
|
duplicable: true,
|
||||||
|
deletable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
parametrics: doorParametrics,
|
||||||
|
|
||||||
|
renderer: {
|
||||||
|
kind: 'parametric',
|
||||||
|
module: () => import('./renderer'),
|
||||||
|
},
|
||||||
|
system: {
|
||||||
|
module: () => import('./system'),
|
||||||
|
// Priority 3 mirrors the legacy DoorSystem (after animation at 2,
|
||||||
|
// before wall mitering at 4).
|
||||||
|
priority: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
toolHints: [
|
||||||
|
{ key: 'Left click', label: 'Place door on wall' },
|
||||||
|
{ key: 'Esc', label: 'Cancel' },
|
||||||
|
],
|
||||||
|
|
||||||
|
presentation: {
|
||||||
|
label: 'Door',
|
||||||
|
description: 'A door cut into a wall. Animated open/close state.',
|
||||||
|
icon: { kind: 'iconify', name: 'lucide:door-open' },
|
||||||
|
paletteSection: 'structure',
|
||||||
|
paletteOrder: 50,
|
||||||
|
},
|
||||||
|
|
||||||
|
mcp: {
|
||||||
|
description: 'A door mounted on a wall, with type / dimensions / hardware options.',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { doorDefinition } from './definition'
|
||||||
|
export { DoorNode } from './schema'
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { ParametricDescriptor } from '@pascal-app/core'
|
||||||
|
import type { DoorNode } from './schema'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal inspector descriptor for door. The legacy `<DoorPanel>` has
|
||||||
|
* 29 SliderControls covering segments, hardware, hinges, panic bar,
|
||||||
|
* opening shape, etc. — too elaborate for the auto-inspector at Stage A.
|
||||||
|
* Legacy panel keeps rendering via the hardcoded `case 'door':` in
|
||||||
|
* panel-manager.tsx. This descriptor only exposes the simple dimension
|
||||||
|
* fields so the registry knows door has parametric data. Phase 5 Stage E
|
||||||
|
* (drop legacy panel) will extend this — likely via
|
||||||
|
* `parametrics.customPanel?` since door has too much non-numeric UI
|
||||||
|
* (segmented controls, presets) to fit the generic auto-UI.
|
||||||
|
*/
|
||||||
|
export const doorParametrics: ParametricDescriptor<DoorNode> = {
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
label: 'Dimensions',
|
||||||
|
fields: [
|
||||||
|
{ key: 'width', kind: 'number', unit: 'm', min: 0.5, max: 6, step: 0.05 },
|
||||||
|
{ key: 'height', kind: 'number', unit: 'm', min: 1.0, max: 4, step: 0.05 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Frame',
|
||||||
|
fields: [
|
||||||
|
{ key: 'frameThickness', kind: 'number', unit: 'm', min: 0.01, max: 0.2, step: 0.005 },
|
||||||
|
{ key: 'frameDepth', kind: 'number', unit: 'm', min: 0.01, max: 0.3, step: 0.005 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { DoorRenderer } from '@pascal-app/viewer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap-export of the legacy `DoorRenderer`. The renderer is 33 lines
|
||||||
|
* (thin placeholder + register + dirty-on-mount) — could be duplicated
|
||||||
|
* but at Stage A re-export is sufficient. Phase 5 Stage F will inline
|
||||||
|
* it here and delete the viewer-side file.
|
||||||
|
*/
|
||||||
|
export default DoorRenderer
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { DoorNode } from '@pascal-app/core'
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { DoorAnimationSystem, DoorSystem } from '@pascal-app/viewer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry-driven door system bundle. Door has TWO per-frame systems:
|
||||||
|
*
|
||||||
|
* - **`DoorSystem`** — rebuilds frame / leaf / glass / hardware
|
||||||
|
* geometry from `dirtyNodes`. Cascades dirty to the parent wall so
|
||||||
|
* the wall cutout reflects the new door footprint.
|
||||||
|
* - **`DoorAnimationSystem`** — advances `operationState` (open/close
|
||||||
|
* angle for hinged, slide offset for sliding/pocket, fold angle for
|
||||||
|
* folding) at frame priority 2, then marks the door dirty so the
|
||||||
|
* geometry system rebuilds at priority 3.
|
||||||
|
*
|
||||||
|
* Both are wrapped in `<LegacySystem kind="door">` at the legacy mount
|
||||||
|
* point; with door registered, those wrappers short-circuit and this
|
||||||
|
* bundle takes over.
|
||||||
|
*
|
||||||
|
* Future Phase 5 Stage B: extract the geometry into a pure
|
||||||
|
* `buildDoorGeometry(node, ctx)` and migrate to `def.geometry`. The
|
||||||
|
* animation system stays as `def.system` (it's a real per-frame
|
||||||
|
* concern, not a geometry build).
|
||||||
|
*/
|
||||||
|
const DoorSystems = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DoorAnimationSystem />
|
||||||
|
<DoorSystem />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DoorSystems
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import type { AnyNodeDefinition, Plugin } from '@pascal-app/core'
|
import type { AnyNodeDefinition, Plugin } from '@pascal-app/core'
|
||||||
import { ceilingDefinition } from './ceiling'
|
import { ceilingDefinition } from './ceiling'
|
||||||
|
import { doorDefinition } from './door'
|
||||||
import { fenceDefinition } from './fence'
|
import { fenceDefinition } from './fence'
|
||||||
import { shelfDefinition } from './shelf'
|
import { shelfDefinition } from './shelf'
|
||||||
import { slabDefinition } from './slab'
|
import { slabDefinition } from './slab'
|
||||||
import { spawnDefinition } from './spawn'
|
import { spawnDefinition } from './spawn'
|
||||||
import { wallDefinition } from './wall'
|
import { wallDefinition } from './wall'
|
||||||
|
import { windowDefinition } from './window'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Built-in plugin bundling every node kind shipped with the Pascal editor.
|
* Built-in plugin bundling every node kind shipped with the Pascal editor.
|
||||||
@@ -34,12 +36,16 @@ export const builtinPlugin: Plugin = {
|
|||||||
fenceDefinition as unknown as AnyNodeDefinition,
|
fenceDefinition as unknown as AnyNodeDefinition,
|
||||||
slabDefinition as unknown as AnyNodeDefinition,
|
slabDefinition as unknown as AnyNodeDefinition,
|
||||||
ceilingDefinition as unknown as AnyNodeDefinition,
|
ceilingDefinition as unknown as AnyNodeDefinition,
|
||||||
|
doorDefinition as unknown as AnyNodeDefinition,
|
||||||
|
windowDefinition as unknown as AnyNodeDefinition,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ceilingDefinition } from './ceiling'
|
export { ceilingDefinition } from './ceiling'
|
||||||
|
export { doorDefinition } from './door'
|
||||||
export { fenceDefinition } from './fence'
|
export { fenceDefinition } from './fence'
|
||||||
export { shelfDefinition } from './shelf'
|
export { shelfDefinition } from './shelf'
|
||||||
export { slabDefinition } from './slab'
|
export { slabDefinition } from './slab'
|
||||||
export { spawnDefinition } from './spawn'
|
export { spawnDefinition } from './spawn'
|
||||||
export { wallDefinition } from './wall'
|
export { wallDefinition } from './wall'
|
||||||
|
export { windowDefinition } from './window'
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import type { NodeDefinition } from '@pascal-app/core'
|
||||||
|
import { windowParametrics } from './parametrics'
|
||||||
|
import { WindowNode } from './schema'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Window — Phase 5 batch kind. Mirrors door's shape: hosted on walls,
|
||||||
|
* cuts holes in them, animated open/close state for opening windows.
|
||||||
|
*
|
||||||
|
* Capabilities: no `movable` (wall-bound drag is bespoke). Tool field
|
||||||
|
* absent (legacy WindowTool / MoveWindowTool continue).
|
||||||
|
*/
|
||||||
|
export const windowDefinition: NodeDefinition<typeof WindowNode> = {
|
||||||
|
kind: 'window',
|
||||||
|
schemaVersion: 1,
|
||||||
|
schema: WindowNode,
|
||||||
|
category: 'structure',
|
||||||
|
|
||||||
|
// Same schema-driven defaults trick as door: parse a stub, strip
|
||||||
|
// id/type. Window also has many fields with zod `.default()` set.
|
||||||
|
defaults: () => {
|
||||||
|
const stub = WindowNode.parse({ id: 'window_default' as never, type: 'window' })
|
||||||
|
const { id: _id, type: _type, ...rest } = stub
|
||||||
|
return rest
|
||||||
|
},
|
||||||
|
|
||||||
|
capabilities: {
|
||||||
|
selectable: { hitVolume: 'bbox' },
|
||||||
|
duplicable: true,
|
||||||
|
deletable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
parametrics: windowParametrics,
|
||||||
|
|
||||||
|
renderer: {
|
||||||
|
kind: 'parametric',
|
||||||
|
module: () => import('./renderer'),
|
||||||
|
},
|
||||||
|
system: {
|
||||||
|
module: () => import('./system'),
|
||||||
|
priority: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
toolHints: [
|
||||||
|
{ key: 'Left click', label: 'Place window on wall' },
|
||||||
|
{ key: 'Esc', label: 'Cancel' },
|
||||||
|
],
|
||||||
|
|
||||||
|
presentation: {
|
||||||
|
label: 'Window',
|
||||||
|
description: 'A window cut into a wall. Animated open/close for opening windows.',
|
||||||
|
icon: { kind: 'iconify', name: 'lucide:rectangle-horizontal' },
|
||||||
|
paletteSection: 'structure',
|
||||||
|
paletteOrder: 60,
|
||||||
|
},
|
||||||
|
|
||||||
|
mcp: {
|
||||||
|
description: 'A window mounted on a wall, with type / dimensions / opening options.',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { windowDefinition } from './definition'
|
||||||
|
export { WindowNode } from './schema'
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { ParametricDescriptor } from '@pascal-app/core'
|
||||||
|
import type { WindowNode } from './schema'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal inspector descriptor for window. Like door, the legacy
|
||||||
|
* `<WindowPanel>` has 15+ SliderControls covering sashes, dividers,
|
||||||
|
* sill, frame, opening shape — too elaborate for the auto-inspector
|
||||||
|
* at Stage A. Legacy panel keeps rendering via panel-manager's
|
||||||
|
* `case 'window':`.
|
||||||
|
*/
|
||||||
|
export const windowParametrics: ParametricDescriptor<WindowNode> = {
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
label: 'Dimensions',
|
||||||
|
fields: [
|
||||||
|
{ key: 'width', kind: 'number', unit: 'm', min: 0.3, max: 4, step: 0.05 },
|
||||||
|
{ key: 'height', kind: 'number', unit: 'm', min: 0.3, max: 4, step: 0.05 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { WindowRenderer } from '@pascal-app/viewer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap-export of the legacy `WindowRenderer`. Thin (~36 lines); Phase 5
|
||||||
|
* Stage F inlines it here and deletes the viewer-side file.
|
||||||
|
*/
|
||||||
|
export default WindowRenderer
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { WindowNode } from '@pascal-app/core'
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { WindowAnimationSystem, WindowSystem } from '@pascal-app/viewer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry-driven window system bundle. Same shape as door — two
|
||||||
|
* per-frame systems wrapped together:
|
||||||
|
*
|
||||||
|
* - **`WindowSystem`** — rebuilds frame / sash / divider / sill /
|
||||||
|
* muntin geometry. Cascades dirty to parent wall for the cutout.
|
||||||
|
* - **`WindowAnimationSystem`** — advances sash/panel open state at
|
||||||
|
* frame priority 2, then marks the window dirty for the geometry
|
||||||
|
* rebuild at priority 3.
|
||||||
|
*
|
||||||
|
* Both wrapped in `<LegacySystem kind="window">` legacy mounts; with
|
||||||
|
* window registered, those short-circuit and this bundle takes over.
|
||||||
|
*/
|
||||||
|
const WindowSystems = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WindowAnimationSystem />
|
||||||
|
<WindowSystem />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WindowSystems
|
||||||
@@ -8,7 +8,12 @@
|
|||||||
// to duplicate at Stage A. Wrap-exported here, ported into the nodes
|
// to duplicate at Stage A. Wrap-exported here, ported into the nodes
|
||||||
// folder in a later stage.
|
// folder in a later stage.
|
||||||
export { CeilingRenderer } from './components/renderers/ceiling/ceiling-renderer'
|
export { CeilingRenderer } from './components/renderers/ceiling/ceiling-renderer'
|
||||||
|
// Door + window internals for registry-driven door / window definitions.
|
||||||
|
// Each kind has a geometry system + animation system to mount via
|
||||||
|
// RegisteredSystems when the kind is registry-driven.
|
||||||
|
export { DoorRenderer } from './components/renderers/door/door-renderer'
|
||||||
export { NodeRenderer } from './components/renderers/node-renderer'
|
export { NodeRenderer } from './components/renderers/node-renderer'
|
||||||
|
export { WindowRenderer } from './components/renderers/window/window-renderer'
|
||||||
export { default as Viewer } from './components/viewer'
|
export { default as Viewer } from './components/viewer'
|
||||||
export type { HoverStyle, HoverStyles } from './components/viewer/post-processing'
|
export type { HoverStyle, HoverStyles } from './components/viewer/post-processing'
|
||||||
export {
|
export {
|
||||||
@@ -38,6 +43,8 @@ export {
|
|||||||
export { mergedOutline } from './lib/merged-outline-node'
|
export { mergedOutline } from './lib/merged-outline-node'
|
||||||
export { default as useViewer } from './store/use-viewer'
|
export { default as useViewer } from './store/use-viewer'
|
||||||
export { CeilingSystem } from './systems/ceiling/ceiling-system'
|
export { CeilingSystem } from './systems/ceiling/ceiling-system'
|
||||||
|
export { DoorAnimationSystem } from './systems/door/door-animation-system'
|
||||||
|
export { DoorSystem } from './systems/door/door-system'
|
||||||
// Fence system follows the wall re-export pattern — composed into the
|
// Fence system follows the wall re-export pattern — composed into the
|
||||||
// registry-driven fence definition's `def.system`. Removed in Phase 6
|
// registry-driven fence definition's `def.system`. Removed in Phase 6
|
||||||
// alongside the legacy fence mount point.
|
// alongside the legacy fence mount point.
|
||||||
@@ -57,3 +64,5 @@ export { getVisibleWallMaterials } from './systems/wall/wall-materials'
|
|||||||
// 800+ lines of CSG / mitering logic during Phase 3. These exports are
|
// 800+ lines of CSG / mitering logic during Phase 3. These exports are
|
||||||
// removed in Phase 6 when the legacy mount points are deleted.
|
// removed in Phase 6 when the legacy mount points are deleted.
|
||||||
export { WallSystem } from './systems/wall/wall-system'
|
export { WallSystem } from './systems/wall/wall-system'
|
||||||
|
export { WindowAnimationSystem } from './systems/window/window-animation-system'
|
||||||
|
export { WindowSystem } from './systems/window/window-system'
|
||||||
|
|||||||
Reference in New Issue
Block a user