diff --git a/packages/editor/src/components/editor/first-person-controls.tsx b/packages/editor/src/components/editor/first-person-controls.tsx index 35ce42d6..b146d533 100644 --- a/packages/editor/src/components/editor/first-person-controls.tsx +++ b/packages/editor/src/components/editor/first-person-controls.tsx @@ -166,7 +166,10 @@ export const FirstPersonControls = () => { node.doorType === 'folding' || node.doorType === 'pocket' || node.doorType === 'barn' || - node.doorType === 'sliding' + node.doorType === 'sliding' || + node.doorType === 'garage-sectional' || + node.doorType === 'garage-rollup' || + node.doorType === 'garage-tiltup' ) { const currentOpenAmount = node.operationState ?? 0 updateDoorOpenState(doorId, { diff --git a/packages/editor/src/components/editor/floorplan-panel.tsx b/packages/editor/src/components/editor/floorplan-panel.tsx index df6f4a1e..0cb75168 100644 --- a/packages/editor/src/components/editor/floorplan-panel.tsx +++ b/packages/editor/src/components/editor/floorplan-panel.tsx @@ -4820,6 +4820,23 @@ const FloorplanGeometryLayer = memo(function FloorplanGeometryLayer({ } const slidingFixedPoints = slidingPanelPoints(slidingFixedCenter, doorCubeSize * 0.34) const slidingActivePoints = slidingPanelPoints(slidingActiveCenter, doorCubeSize * 0.68) + const isGarageSectionalDoor = opening.doorType === 'garage-sectional' + const isGarageRollupDoor = opening.doorType === 'garage-rollup' + const isGarageTiltupDoor = opening.doorType === 'garage-tiltup' + const garagePanelCount = Math.max(3, Math.min(12, opening.garagePanelCount ?? 4)) + const garagePanelLines = Array.from({ length: garagePanelCount - 1 }, (_, index) => { + const t = (index + 1) / garagePanelCount + return { + start: { + x: svgP1.x + (svgP2.x - svgP1.x) * t, + y: svgP1.y + (svgP2.y - svgP1.y) * t, + }, + end: { + x: svgP1.x + (svgP2.x - svgP1.x) * t + px * swingSign * doorCubeSize * 0.78, + y: svgP1.y + (svgP2.y - svgP1.y) * t + py * swingSign * doorCubeSize * 0.78, + }, + } + }) const isDoubleSwingDoor = opening.doorType === 'double' || opening.doorType === 'french' const doubleLeafPlans = isDoubleSwingDoor ? ( @@ -5215,6 +5232,66 @@ const FloorplanGeometryLayer = memo(function FloorplanGeometryLayer({ vectorEffect="non-scaling-stroke" /> + ) : isGarageSectionalDoor || isGarageRollupDoor || isGarageTiltupDoor ? ( + <> + + + {isGarageRollupDoor ? ( + + ) : isGarageTiltupDoor ? ( + + ) : ( + garagePanelLines.map((line, index) => ( + + )) + )} + ) : isDoubleSwingDoor ? ( <> {doubleLeafPlans.map((leaf) => diff --git a/packages/editor/src/components/ui/panels/door-panel.tsx b/packages/editor/src/components/ui/panels/door-panel.tsx index f0b5c480..77b009d7 100755 --- a/packages/editor/src/components/ui/panels/door-panel.tsx +++ b/packages/editor/src/components/ui/panels/door-panel.tsx @@ -37,6 +37,16 @@ const doorTypeOptions = [ available: boolean }[] +const garageDoorTypeOptions = [ + { label: 'Sectional', value: 'garage-sectional', available: true }, + { label: 'Roll-up', value: 'garage-rollup', available: true }, + { label: 'Tilt-up', value: 'garage-tiltup', available: true }, +] satisfies { + label: string + value: DoorNode['doorType'] + available: boolean +}[] + const frenchDoorSegments: DoorNode['segments'] = [ { type: 'glass', @@ -328,7 +338,13 @@ export function DoorPanel() { const doorType = node.doorType ?? 'hinged' const isSwingDoor = doorType === 'hinged' || doorType === 'double' || doorType === 'french' const isSlidingDoor = doorType === 'pocket' || doorType === 'barn' || doorType === 'sliding' + const isGarageDoor = node.doorCategory === 'garage' || doorType.startsWith('garage-') + const isSectionalGarageDoor = doorType === 'garage-sectional' + const isRollupGarageDoor = doorType === 'garage-rollup' + const isTiltupGarageDoor = doorType === 'garage-tiltup' + const typeMode = isOpening ? 'opening' : isGarageDoor ? 'garage' : 'door' const supportsHandleSide = isSwingDoor + const maxDoorWidth = isGarageDoor ? 6 : 3 const setOpeningTopRadius = (index: number, value: number, commit = false) => { const next = [...openingTopRadii] as [number, number] @@ -427,11 +443,60 @@ export function DoorPanel() { } } + if (nextDoorType === 'garage-sectional') { + return { + doorCategory: 'garage', + doorType: nextDoorType, + leafCount: 1, + ...dimensionUpdates, + handle: false, + threshold: false, + trackStyle: 'overhead', + operationState: 0, + garagePanelCount: Math.max(3, Math.min(8, node.garagePanelCount ?? 4)), + contentPadding: [0.04, 0.04], + segments: foldingDoorSegments, + } + } + + if (nextDoorType === 'garage-rollup') { + return { + doorCategory: 'garage', + doorType: nextDoorType, + leafCount: 1, + ...dimensionUpdates, + handle: false, + threshold: false, + trackStyle: 'overhead', + operationState: 0, + garagePanelCount: 4, + contentPadding: [0.04, 0.04], + segments: foldingDoorSegments, + } + } + + if (nextDoorType === 'garage-tiltup') { + return { + doorCategory: 'garage', + doorType: nextDoorType, + leafCount: 1, + ...dimensionUpdates, + handle: false, + threshold: false, + trackStyle: 'overhead', + operationState: 0, + garagePanelCount: 4, + contentPadding: [0.04, 0.04], + segments: foldingDoorSegments, + } + } + return { doorCategory: 'interior', doorType: nextDoorType, leafCount: 1, ...dimensionUpdates, + threshold: true, } } @@ -478,19 +543,28 @@ export function DoorPanel() { archHeight, openingRevealRadius, } - : { openingKind: v }, + : v === 'garage' + ? { + openingKind: 'door', + ...getDoorTypeUpdates(isGarageDoor ? doorType : 'garage-sectional'), + } + : { + openingKind: 'door', + ...(isGarageDoor ? getDoorTypeUpdates('hinged') : {}), + }, ) } options={[ { label: 'Door', value: 'door' }, { label: 'Opening', value: 'opening' }, + { label: 'Garage', value: 'garage' }, ]} - value={node.openingKind} + value={typeMode} /> {!isOpening && (
- {doorTypeOptions.map((option) => { + {(isGarageDoor ? garageDoorTypeOptions : doorTypeOptions).map((option) => { const isSelected = doorType === option.value return (