Merge remote-tracking branch 'origin/main-dev' into main-demo

This commit is contained in:
2025-08-20 18:12:17 +05:30
3 changed files with 145 additions and 43 deletions

View File

@@ -0,0 +1,74 @@
import * as THREE from "three";
export function handleAssetRotationSnap({
object,
pointerDeltaX,
keyEvent,
snapBaseRef,
prevRotationRef,
wasShiftHeldRef
}: {
object: THREE.Object3D;
pointerDeltaX: number;
keyEvent: string;
snapBaseRef: React.MutableRefObject<number | null>;
prevRotationRef: React.MutableRefObject<number | null>;
wasShiftHeldRef: React.MutableRefObject<boolean>;
}): number {
const SHIFT_SPEED = 0.5; // Fine rotation speed
const NORMAL_SPEED = 5; // Normal rotation speed
const CTRL_SNAP_DEG = 15; // 15 degrees
const CTRL_SHIFT_SNAP_DEG = 5; // 5 degrees
const isShiftHeld = keyEvent.includes("Shift");
const isCtrlHeld = keyEvent.includes("Ctrl");
const speedFactor = isShiftHeld ? SHIFT_SPEED : NORMAL_SPEED;
let deltaAngle = pointerDeltaX * speedFactor;
// Track if modifier changed
const modifierChanged = isShiftHeld !== wasShiftHeldRef.current;
if (modifierChanged) {
wasShiftHeldRef.current = isShiftHeld;
if (isCtrlHeld) snapBaseRef.current = null;
}
if (isCtrlHeld) {
const snapDeg = isShiftHeld ? CTRL_SHIFT_SNAP_DEG : CTRL_SNAP_DEG;
const snapRad = THREE.MathUtils.degToRad(snapDeg);
// Get current Y rotation from object's quaternion
const euler = new THREE.Euler().setFromQuaternion(object.quaternion, 'YXZ');
let currentAngle = euler.y;
// Initialize snap base on first frame
if (snapBaseRef.current === null) {
snapBaseRef.current = currentAngle;
prevRotationRef.current = currentAngle;
}
// Accumulate the total rotation from the base
const totalRotation = snapBaseRef.current + deltaAngle;
// Snap to nearest increment
const snappedAngle = Math.round(totalRotation / snapRad) * snapRad;
// Calculate the delta needed to reach the snapped angle from current rotation
let targetDelta = snappedAngle - currentAngle;
// Handle wrapping around 360 degrees
if (Math.abs(targetDelta) > Math.PI) {
targetDelta = targetDelta - Math.sign(targetDelta) * 2 * Math.PI;
}
// Update snap base for next frame
snapBaseRef.current = totalRotation;
return targetDelta;
} else {
// Reset snapping when Ctrl is not held
snapBaseRef.current = null;
prevRotationRef.current = null;
return deltaAngle;
}
}

View File

