feat: measurement tool axis lock added, style update, no animation ui update
This commit is contained in:
@@ -103,9 +103,16 @@ const AssetProperties: React.FC = () => {
|
||||
</section>
|
||||
<div className="header">Animations</div>
|
||||
<section className="animations-lists">
|
||||
{assets.map((asset) => {
|
||||
{assets.map((asset, i) => {
|
||||
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) => (
|
||||
<div key={index} className="animations-list-wrapper">
|
||||
|
||||
@@ -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 && (
|
||||
<ScreenSpace depth={1} >
|
||||
<Html
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: menuPosition.y,
|
||||
left: menuPosition.x,
|
||||
zIndex: 1000
|
||||
}}
|
||||
>
|
||||
<ContextMenu
|
||||
visibility={visibility}
|
||||
onRename={() => 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()}
|
||||
/>
|
||||
</Html>
|
||||
</ScreenSpace>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
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 && (
|
||||
<ScreenSpace depth={1}>
|
||||
<Html
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: menuPosition.y,
|
||||
left: menuPosition.x,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<ContextMenu
|
||||
visibility={visibility}
|
||||
onRename={() => 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()}
|
||||
/>
|
||||
</Html>
|
||||
</ScreenSpace>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContextControls;
|
||||
|
||||
@@ -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<THREE.Vector3[]>([]);
|
||||
const [linePoints, setLinePoints] = useState<THREE.Vector3[] | null>(null);
|
||||
const [axisLock, setAxisLock] = useState<"x" | "y" | "z" | null>(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(() => {
|
||||
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 */}
|
||||
<Line
|
||||
points={linePoints}
|
||||
color="#b18ef1"
|
||||
color={getLineColor()}
|
||||
lineWidth={2} // actual line width
|
||||
depthTest={false}
|
||||
depthWrite={false}
|
||||
|
||||
@@ -1598,6 +1598,10 @@
|
||||
.animations-lists {
|
||||
max-height: 210px;
|
||||
overflow: auto;
|
||||
.no-animation{
|
||||
padding: 6px 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.animations-list-wrapper {
|
||||
padding: 0 4px;
|
||||
.animations-list {
|
||||
|
||||
Reference in New Issue
Block a user