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 = () => {
- {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 */}