added 2d point select delete and move

This commit is contained in:
2025-07-11 17:40:15 +05:30
parent 4baef5fa77
commit 96216783ef
4 changed files with 364 additions and 25 deletions

View File

@@ -0,0 +1,341 @@
import * as THREE from "three";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore";
import useModuleStore from "../../../../../store/useModuleStore";
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
function MoveControls2D({
movedObjects,
setMovedObjects,
pastedObjects,
setpastedObjects,
duplicatedObjects,
setDuplicatedObjects,
rotatedObjects,
setRotatedObjects,
}: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { selectedPoints, clearSelectedPoints } = useSelectedPoints();
const { socket } = useSocketStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
const { setPosition: setAislePosition, getAislesByPointId } = aisleStore();
const { setPosition: setWallPosition, getWallsByPointId } = wallStore();
const { setPosition: setFloorPosition, getFloorsByPointId } = floorStore();
const { setPosition: setZonePosition, getZonesByPointId } = zoneStore();
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
const [isMoving, setIsMoving] = useState(false);
useEffect(() => {
if (!camera || !scene || !toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeMovedAssets();
}
if (!isMoving && movedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
setMovedObjects([]);
resetToInitialPositions();
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
if (keyCombination === "G") {
if (selectedPoints.length > 0) {
moveAssets();
}
}
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
setMovedObjects([]);
resetToInitialPositions();
}
};
if (toggleView && selectedPoints.length > 0) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedPoints, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]);
useEffect(() => {
if (toolMode !== 'move' || !toggleView) {
if (movedObjects.length > 0) {
resetToInitialPositions();
}
clearSelection();
}
}, [activeModule, toolMode, toggleView, movedObjects]);
useFrame(() => {
if (!isMoving || movedObjects.length === 0 || !dragOffset) return;
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
const baseNewPosition = new THREE.Vector3().addVectors(hit, dragOffset);
movedObjects.forEach((movedPoint: THREE.Object3D) => {
if (movedPoint.userData.pointUuid) {
const point: Point = movedPoint.userData as Point;
const initialPosition = initialPositions[movedPoint.uuid];
if (initialPosition) {
const relativeOffset = new THREE.Vector3().subVectors(
initialPosition,
initialPositions[movedObjects[0].uuid]
);
const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset);
const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
if (point.pointType === 'Aisle') {
setAislePosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Wall') {
setWallPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Floor') {
setFloorPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Zone') {
setZonePosition(point.pointUuid, positionArray);
}
}
}
});
}
});
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
const moveAssets = useCallback(() => {
if (selectedPoints.length === 0) return;
const states: Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }> = {};
selectedPoints.forEach((point: THREE.Object3D) => {
states[point.uuid] = {
position: new THREE.Vector3().copy(point.position),
rotation: point.rotation ? new THREE.Euler().copy(point.rotation) : undefined
};
});
setInitialStates(states);
const positions: Record<string, THREE.Vector3> = {};
selectedPoints.forEach((point: THREE.Object3D) => { positions[point.uuid] = new THREE.Vector3().copy(point.position); });
setInitialPositions(positions);
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit && selectedPoints[0]) {
const offset = calculateDragOffset(selectedPoints[0], hit);
setDragOffset(offset);
}
setMovedObjects(selectedPoints);
setIsMoving(true);
}, [selectedPoints, camera, pointer, plane, raycaster, calculateDragOffset]);
const resetToInitialPositions = useCallback(() => {
setTimeout(() => {
movedObjects.forEach((movedPoint: THREE.Object3D) => {
if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) {
const point: Point = movedPoint.userData as Point;
const initialState = initialStates[movedPoint.uuid];
const positionArray: [number, number, number] = [
initialState.position.x,
initialState.position.y,
initialState.position.z
];
if (point.pointType === 'Aisle') {
setAislePosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Wall') {
setWallPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Floor') {
setFloorPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Zone') {
setZonePosition(point.pointUuid, positionArray);
}
}
});
}, 0)
}, [movedObjects, initialStates, setAislePosition, setWallPosition, setFloorPosition, setZonePosition]);
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;
movedObjects.forEach((movedObject: THREE.Object3D) => {
if (movedObject.userData.pointUuid) {
const point: Point = movedObject.userData as Point;
if (point.pointType === 'Aisle') {
const updatedAisles = getAislesByPointId(point.pointUuid);
if (updatedAisles.length > 0 && projectId) {
updatedAisles.forEach((updatedAisle) => {
// API
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
})
})
}
} else if (point.pointType === 'Wall') {
const updatedWalls = getWallsByPointId(point.pointUuid);
if (updatedWalls && updatedWalls.length > 0 && projectId) {
updatedWalls.forEach((updatedWall) => {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
});
}
} else if (point.pointType === 'Floor') {
const updatedFloors = getFloorsByPointId(point.pointUuid);
if (updatedFloors && updatedFloors.length > 0 && projectId) {
updatedFloors.forEach((updatedFloor) => {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
});
}
} else if (point.pointType === 'Zone') {
const updatedZones = getZonesByPointId(point.pointUuid);
if (updatedZones && updatedZones.length > 0 && projectId) {
updatedZones.forEach((updatedZone) => {
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
// SOCKET
const data = {
zoneData: updatedZone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
});
}
}
}
})
echo.success("Object moved!");
clearSelection();
};
const clearSelection = () => {
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
clearSelectedPoints();
};
return (
<>
</>
);
}
export default MoveControls2D;

