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

552 lines
27 KiB
TypeScript

import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
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 { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
const CopyPasteControls3D = ({
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();
const { assetStore, eventStore } = useSceneContext();
const { addEvent } = eventStore();
const { projectId } = useParams();
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
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 };
}, []);
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isPointerMoving = false;
const onPointerDown = (event: PointerEvent) => {
isPointerMoving = false;
if (event.button === 0) mouseButtonsDown.current.left = true;
if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerMove = () => {
isPointerMoving = true;
};
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) {
event.preventDefault();
addPastedObjects();
}
if (!isPointerMoving && pastedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
pastedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
};
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);
});
}
};
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 model = scene.getObjectByProperty("uuid", pastedObject.userData.modelUuid);
if (!model) return;
const newPos = new THREE.Vector3().addVectors(hit, relativePositions[index]);
model.position.set(newPos.x, 0, newPos.z);
});
}
});
const copySelection = () => {
if (selectedAssets.length > 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = SkeletonUtils.clone(asset);
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();
return clone;
});
setpastedObjects(newPastedObjects);
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
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);
});
}
}
};
const addPastedObjects = () => {
if (pastedObjects.length === 0) return;
pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
if (pastedAsset) {
const assetUuid = pastedAsset.userData.modelUuid;
const asset = getAssetById(assetUuid);
const model = scene.getObjectByProperty("uuid", pastedAsset.userData.modelUuid);
if (!asset || !model) return;
const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
modelUuid: pastedAsset.userData.modelUuid,
modelName: pastedAsset.userData.modelName,
assetId: pastedAsset.userData.assetId,
position: [position.x, position.y, position.z],
rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] },
isLocked: false,
isVisible: true,
};
let updatedEventData = null;
if (pastedAsset.userData.eventData) {
updatedEventData = JSON.parse(JSON.stringify(pastedAsset.userData.eventData));
updatedEventData.modelUuid = newFloorItem.modelUuid;
const eventData: any = {
type: pastedAsset.userData.eventData.type,
subType: pastedAsset.userData.eventData.subType,
};
if (pastedAsset.userData.eventData.type === "Conveyor") {
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',
subType: pastedAsset.userData.eventData.subType || '',
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") {
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",
subType: pastedAsset.userData.eventData.subType || '',
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,
paths: {
initPickup: [],
pickupDrop: [],
dropPickup: [],
},
triggers: []
}
}
};
addEvent(vehicleEvent);
eventData.point = {
uuid: vehicleEvent.point.uuid,
position: vehicleEvent.point.position,
rotation: vehicleEvent.point.rotation
};
} else if (pastedAsset.userData.eventData.type === "ArmBot") {
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",
subType: pastedAsset.userData.eventData.subType || '',
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") {
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",
subType: pastedAsset.userData.eventData.subType || '',
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") {
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",
subType: pastedAsset.userData.eventData.subType || '',
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",
subType: pastedAsset.userData.eventData.subType || '',
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,
assemblyCount: 1,
loadCount: 1,
processTime: 10,
triggers: []
}
]
}
}
addEvent(humanEvent);
eventData.point = {
uuid: humanEvent.point.uuid,
position: humanEvent.point.position,
rotation: humanEvent.point.rotation
};
} else if (pastedAsset.userData.eventData.type === "Crane") {
const craneEvent: CraneEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "crane",
subType: pastedAsset.userData.eventData.subType || '',
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: "pickAndDrop",
maxPickUpCount: 1,
triggers: []
}
]
}
}
addEvent(craneEvent);
eventData.point = {
uuid: craneEvent.point.uuid,
position: craneEvent.point.position,
rotation: craneEvent.point.rotation
};
}
newFloorItem.eventData = eventData;
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: [position.x, 0, position.z],
rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
eventData: eventData,
versionId: selectedVersion?.versionId || '',
userId,
projectId
};
//REST
// await setAssetsApi(data);
//SOCKET
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: [position.x, 0, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
eventData: data.eventData
};
updateAsset(asset.modelUuid, asset);
} else {
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: [position.x, 0, position.z],
rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
versionId: selectedVersion?.versionId || '',
projectId,
userId
};
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: [position.x, 0, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
};
updateAsset(asset.modelUuid, asset);
}
}
});
echo.success("Object added!");
clearSelection();
};
const clearSelection = () => {
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
setIsPasting(false);
setCenterOffset(null);
};
return null;
};
export default CopyPasteControls3D;