@@ -9,6 +9,8 @@ import { useProductContext } from "../../../../simulation/products/productContex
import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { handleAssetRotationSnap } from "./functions/handleAssetRotationSnap";
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
@@ -23,7 +25,6 @@ function RotateControls3D({
setDuplicatedObjects
}: any) {
const { camera, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
@@ -38,6 +39,7 @@ function RotateControls3D({
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
const [initialRotations, setInitialRotations] = useState<Record<string, THREE.Euler>>({});
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [isRotating, setIsRotating] = useState(false);
@@ -45,6 +47,9 @@ function RotateControls3D({
const rotationCenter = useRef<THREE.Vector3 | null>(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
const { contextAction, setContextAction } = useContextActionStore()
const snapBaseRef = useRef<number | null>(null);
const prevRotationRef = useRef<number | null>(null);
const wasShiftHeldRef = useRef(false);
const updateBackend = useCallback((
productName: string,
@@ -103,6 +108,8 @@ function RotateControls3D({
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return;
if (event.key.toLowerCase() === "r") {
@@ -110,6 +117,15 @@ function RotateControls3D({
rotateAssets();
}
}
if (keyCombination !== keyEvent) {
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
setKeyEvent(keyCombination);
} else {
setKeyEvent("");
}
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
resetToInitialRotations();
@@ -118,11 +134,24 @@ function RotateControls3D({
}
};
const onKeyUp = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "") {
setKeyEvent("");
} else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
setKeyEvent(keyCombination);
}
const currentPointer = new THREE.Vector2(pointer.x, pointer.y);
prevPointerPosition.current = currentPointer.clone();
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
canvasElement?.addEventListener("keyup", onKeyUp);
}
return () => {
@@ -130,68 +159,72 @@ function RotateControls3D({
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
canvasElement?.removeEventListener("keyup", onKeyUp);
};
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]);
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]);
const resetToInitialRotations = useCallback(() => {
rotatedObjects.forEach((obj: THREE.Object3D) => {
const uuid = obj.uuid;
if (obj.userData.modelUuid) {
const initialRotation = initialRotations[uuid];
const initialPosition = initialPositions[uuid];
setTimeout(() => {
rotatedObjects.forEach((obj: THREE.Object3D) => {
const uuid = obj.uuid;
if (obj.userData.modelUuid) {
const initialRotation = initialRotations[uuid];
const initialPosition = initialPositions[uuid];
if (initialRotation && initialPosition) {
const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,];
const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,];
if (initialRotation && initialPosition) {
const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,];
const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,];
updateAsset(obj.userData.modelUuid, {
rotation: rotationArray,
position: positionArray,
});
updateAsset(obj.userData.modelUuid, {
rotation: rotationArray,
position: positionArray,
});
obj.rotation.copy(initialRotation);
obj.position.copy(initialPosition);
obj.rotation.copy(initialRotation);
obj.position.copy(initialPosition);
}
}
}
});
});
}, 100)
}, [rotatedObjects, initialRotations, initialPositions, updateAsset]);
useFrame(() => {
if (!isRotating || rotatedObjects.length === 0) return;
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
const currentPointer = new THREE.Vector2(pointer.x, pointer.y);
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
if (point) {
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
prevPointerPosition.current = currentPointer.clone();
return;
}
if (point && prevPointerPosition.current && rotationCenter.current) {
if (prevPointerPosition.current && rotationCenter.current) {
const center = rotationCenter.current;
const currentAngle = Math.atan2(point.z - center.z, point.x - center.x);
const prevAngle = Math.atan2(
prevPointerPosition.current.y - center.z,
prevPointerPosition.current.x - center.x
);
const angleDelta = prevAngle - currentAngle;
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
const deltaX = currentPointer.x - prevPointerPosition.current.x;
rotatedObjects.forEach((obj: THREE.Object3D) => {
if (obj.userData.modelUuid) {
const angleDelta = handleAssetRotationSnap({
object: obj,
pointerDeltaX: deltaX,
keyEvent,
snapBaseRef,
prevRotationRef,
wasShiftHeldRef
});
const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
relativePosition.applyMatrix4(rotationMatrix);
obj.position.copy(center).add(relativePosition);
obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta);
const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta);
obj.quaternion.multiply(rotationQuat);
obj.rotation.setFromQuaternion(obj.quaternion);
}
});
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
prevPointerPosition.current = currentPointer.clone();
}
});
@@ -213,17 +246,13 @@ function RotateControls3D({
setInitialRotations(rotations);
setInitialPositions(positions);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
prevPointerPosition.current = new THREE.Vector2(pointer.x, pointer.y);
setRotatedObjects(selectedAssets);
setIsRotating(true);
}, [selectedAssets, camera, pointer, raycaster, plane]);
}, [selectedAssets, camera, pointer, raycaster]);
const placeRotatedAssets = useCallback(() => {
if (rotatedObjects.length === 0) return;

View File

@@ -23,7 +23,6 @@ export function useHumanEventManager() {
}, [isReset, isPlaying]);
const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => {
console.log('humanId: ', humanId);
const human = getHumanById(humanId);
const action = getActionByUuid(selectedProduct.productUuid, actionUuid);
if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker' && action.actionType !== 'operator') || !humanEventManagerRef.current) return;