View File

@@ -14,6 +14,7 @@ import { useProductContext } from "../../../../simulation/products/productContex
import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore";
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
import MoveControls2D from "./moveControls2D";
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
@@ -24,7 +25,6 @@ import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
const SelectionControls2D: React.FC = () => {
const { camera, controls, gl, scene, raycaster, pointer } = useThree();
const selectionGroup = useRef() as Types.RefGroup;
const { toggleView } = useToggleView();
const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints();
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
@@ -32,22 +32,19 @@ const SelectionControls2D: React.FC = () => {
const [copiedObjects, setCopiedObjects] = useState<THREE.Object3D[]>([]);
const [pastedObjects, setpastedObjects] = useState<THREE.Object3D[]>([]);
const [duplicatedObjects, setDuplicatedObjects] = useState<THREE.Object3D[]>([]);
const boundingBoxRef = useRef<THREE.Mesh>();
const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { projectId } = useParams();
const { hoveredLine, hoveredPoint } = useBuilderStore();
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore();
const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore();
const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore();
const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore();
const { removePoint: removeAislePoint } = aisleStore();
const { removePoint: removeWallPoint } = wallStore();
const { removePoint: removeFloorPoint } = floorStore();
const { removePoint: removeZonePoint } = zoneStore();
const isDragging = useRef(false);
const isLeftMouseDown = useRef(false);
@@ -178,12 +175,6 @@ const SelectionControls2D: React.FC = () => {
}
}, [activeModule, toolMode, toggleView]);
useFrame(() => {
if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
selectionGroup.current.position.set(0, 0, 0);
}
});
const selectAssets = useCallback(() => {
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
if (controls) (controls as any).enabled = true;
@@ -226,9 +217,6 @@ const SelectionControls2D: React.FC = () => {
}, [selectionBox, pointer, controls, selectedPoints, setSelectedPoints]);
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
clearSelectedPoints();
@@ -237,7 +225,7 @@ const SelectionControls2D: React.FC = () => {
const deleteSelection = () => {
if (selectedPoints.length > 0 && duplicatedObjects.length === 0) {
selectedPoints.map((selectedPoint) => {
selectedPoints.forEach((selectedPoint) => {
if (selectedPoint.userData.pointUuid) {
const point: Point = selectedPoint.userData as Point;
if (point.pointType === 'Aisle') {
@@ -394,9 +382,7 @@ const SelectionControls2D: React.FC = () => {
return (
<>
<group name="SelectionGroup-3D">
<group ref={selectionGroup} name="selectionAssetGroup-3D" />
</group>
<MoveControls2D movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} />
</>
);

View File

@@ -254,7 +254,7 @@ const SelectionControls3D: React.FC = () => {
});
if (Objects.size === 0) {
clearSelection();
// clearSelection();
return;
}

View File

@@ -103,9 +103,21 @@ export function Arrows({ connections }: { readonly connections: ConnectionLine[]
return (
<group
key={key}
onPointerOver={() => setHoveredArrowTrigger(trigger.triggerUuid)}
onPointerOut={() => setHoveredArrowTrigger(null)}
onClick={() => { removeConnection(trigger) }}
onPointerOver={() => {
if (toolMode === '3D-Delete') {
setHoveredArrowTrigger(trigger.triggerUuid)
}
}}
onPointerOut={() => {
if (toolMode === '3D-Delete') {
setHoveredArrowTrigger(null)
}
}}
onClick={() => {
if (toolMode === '3D-Delete') {
removeConnection(trigger)
}
}}
>
<mesh
geometry={shaftGeometry}