diff --git a/apps/editor/components/ui/panels/panel-manager.tsx b/apps/editor/components/ui/panels/panel-manager.tsx index 6697574a..d5fa0b56 100644 --- a/apps/editor/components/ui/panels/panel-manager.tsx +++ b/apps/editor/components/ui/panels/panel-manager.tsx @@ -8,6 +8,7 @@ import { ItemPanel } from './item-panel' import { ReferencePanel } from './reference-panel' import { RoofPanel } from './roof-panel' import { SlabPanel } from './slab-panel' +import { WallPanel } from './wall-panel' import { WindowPanel } from './window-panel' export function PanelManager() { @@ -34,6 +35,8 @@ export function PanelManager() { return case 'ceiling': return + case 'wall': + return case 'window': return } diff --git a/apps/editor/components/ui/panels/wall-panel.tsx b/apps/editor/components/ui/panels/wall-panel.tsx new file mode 100644 index 00000000..c51b937f --- /dev/null +++ b/apps/editor/components/ui/panels/wall-panel.tsx @@ -0,0 +1,108 @@ +'use client' + +import { type AnyNode, type AnyNodeId, type WallNode, useScene } from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { X } from 'lucide-react' +import Image from 'next/image' +import { useCallback } from 'react' +import { NumberInput } from '@/components/ui/primitives/number-input' + +export function WallPanel() { + const selectedIds = useViewer((s) => s.selection.selectedIds) + const setSelection = useViewer((s) => s.setSelection) + const nodes = useScene((s) => s.nodes) + const updateNode = useScene((s) => s.updateNode) + + const selectedId = selectedIds[0] + const node = selectedId + ? (nodes[selectedId as AnyNode['id']] as WallNode | undefined) + : undefined + + const handleUpdate = useCallback( + (updates: Partial) => { + if (!selectedId) return + updateNode(selectedId as AnyNode['id'], updates) + useScene.getState().dirtyNodes.add(selectedId as AnyNodeId) + }, + [selectedId, updateNode], + ) + + const handleClose = useCallback(() => { + setSelection({ selectedIds: [] }) + }, [setSelection]) + + if (!node || node.type !== 'wall' || selectedIds.length !== 1) return null + + const dx = node.end[0] - node.start[0] + const dz = node.end[1] - node.start[1] + const length = Math.sqrt(dx * dx + dz * dz) + + const height = node.height ?? 2.5 + const thickness = node.thickness ?? 0.1 + + return ( +
+ {/* Header */} +
+
+ +

+ {node.name || `Wall (${length.toFixed(2)}m)`} +

+
+ +
+ + {/* Content */} +
+ + {/* Dimensions */} +
+ +
+ handleUpdate({ height: Math.max(0.1, v) })} + min={0.1} + precision={2} + step={0.1} + className="flex-1" + /> + m +
+
+ handleUpdate({ thickness: Math.max(0.05, v) })} + min={0.05} + precision={3} + step={0.01} + className="flex-1" + /> + m +
+
+ + {/* Info */} +
+ +
+ Length: {length.toFixed(2)} m +
+
+
+
+ ) +} diff --git a/packages/core/src/systems/wall/wall-system.tsx b/packages/core/src/systems/wall/wall-system.tsx index 31208a1c..13589518 100644 --- a/packages/core/src/systems/wall/wall-system.tsx +++ b/packages/core/src/systems/wall/wall-system.tsx @@ -132,7 +132,7 @@ function updateWallGeometry(wallId: string, miterData: WallMiterData) { collisionMesh.geometry = collisionGeo } - mesh.position.set(node.start[0], 0, node.start[1]) + mesh.position.set(node.start[0], slabElevation, node.start[1]) const angle = Math.atan2(node.end[1] - node.start[1], node.end[0] - node.start[0]) mesh.rotation.y = -angle } @@ -153,8 +153,10 @@ export function generateExtrudedWall( const wallStart: Point2D = { x: wallNode.start[0], y: wallNode.start[1] } const wallEnd: Point2D = { x: wallNode.end[0], y: wallNode.end[1] } - // Wall height is adjusted by slab elevation (positive reduces, negative increases) - const height = (wallNode.height ?? 2.5) - slabElevation + // Positive slab: shift the whole wall up (full height preserved) + // Negative slab: extend wall downward so top stays fixed at wallNode.height + const wallHeight = wallNode.height ?? 2.5 + const height = slabElevation > 0 ? wallHeight : wallHeight - slabElevation const thickness = wallNode.thickness ?? 0.1 const halfT = thickness / 2 @@ -248,10 +250,6 @@ export function generateExtrudedWall( // Rotate so extrusion direction (Z) becomes height direction (Y) geometry.rotateX(-Math.PI / 2) - // Translate by slab elevation (works for both positive and negative values) - if (slabElevation !== 0) { - geometry.translate(0, slabElevation, 0) - } geometry.computeVertexNormals() // Apply CSG subtraction for cutouts (doors/windows)