From affffe09c801ab0c17fef12fea0a11621cc1803c Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 14:08:25 +0530 Subject: [PATCH] feat: measurement tool axis lock added, style update, no animation ui update --- .../properties/AssetProperties.tsx | 11 +- .../contextControls/contextControls.tsx | 441 +++++++++--------- .../modules/scene/tools/measurementTool.tsx | 93 +++- app/src/styles/layout/sidebar.scss | 4 + 4 files changed, 332 insertions(+), 217 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 4f76584..5d0a97c 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -103,9 +103,16 @@ const AssetProperties: React.FC = () => {
Animations
- {assets.map((asset) => { + {assets.map((asset, i) => { if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations) - return null; + return ( + i === 0 && ( +
+ Looks like there are no preset animations yet. Stay tuned for + future additions! +
+ ) + ); return asset.animations.map((animation, index) => (
diff --git a/app/src/modules/scene/controls/contextControls/contextControls.tsx b/app/src/modules/scene/controls/contextControls/contextControls.tsx index 1d0bbfa..142f8d1 100644 --- a/app/src/modules/scene/controls/contextControls/contextControls.tsx +++ b/app/src/modules/scene/controls/contextControls/contextControls.tsx @@ -1,222 +1,243 @@ -import { useEffect, useRef, useState } from 'react'; -import { useThree } from '@react-three/fiber'; -import { CameraControls, Html, ScreenSpace } from '@react-three/drei'; -import { useContextActionStore, useRenameModeStore, useSelectedAssets } from '../../../../store/builder/store'; -import ContextMenu from '../../../../components/ui/menu/contextMenu'; +import { useEffect, useRef, useState } from "react"; +import { useThree } from "@react-three/fiber"; +import { CameraControls, Html, ScreenSpace } from "@react-three/drei"; +import { + useContextActionStore, + useRenameModeStore, + useSelectedAssets, +} from "../../../../store/builder/store"; +import ContextMenu from "../../../../components/ui/menu/contextMenu"; function ContextControls() { - const { gl, controls } = useThree(); - const [canRender, setCanRender] = useState(false); - const [visibility, setVisibility] = useState({ rename: true, focus: true, flipX: true, flipZ: true, move: true, rotate: true, duplicate: true, copy: true, paste: true, modifier: false, group: false, array: false, delete: true, }); - const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); - const { selectedAssets } = useSelectedAssets(); - const { setContextAction } = useContextActionStore(); - const { setIsRenameMode } = useRenameModeStore(); - const rightDrag = useRef(false); - const isRightMouseDown = useRef(false); + const { gl, controls } = useThree(); + const [canRender, setCanRender] = useState(false); + const [visibility, setVisibility] = useState({ + rename: true, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: false, + array: false, + delete: true, + }); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + const { selectedAssets } = useSelectedAssets(); + const { setContextAction } = useContextActionStore(); + const { setIsRenameMode } = useRenameModeStore(); + const rightDrag = useRef(false); + const isRightMouseDown = useRef(false); - useEffect(() => { - if (selectedAssets.length === 1) { - setVisibility({ - rename: true, - focus: true, - flipX: true, - flipZ: true, - move: true, - rotate: true, - duplicate: true, - copy: true, - paste: true, - modifier: false, - group: false, - array: false, - delete: true, - }); - } else if (selectedAssets.length > 1) { - setVisibility({ - rename: false, - focus: true, - flipX: true, - flipZ: true, - move: true, - rotate: true, - duplicate: true, - copy: true, - paste: true, - modifier: false, - group: true, - array: false, - delete: true, - }); - } else { - setVisibility({ - rename: false, - focus: false, - flipX: false, - flipZ: false, - move: false, - rotate: false, - duplicate: false, - copy: false, - paste: false, - modifier: false, - group: false, - array: false, - delete: false, - }); + useEffect(() => { + if (selectedAssets.length === 1) { + setVisibility({ + rename: true, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: false, + array: false, + delete: true, + }); + } else if (selectedAssets.length > 1) { + setVisibility({ + rename: false, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: true, + array: false, + delete: true, + }); + } else { + setVisibility({ + rename: false, + focus: false, + flipX: false, + flipZ: false, + move: false, + rotate: false, + duplicate: false, + copy: false, + paste: false, + modifier: false, + group: false, + array: false, + delete: false, + }); + } + }, [selectedAssets]); + + useEffect(() => { + const canvasElement = gl.domElement; + + const onPointerDown = (evt: any) => { + if (evt.button === 2) { + isRightMouseDown.current = true; + rightDrag.current = false; + } + }; + + const onPointerMove = () => { + if (isRightMouseDown.current) { + rightDrag.current = true; + } + }; + + const onPointerUp = (evt: any) => { + if (evt.button === 2) { + isRightMouseDown.current = false; + } + }; + + const handleContextClick = (event: MouseEvent) => { + event.preventDefault(); + if (rightDrag.current) return; + if (selectedAssets.length > 0) { + setMenuPosition({ + x: event.clientX - gl.domElement.width / 2, + y: event.clientY - gl.domElement.height / 2, + }); + setCanRender(true); + if (controls) { + (controls as CameraControls).enabled = false; } - }, [selectedAssets]); - - useEffect(() => { - const canvasElement = gl.domElement; - - const onPointerDown = (evt: any) => { - if (evt.button === 2) { - isRightMouseDown.current = true; - rightDrag.current = false; - } - }; - - const onPointerMove = () => { - if (isRightMouseDown.current) { - rightDrag.current = true; - } - }; - - const onPointerUp = (evt: any) => { - if (evt.button === 2) { - isRightMouseDown.current = false; - } - }; - - const handleContextClick = (event: MouseEvent) => { - event.preventDefault(); - if (rightDrag.current) return; - if (selectedAssets.length > 0) { - setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 }); - setCanRender(true); - if (controls) { - (controls as CameraControls).enabled = false; - } - } else { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - } - }; - - if (selectedAssets.length > 0) { - canvasElement.addEventListener('pointerdown', onPointerDown); - canvasElement.addEventListener('pointermove', onPointerMove); - canvasElement.addEventListener('pointerup', onPointerUp); - canvasElement.addEventListener('contextmenu', handleContextClick) - } else { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setMenuPosition({ x: 0, y: 0 }); - } - - return () => { - canvasElement.removeEventListener('pointerdown', onPointerDown); - canvasElement.removeEventListener('pointermove', onPointerMove); - canvasElement.removeEventListener('pointerup', onPointerUp); - canvasElement.removeEventListener('contextmenu', handleContextClick); - }; - }, [gl, selectedAssets]); - - const handleAssetRename = () => { + } else { setCanRender(false); if (controls) { - (controls as CameraControls).enabled = true; + (controls as CameraControls).enabled = true; } - setContextAction("renameAsset"); - setIsRenameMode(true); - } - const handleAssetFocus = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("focusAsset"); - } - const handleAssetMove = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("moveAsset") - } - const handleAssetRotate = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("rotateAsset") - } - const handleAssetCopy = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("copyAsset") - } - const handleAssetPaste = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("pasteAsset") - } - const handleAssetDelete = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("deleteAsset") - } - const handleAssetDuplicate = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("duplicateAsset") + } + }; + + if (selectedAssets.length > 0) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + canvasElement.addEventListener("contextmenu", handleContextClick); + } else { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setMenuPosition({ x: 0, y: 0 }); } - return ( - <> - {canRender && ( - - - handleAssetRename()} - onFocus={() => handleAssetFocus()} - onFlipX={() => console.log("Flip to X")} - onFlipZ={() => console.log("Flip to Z")} - onMove={() => handleAssetMove()} - onRotate={() => handleAssetRotate()} - onDuplicate={() => handleAssetDuplicate()} - onCopy={() => handleAssetCopy()} - onPaste={() => handleAssetPaste()} - onGroup={() => console.log("Group")} - onArray={() => console.log("Array")} - onDelete={() => handleAssetDelete()} - /> - - - )} - - ); + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + canvasElement.removeEventListener("contextmenu", handleContextClick); + }; + }, [controls, gl, selectedAssets]); + + const handleAssetRename = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("renameAsset"); + setIsRenameMode(true); + }; + const handleAssetFocus = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("focusAsset"); + }; + const handleAssetMove = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("moveAsset"); + }; + const handleAssetRotate = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("rotateAsset"); + }; + const handleAssetCopy = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("copyAsset"); + }; + const handleAssetPaste = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("pasteAsset"); + }; + const handleAssetDelete = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("deleteAsset"); + }; + const handleAssetDuplicate = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("duplicateAsset"); + }; + + return ( + <> + {canRender && ( + + + handleAssetRename()} + onFocus={() => handleAssetFocus()} + onFlipX={() => console.log("Flip to X")} + onFlipZ={() => console.log("Flip to Z")} + onMove={() => handleAssetMove()} + onRotate={() => handleAssetRotate()} + onDuplicate={() => handleAssetDuplicate()} + onCopy={() => handleAssetCopy()} + onPaste={() => handleAssetPaste()} + onGroup={() => console.log("Group")} + onArray={() => console.log("Array")} + onDelete={() => handleAssetDelete()} + /> + + + )} + + ); } export default ContextControls; diff --git a/app/src/modules/scene/tools/measurementTool.tsx b/app/src/modules/scene/tools/measurementTool.tsx index 4ca56fd..9f8189d 100644 --- a/app/src/modules/scene/tools/measurementTool.tsx +++ b/app/src/modules/scene/tools/measurementTool.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useThree, useFrame } from "@react-three/fiber"; import { useToolMode } from "../../../store/builder/store"; import { Html, Line } from "@react-three/drei"; @@ -10,7 +10,81 @@ const MeasurementTool = () => { const [points, setPoints] = useState([]); const [linePoints, setLinePoints] = useState(null); + const [axisLock, setAxisLock] = useState<"x" | "y" | "z" | null>(null); const groupRef = useRef(null); + const keysPressed = useRef>(new Set()); + + // Axis lock key handling + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.altKey) { + if (e.key.toLowerCase() === "x") keysPressed.current.add("x"); + else if (e.key.toLowerCase() === "y") keysPressed.current.add("y"); + else if (e.key.toLowerCase() === "z") keysPressed.current.add("z"); + + if (keysPressed.current.has("x")) setAxisLock("x"); + else if (keysPressed.current.has("y")) setAxisLock("y"); + else if (keysPressed.current.has("z")) setAxisLock("z"); + } else if (e.key === "Escape") { + setPoints([]); + setLinePoints(null); + setAxisLock(null); + } + }; + + const handleKeyUp = (e: KeyboardEvent) => { + keysPressed.current.delete(e.key.toLowerCase()); + if (keysPressed.current.has("x")) setAxisLock("x"); + else if (keysPressed.current.has("y")) setAxisLock("y"); + else if (keysPressed.current.has("z")) setAxisLock("z"); + else setAxisLock(null); + }; + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }, []); + + const getLineColor = useCallback(() => { + switch (axisLock) { + case "x": + return "#d94522"; // Red for X axis + case "y": + return "#22ab2e"; // Green for Y axis + case "z": + return "#227bd9"; // Blue for Z axis + default: + return "#b18ef1"; // Default purple + } + }, [axisLock]); + + // Apply axis lock to a point + const applyAxisLock = useCallback( + (point: THREE.Vector3, referencePoint: THREE.Vector3) => { + const lockedPoint = point.clone(); + + switch (axisLock) { + case "x": + lockedPoint.y = referencePoint.y; + lockedPoint.z = referencePoint.z; + break; + case "y": + lockedPoint.x = referencePoint.x; + lockedPoint.z = referencePoint.z; + break; + case "z": + lockedPoint.x = referencePoint.x; + lockedPoint.y = referencePoint.y; + break; + } + + return lockedPoint; + }, + [axisLock] + ); useEffect(() => { const canvasElement = gl.domElement; @@ -45,7 +119,13 @@ const MeasurementTool = () => { ); if (intersects.length > 0) { - const intersectionPoint = intersects[0].point.clone(); + let intersectionPoint = intersects[0].point.clone(); + if (axisLock && points.length > 0) { + intersectionPoint = applyAxisLock( + intersectionPoint, + points[points.length - 1] + ); + } if (points.length < 2) { setPoints([...points, intersectionPoint]); } else { @@ -84,7 +164,7 @@ const MeasurementTool = () => { canvasElement.removeEventListener("contextmenu", onContextMenu); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [toolMode, camera, raycaster, pointer, scene, points]); + }, [toolMode, camera, raycaster, pointer, scene, points, axisLock]); useFrame(() => { if (points.length === 1) { @@ -107,7 +187,10 @@ const MeasurementTool = () => { ); if (intersects.length > 0) { - const tempEnd = intersects[0].point.clone(); + let tempEnd = intersects[0].point.clone(); + if (axisLock) { + tempEnd = applyAxisLock(tempEnd, points[0]); + } updateMeasurement(points[0], tempEnd); } } else if (points.length === 2) { @@ -139,7 +222,7 @@ const MeasurementTool = () => { {/* Main line */}