feat: measurement tool axis lock added, style update, no animation ui update

This commit is contained in:
2025-08-25 14:08:25 +05:30
parent 706f587e72
commit affffe09c8
4 changed files with 332 additions and 217 deletions

View File

@@ -103,9 +103,16 @@ const AssetProperties: React.FC = () => {
</section> </section>
<div className="header">Animations</div> <div className="header">Animations</div>
<section className="animations-lists"> <section className="animations-lists">
{assets.map((asset) => { {assets.map((asset, i) => {
if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations) if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations)
return null; return (
i === 0 && (
<div className="no-animation">
Looks like there are no preset animations yet. Stay tuned for
future additions!
</div>
)
);
return asset.animations.map((animation, index) => ( return asset.animations.map((animation, index) => (
<div key={index} className="animations-list-wrapper"> <div key={index} className="animations-list-wrapper">

View File

@@ -1,13 +1,31 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from "react";
import { useThree } from '@react-three/fiber'; import { useThree } from "@react-three/fiber";
import { CameraControls, Html, ScreenSpace } from '@react-three/drei'; import { CameraControls, Html, ScreenSpace } from "@react-three/drei";
import { useContextActionStore, useRenameModeStore, useSelectedAssets } from '../../../../store/builder/store'; import {
import ContextMenu from '../../../../components/ui/menu/contextMenu'; useContextActionStore,
useRenameModeStore,
useSelectedAssets,
} from "../../../../store/builder/store";
import ContextMenu from "../../../../components/ui/menu/contextMenu";
function ContextControls() { function ContextControls() {
const { gl, controls } = useThree(); const { gl, controls } = useThree();
const [canRender, setCanRender] = useState(false); 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 [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 [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
const { selectedAssets } = useSelectedAssets(); const { selectedAssets } = useSelectedAssets();
const { setContextAction } = useContextActionStore(); const { setContextAction } = useContextActionStore();
@@ -93,7 +111,10 @@ function ContextControls() {
event.preventDefault(); event.preventDefault();
if (rightDrag.current) return; if (rightDrag.current) return;
if (selectedAssets.length > 0) { if (selectedAssets.length > 0) {
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 }); setMenuPosition({
x: event.clientX - gl.domElement.width / 2,
y: event.clientY - gl.domElement.height / 2,
});
setCanRender(true); setCanRender(true);
if (controls) { if (controls) {
(controls as CameraControls).enabled = false; (controls as CameraControls).enabled = false;
@@ -107,10 +128,10 @@ function ContextControls() {
}; };
if (selectedAssets.length > 0) { if (selectedAssets.length > 0) {
canvasElement.addEventListener('pointerdown', onPointerDown); canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener('pointermove', onPointerMove); canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener('pointerup', onPointerUp); canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener('contextmenu', handleContextClick) canvasElement.addEventListener("contextmenu", handleContextClick);
} else { } else {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
@@ -120,12 +141,12 @@ function ContextControls() {
} }
return () => { return () => {
canvasElement.removeEventListener('pointerdown', onPointerDown); canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener('pointermove', onPointerMove); canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener('pointerup', onPointerUp); canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener('contextmenu', handleContextClick); canvasElement.removeEventListener("contextmenu", handleContextClick);
}; };
}, [gl, selectedAssets]); }, [controls, gl, selectedAssets]);
const handleAssetRename = () => { const handleAssetRename = () => {
setCanRender(false); setCanRender(false);
@@ -134,56 +155,56 @@ function ContextControls() {
} }
setContextAction("renameAsset"); setContextAction("renameAsset");
setIsRenameMode(true); setIsRenameMode(true);
} };
const handleAssetFocus = () => { const handleAssetFocus = () => {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
(controls as CameraControls).enabled = true; (controls as CameraControls).enabled = true;
} }
setContextAction("focusAsset"); setContextAction("focusAsset");
} };
const handleAssetMove = () => { const handleAssetMove = () => {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
(controls as CameraControls).enabled = true; (controls as CameraControls).enabled = true;
} }
setContextAction("moveAsset") setContextAction("moveAsset");
} };
const handleAssetRotate = () => { const handleAssetRotate = () => {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
(controls as CameraControls).enabled = true; (controls as CameraControls).enabled = true;
} }
setContextAction("rotateAsset") setContextAction("rotateAsset");
} };
const handleAssetCopy = () => { const handleAssetCopy = () => {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
(controls as CameraControls).enabled = true; (controls as CameraControls).enabled = true;
} }
setContextAction("copyAsset") setContextAction("copyAsset");
} };
const handleAssetPaste = () => { const handleAssetPaste = () => {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
(controls as CameraControls).enabled = true; (controls as CameraControls).enabled = true;
} }
setContextAction("pasteAsset") setContextAction("pasteAsset");
} };
const handleAssetDelete = () => { const handleAssetDelete = () => {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
(controls as CameraControls).enabled = true; (controls as CameraControls).enabled = true;
} }
setContextAction("deleteAsset") setContextAction("deleteAsset");
} };
const handleAssetDuplicate = () => { const handleAssetDuplicate = () => {
setCanRender(false); setCanRender(false);
if (controls) { if (controls) {
(controls as CameraControls).enabled = true; (controls as CameraControls).enabled = true;
} }
setContextAction("duplicateAsset") setContextAction("duplicateAsset");
} };
return ( return (
<> <>
@@ -191,10 +212,10 @@ function ContextControls() {
<ScreenSpace depth={1}> <ScreenSpace depth={1}>
<Html <Html
style={{ style={{
position: 'fixed', position: "fixed",
top: menuPosition.y, top: menuPosition.y,
left: menuPosition.x, left: menuPosition.x,
zIndex: 1000 zIndex: 1000,
}} }}
> >
<ContextMenu <ContextMenu

View File

@@ -1,5 +1,5 @@
import * as THREE from "three"; 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 { useThree, useFrame } from "@react-three/fiber";
import { useToolMode } from "../../../store/builder/store"; import { useToolMode } from "../../../store/builder/store";
import { Html, Line } from "@react-three/drei"; import { Html, Line } from "@react-three/drei";
@@ -10,7 +10,81 @@ const MeasurementTool = () => {
const [points, setPoints] = useState<THREE.Vector3[]>([]); const [points, setPoints] = useState<THREE.Vector3[]>([]);
const [linePoints, setLinePoints] = useState<THREE.Vector3[] | null>(null); const [linePoints, setLinePoints] = useState<THREE.Vector3[] | null>(null);
const [axisLock, setAxisLock] = useState<"x" | "y" | "z" | null>(null);
const groupRef = useRef<THREE.Group>(null); const groupRef = useRef<THREE.Group>(null);
const keysPressed = useRef<Set<string>>(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(() => { useEffect(() => {
const canvasElement = gl.domElement; const canvasElement = gl.domElement;
@@ -45,7 +119,13 @@ const MeasurementTool = () => {
); );
if (intersects.length > 0) { 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) { if (points.length < 2) {
setPoints([...points, intersectionPoint]); setPoints([...points, intersectionPoint]);
} else { } else {
@@ -84,7 +164,7 @@ const MeasurementTool = () => {
canvasElement.removeEventListener("contextmenu", onContextMenu); canvasElement.removeEventListener("contextmenu", onContextMenu);
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [toolMode, camera, raycaster, pointer, scene, points]); }, [toolMode, camera, raycaster, pointer, scene, points, axisLock]);
useFrame(() => { useFrame(() => {
if (points.length === 1) { if (points.length === 1) {
@@ -107,7 +187,10 @@ const MeasurementTool = () => {
); );
if (intersects.length > 0) { 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); updateMeasurement(points[0], tempEnd);
} }
} else if (points.length === 2) { } else if (points.length === 2) {
@@ -139,7 +222,7 @@ const MeasurementTool = () => {
{/* Main line */} {/* Main line */}
<Line <Line
points={linePoints} points={linePoints}
color="#b18ef1" color={getLineColor()}
lineWidth={2} // actual line width lineWidth={2} // actual line width
depthTest={false} depthTest={false}
depthWrite={false} depthWrite={false}

View File

@@ -1598,6 +1598,10 @@
.animations-lists { .animations-lists {
max-height: 210px; max-height: 210px;
overflow: auto; overflow: auto;
.no-animation{
padding: 6px 8px;
line-height: 20px;
}
.animations-list-wrapper { .animations-list-wrapper {
padding: 0 4px; padding: 0 4px;
.animations-list { .animations-list {