Phase 5 Stage E: full kind migration into packages/nodes
Wholesale move of every remaining kind into its own subdirectory under
`packages/nodes/src/`, finishing the registry-driven migration. Each
kind now ships its definition, schema (re-exported from core), and any
of `geometry` / `renderer` / `system` / `floorplan` / `tool` /
`move-tool` / `panel` / `floorplan-move` / `floorplan-affordances` /
`parametrics` / `preview` it needs — no per-kind code remains under
`packages/editor/src/components/tools/` or
`packages/viewer/src/components/renderers/`.
Deleted (replaced by registry-driven equivalents):
- `tools/{ceiling,column,door,fence,item,slab,spawn,wall,window}/...`
(boundary editors, hole editors, placement tools, move tools,
endpoint movers, curve tools, helpers, math libs)
- `ui/helpers/{ceiling,slab,wall}-helper.tsx`
- `ui/panels/{column,door,elevator,item,roof,roof-segment,spawn,
stair,stair-segment,wall,window}-panel.tsx`
- `viewer/src/components/renderers/{building,ceiling,column,door,
elevator,fence,guide,item,level,roof,roof-segment,scan,site,slab,
spawn,stair,stair-segment,wall,window,zone}-renderer.tsx`
- `viewer/src/components/viewer/legacy-system.tsx`
Added under `packages/nodes/src/`:
- `building/`, `column/`, `elevator/`, `guide/`, `level/`, `roof/`,
`roof-segment/`, `scan/`, `shared/`, `site/`, `stair/`,
`stair-segment/` packages with definition + schema + renderer / system
/ floorplan / panel as appropriate.
- New `floorplan-move.ts` for every kind that supports 2D moves
(ceiling, door, item, shelf, slab, window) — single registry-driven
dispatch path via `def.floorplanMoveTarget`.
- New `floorplan-affordances.ts` for kinds with polygon / endpoint
drags (ceiling, fence, slab, wall) — using the shared
`polygon-vertex-affordance` factories.
- New per-kind `panel.tsx` for kinds with custom inspector content
(door, item, shelf, spawn, wall, window).
- New per-kind `tool.tsx` for placement (door, item, shelf, window).
- New per-kind `move-tool.tsx` for kinds with custom 3D move flows
(door, item, slab, window).
Coordinator + manager updates in `packages/editor/`:
- `tool-manager.tsx` resolves tools from the registry only — no
hardcoded type→component map.
- `panel-manager.tsx` resolves inspector panels the same way.
- `placement-{coordinator,strategies,types}.ts` extended with
shelf-surface placement.
- `selection-manager.tsx` adds the registry-selectable fallback.
- `floorplan-panel.tsx`, `floorplan-background-placement.ts`,
`floorplan-render-context.tsx` updated for the registry layer's new
contract (props, affordance dispatch, render context).
Viewer updates:
- `viewer/index.tsx` drops legacy renderer mounts.
- `node-renderer.tsx` resolves by registry only.
- `scene-bvh.tsx`, `use-node-events.ts`, `level-system.tsx`,
`wall-cutout.tsx`, `zone-system.tsx`, `materials.ts` adjusted for
the registry-only world.
Sidebar tree nodes for ceiling / fence / slab / shelf / tree-node
updated to read from the registered nodes instead of the deleted
legacy renderer trees.
Wiki: new `plugin-authoring.md` page, README index updated.
Tests in `packages/nodes/src/index.test.ts` validate every registered
kind has the required shape.
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
11015ea1ed
commit
d747d2f0ea
@@ -6,6 +6,7 @@ import {
|
||||
type GeometryContext,
|
||||
getScaledDimensions,
|
||||
type ItemNode,
|
||||
useLiveTransforms,
|
||||
} from '@pascal-app/core'
|
||||
|
||||
/**
|
||||
@@ -25,10 +26,17 @@ import {
|
||||
*/
|
||||
type Transform = { x: number; y: number; rotation: number }
|
||||
|
||||
// Plan-space rotation convention used by the legacy `rotatePlanVector`
|
||||
// in `editor/src/lib/floorplan/geometry.ts`. This is a CLOCKWISE rotation
|
||||
// — the registry-side equivalent of the canonical floor-plan transform
|
||||
// math. Don't switch to a standard counter-clockwise rotation; the wall
|
||||
// items math (wallRotation = -atan2(dy, dx)) is calibrated against this
|
||||
// convention, and the legacy item floor-plan stack reads from these
|
||||
// offsets across many sites.
|
||||
function rotateVec(x: number, y: number, angle: number): [number, number] {
|
||||
const c = Math.cos(angle)
|
||||
const s = Math.sin(angle)
|
||||
return [x * c - y * s, x * s + y * c]
|
||||
return [x * c + y * s, -x * s + y * c]
|
||||
}
|
||||
|
||||
function resolveItemTransform(
|
||||
@@ -75,6 +83,35 @@ function resolveItemTransform(
|
||||
rotation: parentT.rotation + localRotation,
|
||||
}
|
||||
}
|
||||
} else if (parentNode?.type === 'shelf') {
|
||||
// Shelf-hosted item: `item.position` is in shelf-local coords. The
|
||||
// shelf has its own `position` + `rotation[1]` in its parent (level)
|
||||
// frame, so the item's plan-space position composes the shelf's
|
||||
// pose with the item's local offset. Without this branch the
|
||||
// `else` below would treat shelf-local coords as level-local and
|
||||
// the item would render at the wrong spot whenever the shelf is
|
||||
// anywhere other than (0, 0, 0).
|
||||
//
|
||||
// We also check `useLiveTransforms` for the shelf — if the shelf is
|
||||
// mid-move (3D or 2D), its scene-state `position` is still at the
|
||||
// pre-move spot but the live transform carries the cursor-tracked
|
||||
// position. Reading the live value here keeps the hosted item
|
||||
// following the shelf in 2D throughout the drag, mirroring how the
|
||||
// shelf's own entry follows via the layer's effectiveNode override.
|
||||
const shelf = parentNode as AnyNode & {
|
||||
position: [number, number, number]
|
||||
rotation: [number, number, number]
|
||||
}
|
||||
const live = useLiveTransforms.getState().get(shelf.id as AnyNodeId)
|
||||
const shelfX = live?.position[0] ?? shelf.position[0]
|
||||
const shelfZ = live?.position[2] ?? shelf.position[2]
|
||||
const shelfRotationY = live?.rotation ?? shelf.rotation[1] ?? 0
|
||||
const [offsetX, offsetY] = rotateVec(item.position[0], item.position[2], shelfRotationY)
|
||||
result = {
|
||||
x: shelfX + offsetX,
|
||||
y: shelfZ + offsetY,
|
||||
rotation: shelfRotationY + localRotation,
|
||||
}
|
||||
} else {
|
||||
// Level / slab / ceiling parent — item.position is level-local.
|
||||
result = {
|
||||
@@ -116,12 +153,45 @@ export function buildItemFloorplan(node: ItemNode, ctx: GeometryContext): Floorp
|
||||
return [cx + rx, cy + ry] as FloorplanPoint
|
||||
})
|
||||
|
||||
return {
|
||||
kind: 'polygon',
|
||||
points,
|
||||
fill: '#fef3c7',
|
||||
stroke: '#92400e',
|
||||
strokeWidth: 0.012,
|
||||
opacity: 0.85,
|
||||
const isSelected = ctx.viewState?.selected ?? false
|
||||
const floorPlanUrl = node.asset.floorPlanUrl
|
||||
const children: FloorplanGeometry[] = [
|
||||
{
|
||||
kind: 'polygon',
|
||||
points,
|
||||
// When an asset thumbnail is present, the polygon is a transparent
|
||||
// hit-target — the image carries the visual weight. Without a
|
||||
// thumbnail the polygon needs a light fill to read at all.
|
||||
//
|
||||
// `transparent` (not `none`) so the interior remains hit-testable —
|
||||
// the registry layer's wrapping `<g>` only fires onPointerDown when
|
||||
// the child renders a paintable surface. `fill="none"` would make
|
||||
// clicks pass through to whatever's beneath, breaking selection.
|
||||
fill: floorPlanUrl ? 'transparent' : '#fef3c7',
|
||||
stroke: '#92400e',
|
||||
strokeWidth: 0.012,
|
||||
opacity: 0.85,
|
||||
},
|
||||
]
|
||||
// Asset thumbnail — top-down PNG capture from the asset modal. Drawn
|
||||
// inside the footprint with the item's rotation applied. Matches the
|
||||
// legacy `FloorplanItemImage` overlay.
|
||||
if (floorPlanUrl) {
|
||||
children.push({
|
||||
kind: 'image',
|
||||
url: floorPlanUrl,
|
||||
center: [cx, cy],
|
||||
width,
|
||||
height: depth,
|
||||
rotation: transform.rotation,
|
||||
})
|
||||
}
|
||||
// Move handle — orange dot at the item center. Only when selected.
|
||||
if (isSelected) {
|
||||
children.push({
|
||||
kind: 'move-handle',
|
||||
point: [cx, cy],
|
||||
})
|
||||
}
|
||||
return { kind: 'group', children }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user