import { useEffect, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import * as THREE from "three"; import { useSubModuleStore } from "../../../../store/useModuleStore"; import { useSelectedAction, useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useEventsStore } from "../../../../store/simulation/useEventsStore"; import { handleAddEventToProduct } from "../points/functions/handleAddEventToProduct"; import { QuadraticBezierLine } from "@react-three/drei"; import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; import { usePlayButtonStore } from "../../../../store/usePlayButtonStore"; import { ArrowOnQuadraticBezier, Arrows } from "../arrows/arrows"; import { useProductContext } from "../../products/productContext"; import { useParams } from "react-router-dom"; import { useToolMode } from "../../../../store/builder/store"; interface ConnectionLine { id: string; startPointUuid: string; endPointUuid: string; trigger: TriggerSchema; } function TriggerConnector() { const { gl, raycaster, scene, pointer, camera } = useThree(); const { subModule } = useSubModuleStore(); const { selectedProductStore } = useProductContext(); const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, removeTrigger, addEvent, getEventByModelUuid, getPointUuidByActionUuid, getProductById } = useProductStore(); const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); const { selectedProduct } = selectedProductStore(); const [hoveredLineKey, setHoveredLineKey] = useState(null); const groupRefs = useRef>({}); const [helperlineColor, setHelperLineColor] = useState("red"); const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null); const { toolMode } = useToolMode(); const { isPlaying } = usePlayButtonStore(); const { selectedAction } = useSelectedAction(); const { projectId } = useParams(); const [firstSelectedPoint, setFirstSelectedPoint] = useState<{ productUuid: string; modelUuid: string; pointUuid: string; actionUuid?: string; } | null>(null); const [connections, setConnections] = useState([]); const updateBackend = ( productName: string, productUuid: string, projectId: string, eventData: EventsSchema ) => { upsertProductOrEventApi({ productName: productName, productUuid: productUuid, projectId: projectId, eventDatas: eventData }) } useEffect(() => { const newConnections: ConnectionLine[] = []; const product = getProductById(selectedProduct.productUuid); if (!product || products.length === 0) return; product.eventDatas.forEach(event => { // Handle Conveyor points if (event.type === "transfer" && 'points' in event) { event.points.forEach(point => { if (point.action?.triggers) { point.action.triggers.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, trigger }); } }); } }); } // Handle Vehicle point else if (event.type === "vehicle" && 'point' in event) { const point = event.point; if (point.action?.triggers) { point.action.triggers.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, trigger }); } }); } } // Handle Robotic Arm points else if (event.type === "roboticArm" && 'point' in event) { const point = event.point; point.actions?.forEach(action => { action.triggers?.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, trigger }); } }); }); } // Handle Machine point else if (event.type === "machine" && 'point' in event) { const point = event.point; if (point.action?.triggers) { point.action.triggers.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, trigger }); } }); } } // Handle StorageUnit point else if (event.type === "storageUnit" && 'point' in event) { const point = event.point; if (point.action?.triggers) { point.action.triggers.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, trigger }); } }); } } }); setConnections(newConnections); }, [products, selectedProduct.productUuid]); useEffect(() => { const canvasElement = gl.domElement; let drag = false; let isRightMouseDown = false; const onMouseDown = (evt: MouseEvent) => { if (selectedAsset) { clearSelectedAsset(); } if (evt.button === 2) { isRightMouseDown = true; drag = false; } }; const onMouseUp = (evt: MouseEvent) => { if (evt.button === 2) { isRightMouseDown = false; } } const onMouseMove = () => { if (isRightMouseDown) { drag = true; } }; const handleRightClick = (evt: MouseEvent) => { if (drag) return; evt.preventDefault(); const intersects = raycaster .intersectObjects(scene.children, true) .filter( (intersect) => intersect.object.name === ('Event-Sphere') ); if (intersects.length === 0) { setFirstSelectedPoint(null); return; }; const currentObject = intersects[0].object; if (!currentObject || currentObject.name !== 'Event-Sphere') { setFirstSelectedPoint(null); return; }; const modelUuid = currentObject.userData.modelUuid; const pointUuid = currentObject.userData.pointUuid; if (firstSelectedPoint && firstSelectedPoint.pointUuid === pointUuid) { setFirstSelectedPoint(null); return; } if (selectedProduct && getIsEventInProduct(selectedProduct.productUuid, modelUuid)) { const point = getPointByUuid( selectedProduct.productUuid, modelUuid, pointUuid ); const event = getEventByModelUuid(selectedProduct.productUuid, modelUuid); const clickedPointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, selectedAction.actionId || ''); if (!point || !event) { setFirstSelectedPoint(null); return; }; let actionUuid: string | undefined; if ('action' in point && point.action) { actionUuid = point.action.actionUuid; } else if ('actions' in point && point.actions.length === 1) { actionUuid = point.actions[0].actionUuid; } if (!firstSelectedPoint) { if (point.uuid !== clickedPointUuid) return; setFirstSelectedPoint({ productUuid: selectedProduct.productUuid, modelUuid, pointUuid, actionUuid: selectedAction.actionId || '' }); } else { const trigger: TriggerSchema = { triggerUuid: THREE.MathUtils.generateUUID(), triggerName: `Trigger ${firstSelectedPoint.pointUuid.slice(0, 4)} → ${pointUuid.slice(0, 4)}`, triggerType: "onComplete", delay: 0, triggeredAsset: { triggeredModel: { modelName: event.modelName || 'Unknown', modelUuid: modelUuid }, triggeredPoint: { pointName: 'Point', pointUuid: point.uuid }, triggeredAction: actionUuid ? { actionName: getActionByUuid(selectedProduct.productUuid, actionUuid)?.actionName || 'Action', actionUuid: actionUuid } : null } }; if (firstSelectedPoint.actionUuid) { const event = addTrigger(selectedProduct.productUuid, firstSelectedPoint.actionUuid, trigger); if (event) { updateBackend( selectedProduct.productName, selectedProduct.productUuid, projectId || '', event ); } } setFirstSelectedPoint(null); } } else if (!getIsEventInProduct(selectedProduct.productUuid, modelUuid) && firstSelectedPoint) { handleAddEventToProduct({ event: useEventsStore.getState().getEventByModelUuid(modelUuid), addEvent, selectedProduct, projectId: projectId || '' }) const point = getPointByUuid( selectedProduct.productUuid, modelUuid, pointUuid ); const event = getEventByModelUuid(selectedProduct.productUuid, modelUuid); if (!point || !event) { setFirstSelectedPoint(null); return; }; let actionUuid: string | undefined; if ('action' in point && point.action) { actionUuid = point.action.actionUuid; } else if ('actions' in point && point.actions.length === 1) { actionUuid = point.actions[0].actionUuid; } const trigger: TriggerSchema = { triggerUuid: THREE.MathUtils.generateUUID(), triggerName: `Trigger ${firstSelectedPoint.pointUuid.slice(0, 4)} → ${pointUuid.slice(0, 4)}`, triggerType: "onComplete", delay: 0, triggeredAsset: { triggeredModel: { modelName: event.modelName || 'Unknown', modelUuid: modelUuid }, triggeredPoint: { pointName: 'Point', pointUuid: point.uuid }, triggeredAction: actionUuid ? { actionName: getActionByUuid(selectedProduct.productUuid, actionUuid)?.actionName || 'Action', actionUuid: actionUuid } : null } }; if (firstSelectedPoint.actionUuid) { const event = addTrigger(selectedProduct.productUuid, firstSelectedPoint.actionUuid, trigger); if (event) { updateBackend( selectedProduct.productName, selectedProduct.productUuid, projectId || '', event ); } } setFirstSelectedPoint(null); } else if (firstSelectedPoint) { setFirstSelectedPoint(null); } }; if (subModule === 'mechanics' && toolMode === 'cursor' && selectedAction.actionId && selectedAction.actionName) { canvasElement.addEventListener("mousedown", onMouseDown); canvasElement.addEventListener("mouseup", onMouseUp); canvasElement.addEventListener("mousemove", onMouseMove); canvasElement.addEventListener('contextmenu', handleRightClick); } else { setFirstSelectedPoint(null); } return () => { canvasElement.removeEventListener("mousedown", onMouseDown); canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener('contextmenu', handleRightClick); }; }, [gl, subModule, selectedProduct, firstSelectedPoint, toolMode, selectedAction]); useFrame(() => { if (firstSelectedPoint) { raycaster.setFromCamera(pointer, camera); const intersects = raycaster.intersectObjects(scene.children, true).filter( (intersect) => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("zonePlane") && !intersect.object.name.includes("SelectionGroup") && !intersect.object.name.includes("selectionAssetGroup") && !intersect.object.name.includes("SelectionGroupBoundingBoxLine") && !intersect.object.name.includes("SelectionGroupBoundingBox") && !intersect.object.name.includes("SelectionGroupBoundingLine") && intersect.object.type !== "GridHelper" && !intersect.object.name.includes("ArrowWithTube") && !intersect.object.parent?.name.includes("Zone") && intersect.object.type !== "Line2" ); let point: THREE.Vector3 | null = null; if (intersects.length > 0) { point = intersects[0].point; if (point.y < 0.05) { point = new THREE.Vector3(point.x, 0.05, point.z); } } const sphereIntersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.name === ('Event-Sphere')); if (sphereIntersects.length > 0 && sphereIntersects[0].object.uuid === firstSelectedPoint.pointUuid) { setHelperLineColor('red'); setCurrentLine(null); return; } const startPoint = getWorldPositionFromScene(firstSelectedPoint.pointUuid); if (point && startPoint) { if (sphereIntersects.length > 0) { point = sphereIntersects[0].object.getWorldPosition(new THREE.Vector3()); } const distance = startPoint.distanceTo(point); const heightFactor = Math.max(0.5, distance * 0.2); const midPoint = new THREE.Vector3( (startPoint.x + point.x) / 2, Math.max(startPoint.y, point.y) + heightFactor, (startPoint.z + point.z) / 2 ); const endPoint = point; setCurrentLine({ start: startPoint, mid: midPoint, end: endPoint, }); setHelperLineColor(sphereIntersects.length > 0 ? "#6cf542" : "red"); } else { setCurrentLine(null); } } else { setCurrentLine(null); } }) const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => { const pointObj = scene.getObjectByProperty("uuid", pointUuid); if (!pointObj) return null; const worldPosition = new THREE.Vector3(); pointObj.getWorldPosition(worldPosition); return worldPosition; }; const removeConnection = (connection: ConnectionLine) => { if (connection.trigger.triggerUuid) { const event = removeTrigger(selectedProduct.productUuid, connection.trigger.triggerUuid); if (event) { updateBackend( selectedProduct.productName, selectedProduct.productUuid, projectId || '', event ); } } }; return ( {connections.map((connection) => { const startPoint = getWorldPositionFromScene(connection.startPointUuid); const endPoint = getWorldPositionFromScene(connection.endPointUuid); if (!startPoint || !endPoint) return null; const distance = startPoint.distanceTo(endPoint); const heightFactor = Math.max(0.5, distance * 0.2); const midPoint = new THREE.Vector3( (startPoint.x + endPoint.x) / 2, Math.max(startPoint.y, endPoint.y) + heightFactor, (startPoint.z + endPoint.z) / 2 ); return ( (groupRefs.current[connection.id] = el!)} start={startPoint.toArray()} end={endPoint.toArray()} mid={midPoint.toArray()} color={toolMode === '3D-Delete' && hoveredLineKey === connection.id ? "red" : "#42a5f5"} lineWidth={4} dashed={toolMode === '3D-Delete' && hoveredLineKey === connection.id ? false : true} dashSize={0.75} dashScale={20} onPointerOver={() => setHoveredLineKey(connection.id)} onPointerOut={() => setHoveredLineKey(null)} onClick={() => { if (toolMode === '3D-Delete') { setHoveredLineKey(null); setCurrentLine(null); removeConnection(connection); } }} userData={connection.trigger} /> ); })} {currentLine && ( <> )} ); } export default TriggerConnector;