Files
Dwinzo_Demo/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx

514 lines
24 KiB
TypeScript
Raw Normal View History

2025-06-10 15:28:23 +05:30
import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2025-06-10 15:28:23 +05:30
import { useFrame, useThree } from "@react-three/fiber";
2025-07-07 10:11:37 +05:30
import { SkeletonUtils } from "three-stdlib";
2025-07-11 16:27:55 +05:30
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
2025-06-10 15:28:23 +05:30
import { useParams } from "react-router-dom";
2025-07-11 16:27:55 +05:30
import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
2025-06-10 15:28:23 +05:30
2025-07-18 15:25:22 +05:30
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
2025-07-11 16:27:55 +05:30
const CopyPasteControls3D = ({
2025-06-10 15:28:23 +05:30
copiedObjects,
setCopiedObjects,
pastedObjects,
setpastedObjects,
setDuplicatedObjects,
movedObjects,
setMovedObjects,
rotatedObjects,
setRotatedObjects,
}: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { socket } = useSocketStore();
2025-06-23 09:37:53 +05:30
const { assetStore, eventStore } = useSceneContext();
const { addEvent } = eventStore();
2025-06-10 15:28:23 +05:30
const { projectId } = useParams();
const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore();
2025-06-23 09:37:53 +05:30
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
2025-06-10 15:28:23 +05:30
const [isPasting, setIsPasting] = useState(false);
const [relativePositions, setRelativePositions] = useState<THREE.Vector3[]>([]);
const [centerOffset, setCenterOffset] = useState<THREE.Vector3 | null>(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
left: false,
right: false,
});
const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => {
if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] };
const box = new THREE.Box3();
objects.forEach(obj => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
const relatives = objects.map(obj => {
const relativePos = new THREE.Vector3().subVectors(obj.position, center);
return relativePos;
});
return { center, relatives };
}, []);
2025-06-10 15:28:23 +05:30
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isPointerMoving = false;
2025-06-10 15:28:23 +05:30
const onPointerDown = (event: PointerEvent) => {
isPointerMoving = false;
if (event.button === 0) mouseButtonsDown.current.left = true;
if (event.button === 2) mouseButtonsDown.current.right = true;
2025-06-10 15:28:23 +05:30
};
const onPointerMove = () => {
isPointerMoving = true;
2025-06-10 15:28:23 +05:30
};
const onPointerUp = (event: PointerEvent) => {
if (event.button === 0) mouseButtonsDown.current.left = false;
if (event.button === 2) mouseButtonsDown.current.right = false;
if (!isPointerMoving && pastedObjects.length > 0 && event.button === 0) {
2025-06-10 15:28:23 +05:30
event.preventDefault();
addPastedObjects();
}
if (!isPointerMoving && pastedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
pastedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
2025-06-10 15:28:23 +05:30
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+C" && movedObjects.length === 0 && rotatedObjects.length === 0) {
copySelection();
}
if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
if (keyCombination === "ESCAPE" && pastedObjects.length > 0) {
event.preventDefault();
clearSelection();
pastedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
2025-06-10 15:28:23 +05:30
};
if (!toggleView) {
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);
};
}, [assets, camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, rotatedObjects]);
useFrame(() => {
if (!isPasting || pastedObjects.length === 0) return;
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) return;
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit && centerOffset) {
pastedObjects.forEach((pastedObject: THREE.Object3D, index: number) => {
const newPos = new THREE.Vector3().addVectors(hit, relativePositions[index]);
setPosition(pastedObject.userData.modelUuid, [newPos.x, 0, newPos.z]);
});
2025-06-10 15:28:23 +05:30
}
});
const copySelection = () => {
if (selectedAssets.length > 0) {
const newClones = selectedAssets.map((asset: any) => {
2025-07-07 10:11:37 +05:30
const clone = SkeletonUtils.clone(asset);
2025-06-10 15:28:23 +05:30
clone.position.copy(asset.position);
return clone;
});
setCopiedObjects(newClones);
echo.info("Objects copied!");
}
};
const pasteCopiedObjects = () => {
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
const { center, relatives } = calculateRelativePositions(copiedObjects);
setRelativePositions(relatives);
const newPastedObjects = copiedObjects.map((obj: any) => {
const clone = SkeletonUtils.clone(obj);
clone.userData.modelUuid = THREE.MathUtils.generateUUID();
2025-06-10 15:28:23 +05:30
return clone;
});
setpastedObjects(newPastedObjects);
2025-06-10 15:28:23 +05:30
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
2025-06-10 15:28:23 +05:30
if (hit) {
const offset = new THREE.Vector3().subVectors(center, hit);
setCenterOffset(offset);
setIsPasting(true);
newPastedObjects.forEach((obj: THREE.Object3D, index: number) => {
const initialPos = new THREE.Vector3().addVectors(hit, relatives[index]);
const asset: Asset = {
modelUuid: obj.userData.modelUuid,
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: initialPos.toArray(),
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
isLocked: false,
isVisible: true,
isCollidable: false,
opacity: 0.5,
};
addAsset(asset);
});
2025-06-10 15:28:23 +05:30
}
}
};
const addPastedObjects = () => {
if (pastedObjects.length === 0) return;
pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
if (pastedAsset) {
const assetUuid = pastedAsset.userData.modelUuid;
const asset = getAssetById(assetUuid);
if (!asset) return;
2025-06-10 15:28:23 +05:30
const newFloorItem: Types.FloorItemType = {
modelUuid: pastedAsset.userData.modelUuid,
modelName: pastedAsset.userData.modelName,
assetId: pastedAsset.userData.assetId,
position: asset.position,
rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] },
2025-06-10 15:28:23 +05:30
isLocked: false,
isVisible: true,
};
let updatedEventData = null;
if (pastedAsset.userData.eventData) {
updatedEventData = JSON.parse(JSON.stringify(pastedAsset.userData.eventData));
2025-06-10 15:28:23 +05:30
updatedEventData.modelUuid = newFloorItem.modelUuid;
const eventData: any = {
type: pastedAsset.userData.eventData.type,
2025-06-10 15:28:23 +05:30
};
if (pastedAsset.userData.eventData.type === "Conveyor") {
2025-06-10 15:28:23 +05:30
const ConveyorEvent: ConveyorEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: 'transfer',
speed: 1,
points: updatedEventData.points.map((point: any, index: number) => ({
uuid: THREE.MathUtils.generateUUID(),
position: [point.position[0], point.position[1], point.position[2]],
rotation: [point.rotation[0], point.rotation[1], point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: `Action 1`,
actionType: 'default',
material: 'Default Material',
delay: 0,
spawnInterval: 5,
spawnCount: 1,
triggers: []
}
}))
};
addEvent(ConveyorEvent);
eventData.points = ConveyorEvent.points.map(point => ({
uuid: point.uuid,
position: point.position,
rotation: point.rotation
}));
} else if (pastedAsset.userData.eventData.type === "Vehicle") {
2025-06-10 15:28:23 +05:30
const vehicleEvent: VehicleEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "vehicle",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "travel",
unLoadDuration: 5,
loadCapacity: 1,
steeringAngle: 0,
pickUpPoint: null,
unLoadPoint: null,
2025-07-17 12:54:47 +05:30
paths: {
initPickup: [],
pickupDrop: [],
dropPickup: [],
},
2025-06-10 15:28:23 +05:30
triggers: []
}
}
};
addEvent(vehicleEvent);
eventData.point = {
uuid: vehicleEvent.point.uuid,
position: vehicleEvent.point.position,
rotation: vehicleEvent.point.rotation
};
} else if (pastedAsset.userData.eventData.type === "ArmBot") {
2025-06-10 15:28:23 +05:30
const roboticArmEvent: RoboticArmEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "roboticArm",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
actions: [
{
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "pickAndPlace",
process: {
startPoint: null,
endPoint: null
},
triggers: []
}
]
}
};
addEvent(roboticArmEvent);
eventData.point = {
uuid: roboticArmEvent.point.uuid,
position: roboticArmEvent.point.position,
rotation: roboticArmEvent.point.rotation
};
} else if (pastedAsset.userData.eventData.type === "StaticMachine") {
2025-06-10 15:28:23 +05:30
const machineEvent: MachineEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "machine",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "process",
processTime: 10,
swapMaterial: "Default Material",
triggers: []
}
}
};
addEvent(machineEvent);
eventData.point = {
uuid: machineEvent.point.uuid,
position: machineEvent.point.position,
rotation: machineEvent.point.rotation
};
} else if (pastedAsset.userData.eventData.type === "Storage") {
2025-06-10 15:28:23 +05:30
const storageEvent: StorageEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "storageUnit",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "store",
storageCapacity: 10,
triggers: []
}
}
}
addEvent(storageEvent);
eventData.point = {
uuid: storageEvent.point.uuid,
position: storageEvent.point.position,
rotation: storageEvent.point.rotation
};
} else if (pastedAsset.userData.eventData.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "human",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
actions: [
{
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
loadCount: 1,
processTime: 10,
triggers: []
}
]
}
}
addEvent(humanEvent);
eventData.point = {
uuid: humanEvent.point.uuid,
position: humanEvent.point.position,
rotation: humanEvent.point.rotation
};
2025-06-10 15:28:23 +05:30
}
newFloorItem.eventData = eventData;
2025-06-10 15:28:23 +05:30
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
2025-06-12 09:31:51 +05:30
assetId: newFloorItem.assetId,
2025-06-10 15:28:23 +05:30
position: newFloorItem.position,
rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
2025-06-10 15:28:23 +05:30
isLocked: false,
isVisible: true,
socketId: socket.id,
eventData: eventData,
2025-06-23 09:37:53 +05:30
versionId: selectedVersion?.versionId || '',
userId,
2025-06-10 15:28:23 +05:30
projectId
};
2025-07-18 15:25:22 +05:30
//REST
// await setAssetsApi(data);
//SOCKET
2025-06-10 15:28:23 +05:30
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
2025-06-12 09:31:51 +05:30
assetId: data.assetId,
2025-06-10 15:28:23 +05:30
position: data.position,
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
eventData: data.eventData
};
2025-06-10 15:28:23 +05:30
updateAsset(asset.modelUuid, asset);
2025-06-10 15:28:23 +05:30
} else {
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
2025-06-12 09:31:51 +05:30
assetId: newFloorItem.assetId,
2025-06-10 15:28:23 +05:30
position: newFloorItem.position,
rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
2025-06-10 15:28:23 +05:30
isLocked: false,
isVisible: true,
2025-06-23 09:37:53 +05:30
socketId: socket.id,
versionId: selectedVersion?.versionId || '',
projectId,
2025-06-10 15:28:23 +05:30
userId
};
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
2025-06-12 09:31:51 +05:30
assetId: data.assetId,
2025-06-10 15:28:23 +05:30
position: data.position,
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
};
2025-06-10 15:28:23 +05:30
updateAsset(asset.modelUuid, asset);
2025-06-10 15:28:23 +05:30
}
}
});
echo.success("Object added!");
clearSelection();
};
const clearSelection = () => {
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
setIsPasting(false);
setCenterOffset(null);
};
2025-06-10 15:28:23 +05:30
return null;
};
2025-07-11 16:27:55 +05:30
export default CopyPasteControls3D;