552 lines
27 KiB
TypeScript
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; |