2025-04-21 06:23:42 +00:00
|
|
|
import * as THREE from "three";
|
|
|
|
import { useEffect, useMemo } from "react";
|
|
|
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
|
|
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
|
|
|
|
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
|
|
|
import * as Types from "../../../../types/world/worldTypes";
|
|
|
|
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
|
|
|
|
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
|
2025-04-30 10:28:23 +00:00
|
|
|
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
2025-04-21 06:23:42 +00:00
|
|
|
|
|
|
|
const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: 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 { floorItems, setFloorItems } = useFloorItems();
|
|
|
|
const { socket } = useSocketStore();
|
2025-04-30 10:28:23 +00:00
|
|
|
const { addEvent } = useEventsStore();
|
2025-04-21 06:23:42 +00:00
|
|
|
|
|
|
|
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 && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
|
|
|
event.preventDefault();
|
|
|
|
addDuplicatedAssets();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
|
|
const keyCombination = detectModifierKeys(event);
|
|
|
|
|
|
|
|
if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
|
|
|
duplicateSelection();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
|
|
|
}, [camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects]);
|
|
|
|
|
|
|
|
useFrame(() => {
|
|
|
|
if (duplicatedObjects.length > 0) {
|
|
|
|
const intersectionPoint = new THREE.Vector3();
|
|
|
|
raycaster.setFromCamera(pointer, camera);
|
|
|
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
|
|
|
if (point) {
|
|
|
|
const position = new THREE.Vector3();
|
|
|
|
if (boundingBoxRef.current) {
|
|
|
|
boundingBoxRef.current?.getWorldPosition(position)
|
|
|
|
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
|
|
|
|
} else {
|
|
|
|
const box = new THREE.Box3();
|
|
|
|
duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
|
|
|
|
const center = new THREE.Vector3();
|
|
|
|
box.getCenter(center);
|
|
|
|
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const duplicateSelection = () => {
|
|
|
|
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
|
|
|
const newClones = selectedAssets.map((asset: any) => {
|
|
|
|
const clone = asset.clone();
|
|
|
|
clone.position.copy(asset.position);
|
|
|
|
return clone;
|
|
|
|
});
|
|
|
|
|
|
|
|
selectionGroup.current.add(...newClones);
|
|
|
|
setDuplicatedObjects(newClones);
|
|
|
|
|
|
|
|
const intersectionPoint = new THREE.Vector3();
|
|
|
|
raycaster.setFromCamera(pointer, camera);
|
|
|
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
|
|
|
|
|
|
|
if (point) {
|
|
|
|
const position = new THREE.Vector3();
|
|
|
|
boundingBoxRef.current?.getWorldPosition(position)
|
|
|
|
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const addDuplicatedAssets = () => {
|
|
|
|
if (duplicatedObjects.length === 0) return;
|
|
|
|
duplicatedObjects.forEach(async (obj: THREE.Object3D) => {
|
|
|
|
const worldPosition = new THREE.Vector3();
|
|
|
|
obj.getWorldPosition(worldPosition);
|
|
|
|
obj.position.copy(worldPosition);
|
|
|
|
|
|
|
|
if (itemsGroupRef.current) {
|
|
|
|
|
2025-04-30 10:28:23 +00:00
|
|
|
const newFloorItem: Types.FloorItemType = {
|
|
|
|
modelUuid: obj.uuid,
|
|
|
|
modelName: obj.userData.name,
|
|
|
|
modelfileID: obj.userData.modelId,
|
|
|
|
position: [worldPosition.x, worldPosition.y, worldPosition.z],
|
|
|
|
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
|
|
|
|
isLocked: false,
|
|
|
|
isVisible: true,
|
|
|
|
};
|
|
|
|
|
2025-04-29 13:45:17 +00:00
|
|
|
let updatedEventData = null;
|
|
|
|
if (obj.userData.eventData) {
|
|
|
|
updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData));
|
2025-04-30 10:28:23 +00:00
|
|
|
updatedEventData.modelUuid = newFloorItem.modelUuid;
|
|
|
|
|
|
|
|
const eventData: any = {
|
|
|
|
type: obj.userData.eventData.type,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (obj.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',
|
|
|
|
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(),
|
2025-05-03 06:55:10 +00:00
|
|
|
actionName: `Action 1`,
|
2025-04-30 10:28:23 +00:00
|
|
|
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
|
|
|
|
}));
|
2025-04-29 13:45:17 +00:00
|
|
|
|
2025-04-30 10:28:23 +00:00
|
|
|
} else if (obj.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",
|
|
|
|
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,
|
2025-05-10 06:54:29 +00:00
|
|
|
loadCapacity: 1,
|
2025-04-30 10:28:23 +00:00
|
|
|
steeringAngle: 0,
|
|
|
|
pickUpPoint: null,
|
|
|
|
unLoadPoint: null,
|
|
|
|
triggers: []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
addEvent(vehicleEvent);
|
|
|
|
eventData.point = {
|
|
|
|
uuid: vehicleEvent.point.uuid,
|
|
|
|
position: vehicleEvent.point.position,
|
|
|
|
rotation: vehicleEvent.point.rotation
|
|
|
|
};
|
2025-04-29 13:45:17 +00:00
|
|
|
|
2025-04-30 10:28:23 +00:00
|
|
|
} else if (obj.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",
|
|
|
|
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: []
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2025-04-29 13:45:17 +00:00
|
|
|
};
|
2025-04-30 10:28:23 +00:00
|
|
|
addEvent(roboticArmEvent);
|
|
|
|
eventData.point = {
|
|
|
|
uuid: roboticArmEvent.point.uuid,
|
|
|
|
position: roboticArmEvent.point.position,
|
|
|
|
rotation: roboticArmEvent.point.rotation
|
2025-04-29 13:45:17 +00:00
|
|
|
};
|
2025-04-30 10:28:23 +00:00
|
|
|
|
|
|
|
} else if (obj.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",
|
|
|
|
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
|
2025-04-29 13:45:17 +00:00
|
|
|
};
|
2025-05-09 17:20:47 +00:00
|
|
|
} else if (obj.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",
|
|
|
|
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
|
|
|
|
};
|
2025-04-29 13:45:17 +00:00
|
|
|
}
|
|
|
|
|
2025-04-30 10:28:23 +00:00
|
|
|
newFloorItem.eventData = eventData;
|
2025-04-21 06:23:42 +00:00
|
|
|
|
2025-04-30 10:28:23 +00:00
|
|
|
setFloorItems((prevItems: Types.FloorItems) => {
|
|
|
|
const updatedItems = [...(prevItems || []), newFloorItem];
|
|
|
|
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
|
|
|
return updatedItems;
|
|
|
|
});
|
|
|
|
|
|
|
|
const email = localStorage.getItem("email");
|
|
|
|
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
|
|
|
|
|
|
|
//REST
|
|
|
|
|
|
|
|
// await setFloorItemApi(
|
|
|
|
// organization,
|
|
|
|
// obj.uuid,
|
|
|
|
// obj.userData.name,
|
|
|
|
// [worldPosition.x, worldPosition.y, worldPosition.z],
|
|
|
|
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
|
|
|
|
// obj.userData.modelId,
|
|
|
|
// false,
|
|
|
|
// true,
|
|
|
|
// );
|
|
|
|
|
|
|
|
//SOCKET
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
organization,
|
|
|
|
modelUuid: newFloorItem.modelUuid,
|
|
|
|
modelName: newFloorItem.modelName,
|
|
|
|
modelfileID: newFloorItem.modelfileID,
|
|
|
|
position: newFloorItem.position,
|
|
|
|
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
|
|
|
|
isLocked: false,
|
|
|
|
isVisible: true,
|
|
|
|
socketId: socket.id,
|
|
|
|
eventData: eventData,
|
|
|
|
};
|
2025-04-29 13:45:17 +00:00
|
|
|
|
2025-04-30 10:28:23 +00:00
|
|
|
socket.emit("v2:model-asset:add", data);
|
|
|
|
|
|
|
|
obj.userData = {
|
|
|
|
name: newFloorItem.modelName,
|
|
|
|
modelId: newFloorItem.modelfileID,
|
|
|
|
modelUuid: newFloorItem.modelUuid,
|
2025-05-09 14:12:08 +00:00
|
|
|
eventData: JSON.parse(JSON.stringify(eventData))
|
2025-04-30 10:28:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
itemsGroupRef.current.add(obj);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
setFloorItems((prevItems: Types.FloorItems) => {
|
|
|
|
const updatedItems = [...(prevItems || []), newFloorItem];
|
|
|
|
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
|
|
|
return updatedItems;
|
|
|
|
});
|
|
|
|
|
|
|
|
const email = localStorage.getItem("email");
|
|
|
|
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
|
|
|
|
|
|
|
//REST
|
|
|
|
|
|
|
|
// await setFloorItemApi(
|
|
|
|
// organization,
|
|
|
|
// obj.uuid,
|
|
|
|
// obj.userData.name,
|
|
|
|
// [worldPosition.x, worldPosition.y, worldPosition.z],
|
|
|
|
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
|
|
|
|
// obj.userData.modelId,
|
|
|
|
// false,
|
|
|
|
// true,
|
|
|
|
// );
|
|
|
|
|
|
|
|
//SOCKET
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
organization,
|
|
|
|
modelUuid: newFloorItem.modelUuid,
|
|
|
|
modelName: newFloorItem.modelName,
|
|
|
|
modelfileID: newFloorItem.modelfileID,
|
|
|
|
position: newFloorItem.position,
|
|
|
|
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
|
|
|
|
isLocked: false,
|
|
|
|
isVisible: true,
|
|
|
|
socketId: socket.id,
|
|
|
|
};
|
|
|
|
|
|
|
|
socket.emit("v2:model-asset:add", data);
|
|
|
|
|
|
|
|
obj.userData = {
|
|
|
|
name: newFloorItem.modelName,
|
|
|
|
modelId: newFloorItem.modelfileID,
|
|
|
|
modelUuid: newFloorItem.modelUuid,
|
|
|
|
};
|
|
|
|
|
|
|
|
itemsGroupRef.current.add(obj);
|
|
|
|
}
|
2025-04-21 06:23:42 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-13 11:51:52 +00:00
|
|
|
echo.success("Object duplicated!");
|
2025-04-21 06:23:42 +00:00
|
|
|
clearSelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
const clearSelection = () => {
|
|
|
|
selectionGroup.current.children = [];
|
|
|
|
selectionGroup.current.position.set(0, 0, 0);
|
|
|
|
selectionGroup.current.rotation.set(0, 0, 0);
|
|
|
|
setMovedObjects([]);
|
|
|
|
setpastedObjects([]);
|
|
|
|
setDuplicatedObjects([]);
|
|
|
|
setRotatedObjects([]);
|
|
|
|
setSelectedAssets([]);
|
|
|
|
}
|
|
|
|
|
2025-04-29 13:45:17 +00:00
|
|
|
return null;
|
2025-04-21 06:23:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export default DuplicationControls;
|