diff --git a/app/.env b/app/.env index 5740621..c50d174 100644 --- a/app/.env +++ b/app/.env @@ -11,7 +11,7 @@ REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:5000 REACT_APP_SERVER_MARKETPLACE_URL=185.100.212.76:50011 # Base URL for the asset library server, used for asset library images and model blob id. -REACT_APP_SERVER_ASSET_LIBRARY_URL=192.168.0.111:3501 +REACT_APP_SERVER_ASSET_LIBRARY_URL=185.100.212.76:50011 # base url for IoT socket server REACT_APP_IOT_SOCKET_SERVER_URL =185.100.212.76:5010 diff --git a/app/src/assets/gltf-glb/box.glb b/app/src/assets/gltf-glb/box.glb new file mode 100644 index 0000000..92ef9d8 Binary files /dev/null and b/app/src/assets/gltf-glb/box.glb differ diff --git a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx b/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx index 4ec9fdd..a719023 100644 --- a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx +++ b/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx @@ -30,14 +30,13 @@ interface ProductionCapacityProps { id: string; type: string; position: [number, number, number]; + rotation: [number, number, number]; onContextMenu?: (event: React.MouseEvent) => void; // onPointerDown:any } -const ProductionCapacity: React.FC = ({ id, type, position, onContextMenu }) => { - +const ProductionCapacity: React.FC = ({ id, type, position, rotation, onContextMenu }) => { const { selectedChartId, setSelectedChartId } = useWidgetStore(); - const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const [measurements, setmeasurements] = useState({}); const [duration, setDuration] = useState("1h") @@ -158,10 +157,10 @@ const ProductionCapacity: React.FC = ({ id, type, posit setDuration(response.data.Data.duration) setName(response.data.widgetName) } else { - console.log("Unexpected response:", response); + } } catch (error) { - console.error("There was an error!", error); + } } } @@ -177,15 +176,42 @@ const ProductionCapacity: React.FC = ({ id, type, posit } , [chartMeasurements, chartDuration, widgetName]) + useEffect(() => { + + + }, [rotation]) + const rotationDegrees = { + x: (rotation[0] * 180) / Math.PI, + y: (rotation[1] * 180) / Math.PI, + z: (rotation[2] * 180) / Math.PI, + }; + + const transformStyle = { + transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`, + }; + return ( - + sprite + zIndexRange={[1,0]} + // center + // distanceFactor={10} // Adjusted for visual balance + style={{ + transform: transformStyle.transform, + transformStyle: 'preserve-3d', + transition: 'transform 0.1s ease-out' + }}>
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} + style={{ + width: '300px', // Original width + height: '300px', // Original height + transform: transformStyle.transform, + transformStyle: 'preserve-3d' + }} >
Production Capacity
diff --git a/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx b/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx index 96fc305..8e6c707 100644 --- a/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx +++ b/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx @@ -43,9 +43,10 @@ interface ReturnOfInvestmentProps { id: string; type: string; position: [number, number, number]; + rotation: [number, number, number]; onContextMenu?: (event: React.MouseEvent) => void; } -const ReturnOfInvestment: React.FC = ({ id, type, position, onContextMenu }) => { +const ReturnOfInvestment: React.FC = ({ id, type, position, rotation, onContextMenu }) => { const { selectedChartId, setSelectedChartId } = useWidgetStore(); const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); @@ -203,13 +204,29 @@ const ReturnOfInvestment: React.FC = ({ id, type, posit } } , [chartMeasurements, chartDuration, widgetName]) + const rotationDegrees = { + x: (rotation[0] * 180) / Math.PI, + y: (rotation[1] * 180) / Math.PI, + z: (rotation[2] * 180) / Math.PI, + }; + + const transformStyle = { + transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`, + }; return ( + sprite + style={{ + transform: transformStyle.transform, + transformStyle: 'preserve-3d', + transition: 'transform 0.1s ease-out' + + }} + >
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} diff --git a/app/src/components/layout/3D-cards/cards/StateWorking.tsx b/app/src/components/layout/3D-cards/cards/StateWorking.tsx index efd4e7d..9adf77f 100644 --- a/app/src/components/layout/3D-cards/cards/StateWorking.tsx +++ b/app/src/components/layout/3D-cards/cards/StateWorking.tsx @@ -10,9 +10,10 @@ interface StateWorkingProps { id: string; type: string; position: [number, number, number]; + rotation: [number, number, number]; onContextMenu?: (event: React.MouseEvent) => void; } -const StateWorking: React.FC = ({ id, type, position, onContextMenu }) => { +const StateWorking: React.FC = ({ id, type, position, rotation, onContextMenu }) => { const { selectedChartId, setSelectedChartId } = useWidgetStore(); const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const [measurements, setmeasurements] = useState({}); @@ -45,7 +46,7 @@ const StateWorking: React.FC = ({ id, type, position, onConte socket.on("connect", startStream); socket.on("lastOutput", (response) => { const responseData = response; - console.log("responceeeeeeeeeee", response); + setDatas(responseData); }); @@ -75,7 +76,7 @@ const StateWorking: React.FC = ({ id, type, position, onConte } } - console.log("dataaaaa", datas); + useEffect(() => { @@ -89,13 +90,28 @@ const StateWorking: React.FC = ({ id, type, position, onConte } , [chartMeasurements, chartDuration, widgetName]) + const rotationDegrees = { + x: (rotation[0] * 180) / Math.PI, + y: (rotation[1] * 180) / Math.PI, + z: (rotation[2] * 180) / Math.PI, + }; + const transformStyle = { + transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`, + }; return ( + sprite + style={{ + transform: transformStyle.transform, + transformStyle: 'preserve-3d', + transition: 'transform 0.1s ease-out' + + }} + >
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} diff --git a/app/src/components/layout/3D-cards/cards/Throughput.tsx b/app/src/components/layout/3D-cards/cards/Throughput.tsx index 5d6fab9..de3109b 100644 --- a/app/src/components/layout/3D-cards/cards/Throughput.tsx +++ b/app/src/components/layout/3D-cards/cards/Throughput.tsx @@ -45,10 +45,11 @@ interface ThroughputProps { id: string; type: string; position: [number, number, number]; + rotation: [number, number, number]; onContextMenu?: (event: React.MouseEvent) => void; } -const Throughput: React.FC = ({ id, type, position, onContextMenu }) => { +const Throughput: React.FC = ({ id, type, position, rotation, onContextMenu }) => { const { selectedChartId, setSelectedChartId } = useWidgetStore(); const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); @@ -183,13 +184,29 @@ const Throughput: React.FC = ({ id, type, position, onContextMe } } , [chartMeasurements, chartDuration, widgetName]) + const rotationDegrees = { + x: (rotation[0] * 180) / Math.PI, + y: (rotation[1] * 180) / Math.PI, + z: (rotation[2] * 180) / Math.PI, + }; + + const transformStyle = { + transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`, + }; return ( + sprite + style={{ + transform: transformStyle.transform, + transformStyle: 'preserve-3d', + transition: 'transform 0.1s ease-out' + + }} + >
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} diff --git a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx index 913bdd1..fd64db9 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx @@ -13,7 +13,8 @@ import { useFloorItems, useSelectedActionSphere, useSelectedPath, - useSimulationPaths, + useSimulationStates, + useSocketStore, } from "../../../../store/store"; import * as THREE from "three"; import * as Types from "../../../../types/world/worldTypes"; @@ -24,44 +25,53 @@ import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAss const ConveyorMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); const { selectedPath, setSelectedPath } = useSelectedPath(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { floorItems, setFloorItems } = useFloorItems(); + const { socket } = useSocketStore(); const actionsContainerRef = useRef(null); const triggersContainerRef = useRef(null); const selectedPoint = useMemo(() => { if (!selectedActionSphere) return null; - return simulationPaths + return simulationStates .filter( (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" ) .flatMap((path) => path.points) - .find((point) => point.uuid === selectedActionSphere.point.uuid); - }, [selectedActionSphere, simulationPaths]); + .find((point) => point.uuid === selectedActionSphere.points.uuid); + }, [selectedActionSphere, simulationStates]); const updateBackend = async (updatedPath: Types.ConveyorEventsSchema | undefined) => { if (!updatedPath) return; - // const email = localStorage.getItem("email"); - // const organization = email ? email.split("@")[1].split(".")[0] : ""; - // console.log('updatedPath: ', updatedPath); - // const a = await setEventApi( + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + // await setEventApi( // organization, // updatedPath.modeluuid, - // updatedPath.points + // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } // ); - // console.log('a: ', a); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + } + + socket.emit('v2:model-asset:updateEventData', data); + } const handleAddAction = () => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => { + const updatedPaths = simulationStates.map((path) => { if (path.type === "Conveyor") { return { ...path, points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.point.uuid) { + if (point.uuid === selectedActionSphere.points.uuid) { const actionIndex = point.actions.length; const newAction = { uuid: THREE.MathUtils.generateUUID(), @@ -86,23 +96,23 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); }; const handleDeleteAction = (uuid: string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.filter( @@ -119,23 +129,23 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); }; const handleActionSelect = (uuid: string, actionType: string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -167,12 +177,12 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); // Update the selected item to reflect changes if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { @@ -181,7 +191,7 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" ) .flatMap((path) => path.points) - .find((p) => p.uuid === selectedActionSphere.point.uuid) + .find((p) => p.uuid === selectedActionSphere.points.uuid) ?.actions.find((a) => a.uuid === uuid); if (updatedAction) { @@ -197,12 +207,12 @@ const ConveyorMechanics: React.FC = () => { const handleMaterialSelect = (uuid: string, material: string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -222,12 +232,12 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); // Update selected item if it's the current action if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { @@ -244,12 +254,12 @@ const ConveyorMechanics: React.FC = () => { const handleDelayChange = (uuid: string, delay: number | string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -266,12 +276,12 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); }; const handleSpawnIntervalChange = ( @@ -280,12 +290,12 @@ const ConveyorMechanics: React.FC = () => { ) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => @@ -304,18 +314,18 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); }; const handleSpeedChange = (speed: number | string) => { if (!selectedPath) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path ); @@ -323,24 +333,24 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); }; const handleAddTrigger = () => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.point.uuid) { + if (point.uuid === selectedActionSphere.points.uuid) { const triggerIndex = point.triggers.length; const newTrigger = { uuid: THREE.MathUtils.generateUUID(), @@ -362,23 +372,23 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); }; const handleDeleteTrigger = (uuid: string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.filter( @@ -395,23 +405,23 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); }; const handleTriggerSelect = (uuid: string, triggerType: string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.map((trigger) => @@ -430,12 +440,12 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); // Ensure the selectedItem is updated immediately const updatedTrigger = updatedPaths @@ -451,12 +461,12 @@ const ConveyorMechanics: React.FC = () => { // Update the toggle handlers to immediately update the selected item const handleActionToggle = (uuid: string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, actions: point.actions.map((action) => ({ @@ -474,12 +484,12 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); // Immediately update the selected item if it's the one being toggled if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { @@ -497,12 +507,12 @@ const ConveyorMechanics: React.FC = () => { const handleTriggerToggle = (uuid: string) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.map((trigger) => ({ @@ -520,12 +530,12 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); // Immediately update the selected item if it's the one being toggled if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { @@ -542,12 +552,12 @@ const ConveyorMechanics: React.FC = () => { const handleTriggerBufferTimeChange = (uuid: string, bufferTime: number) => { if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.type === "Conveyor" ? { ...path, points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid + point.uuid === selectedActionSphere.points.uuid ? { ...point, triggers: point.triggers.map((trigger) => @@ -566,12 +576,12 @@ const ConveyorMechanics: React.FC = () => { (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" && path.points.some( - (point) => point.uuid === selectedActionSphere.point.uuid + (point) => point.uuid === selectedActionSphere.points.uuid ) ); updateBackend(updatedPath); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); // Immediately update selectedItem if it's the currently selected trigger if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { diff --git a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx index bf0b112..4db77fe 100644 --- a/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/VehicleMechanics.tsx @@ -1,56 +1,79 @@ import React, { useRef, useMemo } from "react"; import { InfoIcon } from "../../../icons/ExportCommonIcons"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; -import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationPaths } from "../../../../store/store"; +import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store"; import * as Types from '../../../../types/world/worldTypes'; import PositionInput from "../customInput/PositionInputs"; +import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; const VehicleMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); const { editingPoint, setEditingPoint } = useEditingPoint(); const { previewPosition, setPreviewPosition } = usePreviewPosition(); + const { socket } = useSocketStore(); const propertiesContainerRef = useRef(null); const { selectedPoint, connectedPointUuids } = useMemo(() => { - if (!selectedActionSphere?.point?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; + if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; - const vehiclePaths = simulationPaths.filter( + const vehiclePaths = simulationStates.filter( (path): path is Types.VehicleEventsSchema => path.type === "Vehicle" ); - const point = vehiclePaths.find( - (path) => path.point.uuid === selectedActionSphere.point.uuid - )?.point; + const points = vehiclePaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; - if (!point) return { selectedPoint: null, connectedPointUuids: [] }; + if (!points) return { selectedPoint: null, connectedPointUuids: [] }; const connectedUuids: string[] = []; - if (point.connections?.targets) { - point.connections.targets.forEach(target => { + if (points.connections?.targets) { + points.connections.targets.forEach(target => { connectedUuids.push(target.pointUUID); }); } return { - selectedPoint: point, + selectedPoint: points, connectedPointUuids: connectedUuids }; - }, [selectedActionSphere, simulationPaths]); + }, [selectedActionSphere, simulationStates]); - const handleActionUpdate = React.useCallback((updatedAction: Partial) => { - if (!selectedActionSphere?.point?.uuid) return; + const updateBackend = async (updatedPath: Types.VehicleEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; - const updatedPaths = simulationPaths.map((path) => { - if (path.type === "Vehicle" && path.point.uuid === selectedActionSphere.point.uuid) { + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Vehicle", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Vehicle", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } + + const handleActionUpdate = React.useCallback((updatedAction: Partial) => { + if (!selectedActionSphere?.points?.uuid) return; + + const updatedPaths = simulationStates.map((path) => { + if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, actions: { - ...path.point.actions, + ...path.points.actions, ...updatedAction } } @@ -59,14 +82,15 @@ const VehicleMechanics: React.FC = () => { return path; }); - setSimulationPaths(updatedPaths); - }, [selectedActionSphere?.point?.uuid, simulationPaths, setSimulationPaths]); + const updatedPath = updatedPaths.find( + (path): path is Types.VehicleEventsSchema => + path.type === "Vehicle" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); - const handleStartPointChange = React.useCallback((position: { x: number, y: number }) => { - }, [handleActionUpdate]); - - const handleEndPointChange = React.useCallback((position: { x: number, y: number }) => { - }, [handleActionUpdate]); + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); const handleHitCountChange = React.useCallback((hitCount: number) => { handleActionUpdate({ hitCount }); @@ -77,14 +101,14 @@ const VehicleMechanics: React.FC = () => { }, [handleActionUpdate]); const handleSpeedChange = React.useCallback((speed: number) => { - if (!selectedActionSphere?.point?.uuid) return; + if (!selectedActionSphere?.points?.uuid) return; - const updatedPaths = simulationPaths.map((path) => { - if (path.type === "Vehicle" && path.point.uuid === selectedActionSphere.point.uuid) { + const updatedPaths = simulationStates.map((path) => { + if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, speed: speed } }; @@ -92,8 +116,15 @@ const VehicleMechanics: React.FC = () => { return path; }); - setSimulationPaths(updatedPaths); - }, [selectedActionSphere?.point?.uuid, simulationPaths, setSimulationPaths]); + const updatedPath = updatedPaths.find( + (path): path is Types.VehicleEventsSchema => + path.type === "Vehicle" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); const handleStartEyeDropClick = () => { setEditingPoint('start'); diff --git a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx index adae69e..4ffea3b 100644 --- a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx @@ -33,7 +33,7 @@ const GlobalProperties: React.FC = () => { const { setPlaneValue, setGridValue, planeValue, gridValue } = useTileDistance(); useEffect(() => { - // console.log(gridValue, planeValue, "values"); + }, [gridValue, planeValue]); const { socket } = useSocketStore(); const { limitDistance, setLimitDistance } = useLimitDistance(); diff --git a/app/src/components/layout/sidebarRight/visualization/IotInputCards/FleetEfficiencyInputComponent.tsx b/app/src/components/layout/sidebarRight/visualization/IotInputCards/FleetEfficiencyInputComponent.tsx index cd60d24..55ae422 100644 --- a/app/src/components/layout/sidebarRight/visualization/IotInputCards/FleetEfficiencyInputComponent.tsx +++ b/app/src/components/layout/sidebarRight/visualization/IotInputCards/FleetEfficiencyInputComponent.tsx @@ -45,7 +45,7 @@ const FleetEfficiencyInputComponent = (props: Props) => { try { const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${selectedChartId.id}/${organization}`); if (response.status === 200) { - console.log(response.data); + setSelections(response.data.Data.measurements) setDuration(response.data.Data.duration) diff --git a/app/src/components/layout/sidebarRight/visualization/IotInputCards/FlotingWidgetInput.tsx b/app/src/components/layout/sidebarRight/visualization/IotInputCards/FlotingWidgetInput.tsx index e389802..1164a84 100644 --- a/app/src/components/layout/sidebarRight/visualization/IotInputCards/FlotingWidgetInput.tsx +++ b/app/src/components/layout/sidebarRight/visualization/IotInputCards/FlotingWidgetInput.tsx @@ -45,7 +45,7 @@ const FlotingWidgetInput = (props: Props) => { try { const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${selectedChartId.id}/${organization}`); if (response.status === 200) { - console.log(response.data); + setSelections(response.data.Data.measurements) setDuration(response.data.Data.duration) diff --git a/app/src/components/layout/sidebarRight/visualization/IotInputCards/WarehouseThroughputInputComponent.tsx b/app/src/components/layout/sidebarRight/visualization/IotInputCards/WarehouseThroughputInputComponent.tsx index c35211e..d3ed377 100644 --- a/app/src/components/layout/sidebarRight/visualization/IotInputCards/WarehouseThroughputInputComponent.tsx +++ b/app/src/components/layout/sidebarRight/visualization/IotInputCards/WarehouseThroughputInputComponent.tsx @@ -45,7 +45,7 @@ const WarehouseThroughputInputComponent = (props: Props) => { try { const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${selectedChartId.id}/${organization}`); if (response.status === 200) { - console.log(response.data); + setSelections(response.data.Data.measurements) setDuration(response.data.Data.duration) diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx index e78b4dd..4b54c88 100644 --- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -42,16 +42,19 @@ const Design = () => { const [elementColor, setElementColor] = useState("#6f42c1"); const [showColorPicker, setShowColorPicker] = useState(false); const [chartElements, setChartElements] = useState([]); - const [selectedElementToStyle, setSelectedElementToStyle] = useState(null); + const [selectedElementToStyle, setSelectedElementToStyle] = useState< + string | null + >(null); const [nameInput, setNameInput] = useState(""); const chartRef = useRef(null); - const { selectedChartId, setSelectedChartId, widgets, setWidgets } = useWidgetStore(); + const { selectedChartId, setSelectedChartId, widgets, setWidgets } = + useWidgetStore(); // Initialize name input and extract elements when selectedChartId changes useEffect(() => { setNameInput(selectedChartId?.header || selectedChartId?.title || ""); - + if (!chartRef.current) return; const timer = setTimeout(() => { @@ -65,13 +68,16 @@ const Design = () => { }) .map((el, index) => { const tagName = el.tagName.toLowerCase(); - const className = typeof el.className === "string" ? el.className : ""; + const className = + typeof el.className === "string" ? el.className : ""; const textContent = el.textContent?.trim() || ""; let selector = tagName; if (className && typeof className === "string") { - const classList = className.split(/\s+/).filter((c) => c.length > 0); + const classList = className + .split(/\s+/) + .filter((c) => c.length > 0); if (classList.length > 0) { selector += "." + classList.join("."); } @@ -126,7 +132,13 @@ const Design = () => { useEffect(() => { applyStyles(); - }, [selectedFont, selectedSize, selectedWeight, elementColor, selectedElementToStyle]); + }, [ + selectedFont, + selectedSize, + selectedWeight, + elementColor, + selectedElementToStyle, + ]); const handleUpdateWidget = (updatedProperties: Partial) => { if (!selectedChartId) return; @@ -138,7 +150,9 @@ const Design = () => { setSelectedChartId(updatedChartId); const updatedWidgets = widgets.map((widget) => - widget.id === selectedChartId.id ? { ...widget, ...updatedProperties } : widget + widget.id === selectedChartId.id + ? { ...widget, ...updatedProperties } + : widget ); setWidgets(updatedWidgets); }; @@ -146,7 +160,7 @@ const Design = () => { const handleNameChange = (e: React.ChangeEvent) => { const newName = e.target.value; setNameInput(newName); - + if (selectedChartId?.title) { handleUpdateWidget({ title: newName }); } else if (selectedChartId?.header) { @@ -155,12 +169,12 @@ const Design = () => { }; const defaultChartData = { - labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + labels: ["January", "February", "March", "April", "May", "June", "July"], datasets: [ { data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: elementColor, - borderColor: "#ffffff", + backgroundColor: "#6f42c1", + borderColor: "#b392f0", borderWidth: 1, }, ], @@ -311,4 +325,4 @@ const Design = () => { ); }; -export default Design; \ No newline at end of file +export default Design; diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 02069d8..3de44bf 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -438,7 +438,6 @@ const Tools: React.FC = () => { }`} onClick={() => { setIsPlaying(!isPlaying); - setActiveTool("play"); }} > diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index 7b667c8..41ac1f2 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -96,16 +96,16 @@ export const DraggableWidget = ({ let deleteWidget = { zoneId: selectedZone.zoneId, widgetID: widget.id, - organization: organization - } - console.log('deleteWidget: ', deleteWidget); + organization: organization, + }; + console.log("deleteWidget: ", deleteWidget); if (visualizationSocket) { - visualizationSocket.emit("v2:viz-widget:delete", deleteWidget) + visualizationSocket.emit("v2:viz-widget:delete", deleteWidget); } const updatedWidgets = selectedZone.widgets.filter( (w: Widget) => w.id !== widget.id ); - console.log('updatedWidgets: ', updatedWidgets); + console.log("updatedWidgets: ", updatedWidgets); setSelectedZone((prevZone: any) => ({ ...prevZone, widgets: updatedWidgets, @@ -168,10 +168,10 @@ export const DraggableWidget = ({ let duplicateWidget = { organization: organization, zoneId: selectedZone.zoneId, - widget: duplicatedWidget - } + widget: duplicatedWidget, + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-widget:add", duplicateWidget) + visualizationSocket.emit("v2:viz-widget:add", duplicateWidget); } setSelectedZone((prevZone: any) => ({ ...prevZone, @@ -239,29 +239,95 @@ export const DraggableWidget = ({ onReorder(fromIndex, toIndex); // Call the reorder function passed as a prop } }; - console.log("widget.type", widget.type); // useClickOutside(chartWidget, () => { // setSelectedChartId(null); // }); const { isPlaying } = usePlayButtonStore(); - console.log('isPanelHidden: ', isPanelHidden); + const [canvasDimensions, setCanvasDimensions] = useState({ + width: 0, + height: 0, + }); + // Track canvas dimensions + useEffect(() => { + const canvas = document.getElementById("real-time-vis-canvas"); + if (!canvas) return; + + const updateCanvasDimensions = () => { + const rect = canvas.getBoundingClientRect(); + setCanvasDimensions({ + width: rect.width, + height: rect.height, + }); + }; + + // Initial measurement + updateCanvasDimensions(); + + // Set up ResizeObserver to track changes + const resizeObserver = new ResizeObserver(updateCanvasDimensions); + resizeObserver.observe(canvas); + + return () => { + resizeObserver.unobserve(canvas); + }; + }, []); + + useEffect(() => { + const canvas = document.getElementById("real-time-vis-canvas"); + if (!canvas) return; + + const updateCanvasDimensions = () => { + const rect = canvas.getBoundingClientRect(); + setCanvasDimensions({ + width: rect.width, + height: rect.height, + }); + }; + + // Initial measurement + updateCanvasDimensions(); + + // Set up ResizeObserver to track changes + const resizeObserver = new ResizeObserver(updateCanvasDimensions); + resizeObserver.observe(canvas); + + return () => { + resizeObserver.unobserve(canvas); + }; + }, []); + + console.log("selectedChartId: ", widget); return ( <> +
setSelectedChartId(widget)} @@ -275,8 +341,9 @@ export const DraggableWidget = ({ {openKebabId === widget.id && (
@@ -350,3 +417,5 @@ export const DraggableWidget = ({ ); }; + +// in style if widget .panel is top or bottom set width if left or right set height diff --git a/app/src/components/ui/componets/Dropped3dWidget.tsx b/app/src/components/ui/componets/Dropped3dWidget.tsx index f43cce5..d870195 100644 --- a/app/src/components/ui/componets/Dropped3dWidget.tsx +++ b/app/src/components/ui/componets/Dropped3dWidget.tsx @@ -1,5 +1,5 @@ import { useThree } from "@react-three/fiber"; -import React, { useState, useEffect, useRef } from "react"; +import React, { useEffect, useRef } from "react"; import { useAsset3dWidget, useSocketStore, useWidgetSubOption } from "../../../store/store"; import useModuleStore from "../../../store/useModuleStore"; import { ThreeState } from "../../../types/world/worldTypes"; @@ -13,9 +13,19 @@ import { generateUniqueId } from "../../../functions/generateUniqueId"; import { adding3dWidgets } from "../../../services/realTimeVisulization/zoneData/add3dWidget"; import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData"; import { use3DWidget } from "../../../store/useDroppedObjectsStore"; -import { useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore } from "../../../store/useZone3DWidgetStore"; +import { useEditWidgetOptionsStore, useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore } from "../../../store/useZone3DWidgetStore"; import { useWidgetStore } from "../../../store/useWidgetStore"; import EditWidgetOption from "../menu/EditWidgetOption"; +import { delete3dWidgetApi } from "../../../services/realTimeVisulization/zoneData/delete3dWidget"; +import { update3dWidget, update3dWidgetRotation } from "../../../services/realTimeVisulization/zoneData/update3dWidget"; +type WidgetData = { + id: string; + type: string; + position: [number, number, number]; + rotation?: [number, number, number]; + tempPosition?: [number, number, number]; +}; + export default function Dropped3dWidgets() { const { widgetSelect } = useAsset3dWidget(); @@ -23,22 +33,23 @@ export default function Dropped3dWidgets() { const { raycaster, gl, scene, mouse, camera }: ThreeState = useThree(); const { widgetSubOption } = useWidgetSubOption(); const { selectedZone } = useSelectedZoneStore(); - const { top, setTop } = useTopData() - const { left, setLeft } = useLeftData() - const { rightSelect, setRightSelect } = useRightSelected() - - // ✅ Use Zustand Store instead of useState - const { zoneWidgetData, setZoneWidgetData, addWidget, updateWidgetPosition } = useZoneWidgetStore(); + const { top, setTop } = useTopData(); + const { left, setLeft } = useLeftData(); + const { rightSelect, setRightSelect } = useRightSelected(); + const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore() + const { zoneWidgetData, setZoneWidgetData, addWidget, updateWidgetPosition, updateWidgetRotation } = useZoneWidgetStore(); const { setWidgets3D } = use3DWidget(); const { visualizationSocket } = useSocketStore(); - const { rightClickSelected, setRightClickSelected } = useRightClickSelected() - + const { rightClickSelected, setRightClickSelected } = useRightClickSelected(); const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); // Floor plane for horizontal move const verticalPlane = useRef(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0)); // Vertical plane for vertical move const planeIntersect = useRef(new THREE.Vector3()); - // let [verticalPlane, setFloorPlanesVertical] = useState( - // new THREE.Plane(new THREE.Vector3(0, 1, 0)) - // ); + // const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + // const verticalPlane = useRef(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); + // const planeIntersect = useRef(new THREE.Vector3()); + const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]); + const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }); + useEffect(() => { if (activeModule !== "visualization") return; @@ -48,16 +59,18 @@ export default function Dropped3dWidgets() { const organization = email?.split("@")[1]?.split(".")[0]; async function get3dWidgetData() { - let result = await get3dWidgetZoneData(selectedZone.zoneId, organization); - + const result = await get3dWidgetZoneData(selectedZone.zoneId, organization); + console.log('result: ', result); setWidgets3D(result); - const formattedWidgets = result.map((widget: any) => ({ + const formattedWidgets = result.map((widget: WidgetData) => ({ id: widget.id, type: widget.type, position: widget.position, + rotation: widget.rotation || [0, 0, 0], })); + setZoneWidgetData(selectedZone.zoneId, formattedWidgets); } @@ -91,28 +104,24 @@ export default function Dropped3dWidgets() { if (intersects.length > 0) { const { x, y, z } = intersects[0].point; - const newWidget = { + const newWidget: WidgetData = { id: generateUniqueId(), type: widgetSelect, - position: [x, y, z] as [number, number, number], + position: [x, y, z], + rotation: [0, 0, 0], }; - let add3dWidget = { + const add3dWidget = { organization: organization, widget: newWidget, zoneId: selectedZone.zoneId - } + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget) + visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget); } - // let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget); - // - - // if (response.message === "Widget created successfully") { addWidget(selectedZone.zoneId, newWidget); - // } } }; @@ -128,63 +137,101 @@ export default function Dropped3dWidgets() { if (!rightClickSelected) return; const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; + if (rightSelect === "Duplicate") { - const widgetToDuplicate = activeZoneWidgets.find(w => w.id === rightClickSelected); - if (!widgetToDuplicate) return; - const newWidget = { - id: generateUniqueId(), - type: widgetToDuplicate.type, - position: [ - widgetToDuplicate.position[0] + 0.5, // Slightly shift position - widgetToDuplicate.position[1], - widgetToDuplicate.position[2] + 0.5, - ] as [number, number, number], - }; - let add3dWidget = { - organization, - widget: newWidget, - zoneId: selectedZone.zoneId - }; - // if (visualizationSocket) { - // visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget); - // } - addWidget(selectedZone.zoneId, newWidget); - setRightSelect(null); - setRightClickSelected(null); + async function duplicateWidget() { + const widgetToDuplicate = activeZoneWidgets.find((w: WidgetData) => w.id === rightClickSelected); + if (!widgetToDuplicate) return; + const newWidget: WidgetData = { + id: generateUniqueId(), + type: widgetToDuplicate.type, + position: [ + widgetToDuplicate.position[0] + 0.5, + widgetToDuplicate.position[1], + widgetToDuplicate.position[2] + 0.5, + ], + rotation: widgetToDuplicate.rotation || [0, 0, 0], + }; + const adding3dWidget = { + organization: organization, + widget: newWidget, + zoneId: selectedZone.zoneId + }; + if (visualizationSocket) { + visualizationSocket.emit("v2:viz-3D-widget:add", adding3dWidget); + } + // let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget) + // console.log('response: ', response); + + addWidget(selectedZone.zoneId, newWidget); + setRightSelect(null); + setRightClickSelected(null); + } + duplicateWidget() } + if (rightSelect === "Delete") { - let deleteWidget = { - organization, - widgetId: rightClickSelected, - zoneId: selectedZone.zoneId + const deleteWidgetApi = async () => { + try { + const deleteWidget = { + organization, + id: rightClickSelected, + zoneId: selectedZone.zoneId, + }; + + console.log('deleteWidget: ', deleteWidget); + if (visualizationSocket) { + visualizationSocket.emit("v2:viz-3D-widget:delete", deleteWidget); + } + // Call the API to delete the widget + // const response = await delete3dWidgetApi(selectedZone.zoneId, organization, rightClickSelected); + setZoneWidgetData( + selectedZone.zoneId, + activeZoneWidgets.filter((w: WidgetData) => w.id !== rightClickSelected) + ); + } catch (error) { + console.error("Error deleting widget:", error); + } finally { + setRightClickSelected(null); + setRightSelect(null); + } }; - // if (visualizationSocket) { - // visualizationSocket.emit("v2:viz-3D-widget:delete", deleteWidget); - // } - setZoneWidgetData(selectedZone.zoneId, activeZoneWidgets.filter(w => w.id !== rightClickSelected)); - setRightClickSelected(null); - setRightSelect(null); - } - if (rightSelect === "Horizontal Move") { + deleteWidgetApi(); } - if (rightSelect === "Vertical Move") { - - } - }, [rightSelect, rightClickSelected]); useEffect(() => { - const handleMouseMove = (event: MouseEvent) => { + + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + const handleMouseDown = (event: MouseEvent) => { if (!rightClickSelected || !rightSelect) return; + if (rightSelect === "RotateX" || rightSelect === "RotateY") { + mouseStartRef.current = { x: event.clientX, y: event.clientY }; - const selectedZone = Object.keys(zoneWidgetData).find(zoneId => - zoneWidgetData[zoneId].some(widget => widget.id === rightClickSelected) + const selectedZone = Object.keys(zoneWidgetData).find((zoneId: string) => + zoneWidgetData[zoneId].some((widget: WidgetData) => widget.id === rightClickSelected) + ); + + if (!selectedZone) return; + + const selectedWidget = zoneWidgetData[selectedZone].find((widget: WidgetData) => widget.id === rightClickSelected); + if (selectedWidget) { + rotationStartRef.current = selectedWidget.rotation || [0, 0, 0]; + } + } + }; + + const handleMouseMove = (event: MouseEvent) => { + if (!rightClickSelected || !rightSelect) return; + const selectedZone = Object.keys(zoneWidgetData).find((zoneId: string) => + zoneWidgetData[zoneId].some((widget: WidgetData) => widget.id === rightClickSelected) ); if (!selectedZone) return; - const selectedWidget = zoneWidgetData[selectedZone].find(widget => widget.id === rightClickSelected); + const selectedWidget = zoneWidgetData[selectedZone].find((widget: WidgetData) => widget.id === rightClickSelected); if (!selectedWidget) return; const rect = gl.domElement.getBoundingClientRect(); @@ -194,72 +241,200 @@ export default function Dropped3dWidgets() { raycaster.setFromCamera(mouse, camera); if (rightSelect === "Horizontal Move" && raycaster.ray.intersectPlane(plane.current, planeIntersect.current)) { - - updateWidgetPosition(selectedZone, rightClickSelected, [ + const newPosition: [number, number, number] = [ planeIntersect.current.x, selectedWidget.position[1], planeIntersect.current.z - ]); + ]; + updateWidgetPosition(selectedZone, rightClickSelected, newPosition); + } if (rightSelect === "Vertical Move") { if (raycaster.ray.intersectPlane(verticalPlane.current, planeIntersect.current)) { updateWidgetPosition(selectedZone, rightClickSelected, [ selectedWidget.position[0], - planeIntersect.current.y, // Ensure Y value updates correctly + planeIntersect.current.y, selectedWidget.position[2] ]); - } else { - console.log("No Intersection with Vertical Plane"); } } + + if (rightSelect === "RotateX") { + const deltaX = event.clientX - mouseStartRef.current.x; + const rotationSpeed = 0.03; + const newRotation: [number, number, number] = [ + rotationStartRef.current[0] + deltaX * rotationSpeed, + rotationStartRef.current[1], + rotationStartRef.current[2] + ]; + updateWidgetRotation(selectedZone, rightClickSelected, newRotation); + } + + if (rightSelect === "RotateY") { + const deltaY = event.clientY - mouseStartRef.current.y; + const rotationSpeed = 0.03; + const newRotation: [number, number, number] = [ + rotationStartRef.current[0], + rotationStartRef.current[1] + deltaY * rotationSpeed, + rotationStartRef.current[2] + ]; + updateWidgetRotation(selectedZone, rightClickSelected, newRotation); + } + if (rightSelect === "RotateZ") { + const deltaX = event.movementX; + const rotationSpeed = 0.03; + const currentRotation = selectedWidget.rotation || [0, 0, 0]; + const newRotation: [number, number, number] = [ + currentRotation[0], + currentRotation[1], + currentRotation[2] + deltaX * rotationSpeed + ]; + updateWidgetRotation(selectedZone, rightClickSelected, newRotation); + } + }; const handleMouseUp = () => { + if (!rightClickSelected || !rightSelect) return; + const selectedZone = Object.keys(zoneWidgetData).find(zoneId => + zoneWidgetData[zoneId].some(widget => widget.id === rightClickSelected) + ); + if (!selectedZone) return; - if (rightClickSelected && (rightSelect === "Horizontal Move" || rightSelect === "Vertical Move")) { + const selectedWidget = zoneWidgetData[selectedZone].find(widget => widget.id === rightClickSelected); + if (!selectedWidget) return; + + + + // Format values to 2 decimal places + const formatValues = (vals: number[]) => vals.map(val => parseFloat(val.toFixed(2))); + + if (rightSelect === "Horizontal Move" || rightSelect === "Vertical Move") { + console.log(`${rightSelect} Completed - Full Position:`, formatValues(selectedWidget.position)); + let lastPosition = formatValues(selectedWidget.position) as [number, number, number]; + // (async () => { + // let response = await update3dWidget(selectedZone, organization, rightClickSelected, lastPosition); + // console.log('response: ', response); + // if (response) { + // console.log("Widget position updated in API:", response); + // } + // })(); + let updatingPosition = { + organization: organization, + zoneId: selectedZone, + id: rightClickSelected, + position: lastPosition, + } + if (visualizationSocket) { + visualizationSocket.emit("v2:viz-3D-widget:modifyPositionRotation", updatingPosition); + } - setTimeout(() => { - setRightClickSelected(null); - setRightSelect(null); - }, 50); } - }; + else if (rightSelect.includes("Rotate")) { + const rotation = selectedWidget.rotation || [0, 0, 0]; + console.log(`${rightSelect} Completed - Full Rotation:`, formatValues(rotation)); + let lastRotation = formatValues(rotation) as [number, number, number]; + // (async () => { + // let response = await update3dWidgetRotation(selectedZone, organization, rightClickSelected, lastRotation); + // console.log('response: ', response); + // if (response) { + // console.log("Widget position updated in API:", response); + // } + // })(); + let updatingRotation = { + organization: organization, + zoneId: selectedZone, + id: rightClickSelected, + rotation: lastRotation, + } + if (visualizationSocket) { + visualizationSocket.emit("v2:viz-3D-widget:modifyPositionRotation", updatingRotation); + } + } - // Attach events to window instead of gl.domElement + // Reset selection + setTimeout(() => { + setRightClickSelected(null); + setRightSelect(null); + }, 50); + }; + window.addEventListener("mousedown", handleMouseDown); window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); return () => { + window.removeEventListener("mousedown", handleMouseDown); window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; }, [rightClickSelected, rightSelect, zoneWidgetData, gl]); - - - return ( <> - {activeZoneWidgets.map(({ id, type, position }) => { - const handleRightClick = (event: React.MouseEvent) => { + {activeZoneWidgets.map(({ id, type, position, rotation = [0, 0, 0] }: WidgetData) => { + const handleRightClick = (event: React.MouseEvent, id: string) => { event.preventDefault(); - setRightClickSelected(id) + const canvasElement = document.getElementById("real-time-vis-canvas"); + if (!canvasElement) throw new Error("Canvas element not found"); + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = event.clientX - canvasRect.left; + const relativeY = event.clientY - canvasRect.top; + setEditWidgetOptions(true); + setRightClickSelected(id); + setTop(relativeY); + setLeft(relativeX); }; + switch (type) { case "ui-Widget 1": - return ; + return ( + handleRightClick(e, id)} + /> + ); case "ui-Widget 2": - return ; + return ( + handleRightClick(e, id)} + /> + ); case "ui-Widget 3": - return ; + return ( + handleRightClick(e, id)} + /> + ); case "ui-Widget 4": - return ; + return ( + handleRightClick(e, id)} + /> + ); default: return null; } })} - ); -} +} \ No newline at end of file diff --git a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx index 593df04..4652ec1 100644 --- a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx +++ b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx @@ -126,11 +126,12 @@ const DroppedObjects: React.FC = () => { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; + let deleteFloatingWidget = { floatWidgetID: id, organization: organization, - zoneId: zone.zoneId, - }; + zoneId: zone.zoneId + } if (visualizationSocket) { visualizationSocket.emit("v2:viz-float:delete", deleteFloatingWidget); @@ -143,7 +144,9 @@ const DroppedObjects: React.FC = () => { // if (res.message === "FloatingWidget deleted successfully") { // deleteObject(zoneName, id, index); // Call the deleteObject method from the store // } - } catch (error) {} + } catch (error) { + + } } const handlePointerDown = (event: React.PointerEvent, index: number) => { @@ -458,15 +461,15 @@ const DroppedObjects: React.FC = () => { position: boundedPosition, }, index: draggingIndex.index, - zoneId: zone.zoneId, - }; + zoneId: zone.zoneId + } if (visualizationSocket) { visualizationSocket.emit("v2:viz-float:add", updateFloatingWidget); } // if (response.message === "Widget updated successfully") { - console.log("boundedPosition: ", boundedPosition); + console.log('boundedPosition: ', boundedPosition); updateObjectPosition(zoneName, draggingIndex.index, boundedPosition); // } @@ -509,43 +512,37 @@ const DroppedObjects: React.FC = () => { {zone.objects.map((obj, index) => (
{ diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/components/ui/componets/Panel.tsx index 8ec26a0..94f9886 100644 --- a/app/src/components/ui/componets/Panel.tsx +++ b/app/src/components/ui/componets/Panel.tsx @@ -64,6 +64,59 @@ const Panel: React.FC = ({ const { isPlaying } = usePlayButtonStore(); const { visualizationSocket } = useSocketStore(); + const [canvasDimensions, setCanvasDimensions] = useState({ + width: 0, + height: 0, + }); + // Track canvas dimensions + useEffect(() => { + const canvas = document.getElementById("real-time-vis-canvas"); + if (!canvas) return; + + const updateCanvasDimensions = () => { + const rect = canvas.getBoundingClientRect(); + setCanvasDimensions({ + width: rect.width, + height: rect.height, + }); + }; + + // Initial measurement + updateCanvasDimensions(); + + // Set up ResizeObserver to track changes + const resizeObserver = new ResizeObserver(updateCanvasDimensions); + resizeObserver.observe(canvas); + + return () => { + resizeObserver.unobserve(canvas); + }; + }, []); + + useEffect(() => { + const canvas = document.getElementById("real-time-vis-canvas"); + if (!canvas) return; + + const updateCanvasDimensions = () => { + const rect = canvas.getBoundingClientRect(); + setCanvasDimensions({ + width: rect.width, + height: rect.height, + }); + }; + + // Initial measurement + updateCanvasDimensions(); + + // Set up ResizeObserver to track changes + const resizeObserver = new ResizeObserver(updateCanvasDimensions); + resizeObserver.observe(canvas); + + return () => { + resizeObserver.unobserve(canvas); + }; + }, []); + const getPanelStyle = useMemo( () => (side: Side) => { const currentIndex = selectedZone.panelOrder.indexOf(side); @@ -72,36 +125,50 @@ const Panel: React.FC = ({ const rightActive = previousPanels.includes("right"); const topActive = previousPanels.includes("top"); const bottomActive = previousPanels.includes("bottom"); - const panelSize = isPlaying ? 300 : 210; - + + // Dynamic panel sizes based on canvas width + const panelSizeWidth = Math.max(canvasDimensions.width * 0.165, 200); // Ensure minimum width of 200px + const panelSizeHeight = Math.max(canvasDimensions.width * 0.13, 200); // Ensure minimum height of 200px + switch (side) { case "top": case "bottom": return { + minWidth: "200px", // Minimum width constraint width: `calc(100% - ${ - (leftActive ? panelSize : 0) + (rightActive ? panelSize : 0) + (leftActive ? panelSizeWidth : 0) + (rightActive ? panelSizeWidth : 0) }px)`, - height: `${panelSize - 2}px`, - left: leftActive ? `${panelSize}px` : "0", - right: rightActive ? `${panelSize}px` : "0", + minHeight: "200px", // Minimum height constraint + height: `${panelSizeHeight - 2}px`, // Subtracting for border or margin + left: leftActive ? `${panelSizeWidth}px` : "0", + right: rightActive ? `${panelSizeWidth}px` : "0", [side]: "0", }; + case "left": case "right": return { - width: `${panelSize - 2}px`, + minWidth: "200px", // Minimum width constraint + width: `${panelSizeWidth - 2}px`, // Subtracting for border or margin + minHeight: "200px", // Minimum height constraint height: `calc(100% - ${ - (topActive ? panelSize : 0) + (bottomActive ? panelSize : 0) + (topActive ? panelSizeHeight : 0) + (bottomActive ? panelSizeHeight : 0) }px)`, - top: topActive ? `${panelSize}px` : "0", - bottom: bottomActive ? `${panelSize}px` : "0", + top: topActive ? `${panelSizeHeight}px` : "0", + bottom: bottomActive ? `${panelSizeHeight}px` : "0", [side]: "0", }; + default: return {}; } }, - [selectedZone.panelOrder, isPlaying] + [ + selectedZone.panelOrder, + isPlaying, + canvasDimensions.width, + canvasDimensions.height, + ] ); const handleDrop = (e: React.DragEvent, panel: Side) => { @@ -153,10 +220,10 @@ const Panel: React.FC = ({ let addWidget = { organization: organization, zoneId: selectedZone.zoneId, - widget: newWidget - } + widget: newWidget, + }; if (visualizationSocket) { - visualizationSocket.emit("v2:viz-widget:add", addWidget) + visualizationSocket.emit("v2:viz-widget:add", addWidget); } setSelectedZone((prev) => ({ ...prev, @@ -165,7 +232,6 @@ const Panel: React.FC = ({ try { // let response = await addingWidgets(selectedZone.zoneId, organization, newWidget); - // if (response.message === "Widget created successfully") { // setSelectedZone((prev) => ({ // ...prev, @@ -175,7 +241,6 @@ const Panel: React.FC = ({ } catch (error) { console.error("Error adding widget:", error); } - }; useEffect(() => { @@ -281,3 +346,5 @@ const Panel: React.FC = ({ }; export default Panel; + +// canvasDimensions.width as percent diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 915e3b3..a9ee657 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -7,7 +7,6 @@ import DisplayZone from "./DisplayZone"; import Scene from "../../../modules/scene/scene"; import useModuleStore from "../../../store/useModuleStore"; - import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore"; import { useAsset3dWidget, @@ -24,7 +23,11 @@ import RenderOverlay from "../../templates/Overlay"; import ConfirmationPopup from "../../layout/confirmationPopup/ConfirmationPopup"; import DroppedObjects from "./DroppedFloatingWidgets"; import EditWidgetOption from "../menu/EditWidgetOption"; -import { useRightClickSelected } from "../../../store/useZone3DWidgetStore"; +import { + useEditWidgetOptionsStore, + useRightClickSelected, + useRightSelected, +} from "../../../store/useZone3DWidgetStore"; type Side = "top" | "bottom" | "left" | "right"; @@ -58,8 +61,10 @@ const RealTimeVisulization: React.FC = () => { const [zonesData, setZonesData] = useState({}); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); - - const { rightClickSelected, setRightClickSelected } = useRightClickSelected() + const { rightSelect, setRightSelect } = useRightSelected(); + const { editWidgetOptions, setEditWidgetOptions } = + useEditWidgetOptionsStore(); + const { rightClickSelected, setRightClickSelected } = useRightClickSelected(); const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false); const [floatingWidgets, setFloatingWidgets] = useState< @@ -69,6 +74,7 @@ const RealTimeVisulization: React.FC = () => { const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); const { visualizationSocket } = useSocketStore(); + useEffect(() => { async function GetZoneData() { const email = localStorage.getItem("email") || ""; @@ -96,7 +102,7 @@ const RealTimeVisulization: React.FC = () => { {} ); setZonesData(formattedData); - } catch (error) { } + } catch (error) {} } GetZoneData(); @@ -142,8 +148,8 @@ const RealTimeVisulization: React.FC = () => { const relativeX = event.clientX - canvasRect.left; const relativeY = event.clientY - canvasRect.top; - const newPosition = determinePosition(canvasRect, relativeX, relativeY) - console.log('newPosition: ', newPosition); + const newPosition = determinePosition(canvasRect, relativeX, relativeY); + console.log("newPosition: ", newPosition); const newObject = { ...droppedData, id: generateUniqueId(), @@ -162,12 +168,12 @@ const RealTimeVisulization: React.FC = () => { let addFloatingWidget = { organization: organization, widget: newObject, - zoneId: selectedZone.zoneId - } - console.log('newObject: ', newObject); + zoneId: selectedZone.zoneId, + }; + console.log("newObject: ", newObject); if (visualizationSocket) { - visualizationSocket.emit("v2:viz-float:add", addFloatingWidget) + visualizationSocket.emit("v2:viz-float:add", addFloatingWidget); } // let response = await addingFloatingWidgets( @@ -194,76 +200,134 @@ const RealTimeVisulization: React.FC = () => { ], }, })); - } catch (error) { } + } catch (error) {} }; + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const editWidgetOptions = document.querySelector( + ".editWidgetOptions-wrapper" + ); + if ( + editWidgetOptions && + !editWidgetOptions.contains(event.target as Node) + ) { + setRightClickSelected(null); + setRightSelect(null); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [setRightClickSelected]); + + // Add this useEffect hook to your component + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const editWidgetOptions = document.querySelector( + ".editWidgetOptions-wrapper" + ); + if ( + editWidgetOptions && + !editWidgetOptions.contains(event.target as Node) + ) { + setRightClickSelected(null); + setRightSelect(null); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [setRightClickSelected]); - function handleRightClickSel(){} return ( -
- {openConfirmationPopup && ( - - console.log("confirm")} - onCancel={() => setOpenConfirmationPopup(false)} - /> - - )} + <> + +
+
+ {openConfirmationPopup && ( + + console.log("confirm")} + onCancel={() => setOpenConfirmationPopup(false)} + /> + + )} + {/*
handleDrop(event)} - onDragOver={(event) => event.preventDefault()} - > - -
- {activeModule === "visualization" && selectedZone.zoneName !== "" && } - {activeModule === "visualization" && } - - {activeModule === "visualization" && widgetSubOption === "3D" && rightClickSelected && } - - {activeModule === "visualization" && ( - <> - - - {!isPlaying && selectedZone?.zoneName !== "" && ( - + isPlaying || activeModule !== "visualization" ? "" : "6px", + }} + onDrop={(event) => handleDrop(event)} + onDragOver={(event) => event.preventDefault()} + > + +
*/} + {activeModule === "visualization" && selectedZone.zoneName !== "" && ( + )} + {activeModule === "visualization" && } - - - )} -
+ {activeModule === "visualization" && + editWidgetOptions && + rightClickSelected && ( + + )} + + {activeModule === "visualization" && ( + <> + + + {!isPlaying && selectedZone?.zoneName !== "" && ( + + )} + + + + )} +
+
+ ); }; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index 7629730..000bc20 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -31,7 +31,6 @@ interface ListProps { } const List: React.FC = ({ items = [], remove }) => { - console.log("items: ", items); const { activeModule, setActiveModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { setSubModule } = useSubModuleStore(); diff --git a/app/src/components/ui/menu/EditWidgetOption.tsx b/app/src/components/ui/menu/EditWidgetOption.tsx index ce5261a..1c1fa2e 100644 --- a/app/src/components/ui/menu/EditWidgetOption.tsx +++ b/app/src/components/ui/menu/EditWidgetOption.tsx @@ -1,26 +1,48 @@ import React, { useEffect } from "react"; -import { useLeftData, useRightSelected, useTopData } from "../../../store/useZone3DWidgetStore"; +import { + useEditWidgetOptionsStore, + useLeftData, + useRightClickSelected, + useRightSelected, + useTopData, +} from "../../../store/useZone3DWidgetStore"; interface EditWidgetOptionProps { options: string[]; } -const EditWidgetOption: React.FC = ({ options }) => { - const { top, setTop } = useTopData() - const { left, setLeft } = useLeftData() - const { rightSelect, setRightSelect } = useRightSelected() +const EditWidgetOption: React.FC = ({ + options, +}) => { + const { top } = useTopData(); + const { left } = useLeftData(); + const { setRightSelect } = useRightSelected(); + const { setEditWidgetOptions } = useEditWidgetOptionsStore(); useEffect(() => { - console.log('left: ', left); - console.log('top: ', top); - }, [top, left]) + }, [top, left]); return ( -
+
{options.map((option, index) => ( -
setRightSelect(option)}> +
{ + setRightSelect(option); + setEditWidgetOptions(false); + }} + > {option}
))} diff --git a/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx index 4876fe4..f6589a2 100644 --- a/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx @@ -238,7 +238,7 @@ const BarGraphComponent = ({ }; useEffect(() => { - console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) // Memoize Theme Colors diff --git a/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx index 6eec49e..93c2960 100644 --- a/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx @@ -51,7 +51,7 @@ const DoughnutGraphComponent = ({ }; useEffect(() => { - console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) // Memoize Theme Colors diff --git a/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx index bf76add..c7f7252 100644 --- a/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx @@ -51,7 +51,7 @@ const LineGraphComponent = ({ }; useEffect(() => { - console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) // Memoize Theme Colors diff --git a/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx index 094b9e7..d7cc0da 100644 --- a/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx @@ -237,7 +237,7 @@ const PieChartComponent = ({ }; useEffect(() => { - console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) // Memoize Theme Colors diff --git a/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx b/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx index 92581c0..fb87080 100644 --- a/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx +++ b/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx @@ -51,7 +51,7 @@ const PolarAreaGraphComponent = ({ }; useEffect(() => { - console.log("titleeeeeeeeeeeeeeeeeee",title); + },[]) // Memoize Theme Colors diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index 85f2c54..528fbc2 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -1,24 +1,36 @@ import React, { useState, useRef, useEffect } from "react"; import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { useActiveTool } from "../../../store/store"; +import { + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore, +} from "../../../store/usePlayButtonStore"; const SimulationPlayer: React.FC = () => { - const [speed, setSpeed] = useState(1); + const { speed, setSpeed } = useAnimationPlaySpeed(); const [playSimulation, setPlaySimulation] = useState(false); const { setIsPlaying } = usePlayButtonStore(); const sliderRef = useRef(null); const isDragging = useRef(false); + const { setActiveTool } = useActiveTool(); + const { isPaused, setIsPaused } = usePauseButtonStore(); + const { isReset, setReset } = useResetButtonStore(); // Button functions const handleReset = () => { + setReset(true); setSpeed(1); }; const handlePlayStop = () => { + setIsPaused(!isPaused); setPlaySimulation(!playSimulation); }; const handleExit = () => { setPlaySimulation(false); setIsPlaying(false); + setActiveTool("cursor") }; // Slider functions starts @@ -27,7 +39,7 @@ const SimulationPlayer: React.FC = () => { }; const calculateHandlePosition = () => { - return ((speed - 0.5) / (50 - 0.5)) * 100; + return ((speed - 0.5) / (8 - 0.5)) * 100; }; const handleMouseDown = () => { @@ -115,7 +127,7 @@ const SimulationPlayer: React.FC = () => { { />
-
50x
+
8x
diff --git a/app/src/modules/builder/agv/agv.tsx b/app/src/modules/builder/agv/agv.tsx index ace2e3e..48f4306 100644 --- a/app/src/modules/builder/agv/agv.tsx +++ b/app/src/modules/builder/agv/agv.tsx @@ -1,111 +1,68 @@ -import PolygonGenerator from "./polygonGenerator"; -import { useThree } from "@react-three/fiber"; -import { useEffect, useMemo, useRef, useState } from "react"; -import * as THREE from "three"; -import * as Types from "../../../types/world/worldTypes"; +import { useEffect, useState } from "react"; +import { Line } from "@react-three/drei"; +import { useNavMesh, useSimulationStates } from "../../../store/store"; import PathNavigator from "./pathNavigator"; -import NavMeshDetails from "./navMeshDetails"; -import { - useSelectedActionSphere, - useSimulationPaths, -} from "../../../store/store"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -const Agv = ({ - lines, - plane, -}: { - lines: Types.RefLines; - plane: Types.RefMesh; -}) => { - const [pathPoints, setPathPoints] = useState< - { - uuid: string; - points: { x: number; y: number; z: number }[]; - }[] - >([]); - const { simulationPaths } = useSimulationPaths(); - const { selectedActionSphere } = useSelectedActionSphere(); - useEffect(() => { - if (!Array.isArray(simulationPaths)) { - } else { - let agvModels = simulationPaths.filter( - (val: any) => val.modelName === "agv" - ); +type PathPoints = { + modelUuid: string; + modelSpeed: number; + bufferTime: number; + points: { x: number; y: number; z: number }[]; + hitCount: number; +}; - let findMesh = agvModels.filter( - (val: any) => - val.modeluuid === selectedActionSphere?.path?.modeluuid && - val.type === "Vehicle" - ); +const Agv = () => { + const [pathPoints, setPathPoints] = useState([]); + const { simulationStates } = useSimulationStates(); + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); - const result = - findMesh.length > 0 && - findMesh[0].type === "Vehicle" && - typeof findMesh[0].point?.actions.start === "object" && - typeof findMesh[0].point?.actions.end === "object" && - "x" in findMesh[0].point.actions.start && - "y" in findMesh[0].point.actions.start && - "x" in findMesh[0].point.actions.end && - "y" in findMesh[0].point.actions.end - ? [ - { - uuid: findMesh[0].modeluuid, // Ensure it's a number + useEffect(() => { + if (simulationStates.length > 0) { - points: [ - { - x: findMesh[0].position[0], - y: findMesh[0].position[1], - z: findMesh[0].position[2], - }, - { - x: findMesh[0].point.actions.start.x, - y: 0, - z: findMesh[0].point.actions.start.y, - }, - { - x: findMesh[0].point.actions.end.x, - y: 0, - z: findMesh[0].point.actions.end.y, - }, - ], - }, - ] - : []; - if (result.length > 0) { - setPathPoints((prev) => { - const existingUUIDs = new Set(prev.map((item) => item.uuid)); - const newItems = result.filter( - (item) => !existingUUIDs.has(item.uuid) - ); - return [...prev, ...newItems]; - }); - } - } - }, [simulationPaths, selectedActionSphere]); + const agvModels = simulationStates.filter((val) => val.modelName === "agv" && val.type === "Vehicle"); - let groupRef = useRef() as Types.RefGroup; - const [navMesh, setNavMesh] = useState(); + const newPathPoints = agvModels.filter((model: any) => model.points && model.points.actions && typeof model.points.actions.start === "object" && typeof model.points.actions.end === "object" && "x" in model.points.actions.start && "y" in model.points.actions.start && "x" in model.points.actions.end && "y" in model.points.actions.end) + .map((model: any) => ({ + modelUuid: model.modeluuid, + modelSpeed: model.points.speed, + bufferTime: model.points.actions.buffer, + hitCount: model.points.actions.hitCount, + points: [ + { x: model.position[0], y: model.position[1], z: model.position[2] }, + { x: model.points.actions.start.x, y: 0, z: model.points.actions.start.y }, + { x: model.points.actions.end.x, y: 0, z: model.points.actions.end.y }, + ], + })); - return ( - <> - - - {pathPoints.map((pair, i) => ( - - ))} - - - ); + setPathPoints(newPathPoints); + } + }, [simulationStates]); + + return ( + <> + {pathPoints.map((pair, i) => ( + + + + {pair.points.slice(1).map((point, idx) => ( + + + + + ))} + + ))} + + ); }; export default Agv; diff --git a/app/src/modules/builder/agv/navMeshCreator.tsx b/app/src/modules/builder/agv/navMeshCreator.tsx new file mode 100644 index 0000000..cdbca45 --- /dev/null +++ b/app/src/modules/builder/agv/navMeshCreator.tsx @@ -0,0 +1,31 @@ +import { useRef } from "react"; +import { useNavMesh } from "../../../store/store"; +import PolygonGenerator from "./polygonGenerator"; +import NavMeshDetails from "./navMeshDetails"; +import * as CONSTANTS from "../../../types/world/worldConstants"; +import * as Types from "../../../types/world/worldTypes"; + +type NavMeshCreatorProps = { + lines: Types.RefLines +}; + +function NavMeshCreator({ lines }: NavMeshCreatorProps) { + let groupRef = useRef() as Types.RefGroup; + const { setNavMesh } = useNavMesh(); + + return ( + <> + + + + + + + + + + + ) +} + +export default NavMeshCreator \ No newline at end of file diff --git a/app/src/modules/builder/agv/navMeshDetails.tsx b/app/src/modules/builder/agv/navMeshDetails.tsx index ecb539b..ae95942 100644 --- a/app/src/modules/builder/agv/navMeshDetails.tsx +++ b/app/src/modules/builder/agv/navMeshDetails.tsx @@ -10,14 +10,12 @@ interface NavMeshDetailsProps { setNavMesh: (navMesh: any) => void; groupRef: React.MutableRefObject; lines: Types.RefLines; - plane: Types.RefMesh; } export default function NavMeshDetails({ lines, setNavMesh, groupRef, - plane, }: NavMeshDetailsProps) { const { scene } = useThree(); @@ -34,14 +32,13 @@ export default function NavMeshDetails({ const [positions, indices] = getPositionsAndIndices(meshes); - const cs = 0.25; - const ch = 0.69; + const cellSize = 0.35; + const cellHeight = 0.7; const walkableRadius = 0.5; - const { success, navMesh } = generateSoloNavMesh(positions, indices, { - cs, - ch, - walkableRadius: Math.round(walkableRadius / ch), + cs: cellSize, + ch: cellHeight, + walkableRadius: Math.round(walkableRadius / cellHeight), }); if (!success || !navMesh) { @@ -50,10 +47,14 @@ export default function NavMeshDetails({ setNavMesh(navMesh); + scene.children + .filter((child) => child instanceof DebugDrawer) + .forEach((child) => scene.remove(child)); + const debugDrawer = new DebugDrawer(); debugDrawer.drawNavMesh(navMesh); // scene.add(debugDrawer); - } catch (error) {} + } catch (error) { } }; initializeNavigation(); diff --git a/app/src/modules/builder/agv/pathNavigator.tsx b/app/src/modules/builder/agv/pathNavigator.tsx index 9da0d59..7d7984c 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -3,147 +3,207 @@ import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { NavMeshQuery } from "@recast-navigation/core"; import { Line } from "@react-three/drei"; -import { useTh } from "leva/dist/declarations/src/styles"; -import { useActiveTool } from "../../../store/store"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -// Define interface for props interface PathNavigatorProps { - navMesh: any; - selectedPoints: any; - id: string; + navMesh: any; + pathPoints: any; + id: string; + speed: number; + bufferTime: number; + hitCount: number; } export default function PathNavigator({ - navMesh, - selectedPoints, - id, + navMesh, + pathPoints, + id, + speed, + bufferTime, + hitCount }: PathNavigatorProps) { - const [path, setPath] = useState<[number, number, number][]>([]); - const progressRef = useRef(0); - const distancesRef = useRef([]); - const totalDistanceRef = useRef(0); - const currentSegmentIndex = useRef(0); - const { scene } = useThree(); - const { activeTool } = useActiveTool(); - const [startPoint, setStartPoint] = useState(new THREE.Vector3()); - const meshRef = useRef(null); - useEffect(() => { - if (!scene || !id || path.length < 2) return; + const [path, setPath] = useState<[number, number, number][]>([]); + const [currentPhase, setCurrentPhase] = useState<'initial' | 'loop'>('initial'); + const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>([]); + const [pickupDropPath, setPickupDropPath] = useState<[number, number, number][]>([]); + const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]); + const [initialPosition, setInitialPosition] = useState(null); + const [initialRotation, setInitialRotation] = useState(null); - let totalDistance = 0; - const distances: number[] = []; - for (let i = 0; i < path.length - 1; i++) { - const start = new THREE.Vector3(...path[i]); - const end = new THREE.Vector3(...path[i + 1]); - const segmentDistance = start.distanceTo(end); - distances.push(segmentDistance); - totalDistance += segmentDistance; - } + const distancesRef = useRef([]); + const totalDistanceRef = useRef(0); + const progressRef = useRef(0); + const isWaiting = useRef(false); + const timeoutRef = useRef(null); - distancesRef.current = distances; - totalDistanceRef.current = totalDistance; - progressRef.current = 0; // Reset progress when the path changes - }, [path]); - useEffect(() => { - if (!navMesh || selectedPoints.length === 0) return; + const { scene } = useThree(); + const { isPlaying } = usePlayButtonStore(); - // Flatten the selectedPoints array into a single list of points - const allPoints = selectedPoints.flat(); + useEffect(() => { + const object = scene.getObjectByProperty("uuid", id); + if (object) { + setInitialPosition(object.position.clone()); + setInitialRotation(object.rotation.clone()); + } + }, [scene, id]); - // Compute paths between consecutive points - const computedPath: [number, number, number][] = []; - for (let i = 0; i < allPoints.length - 1; i++) { - const start = allPoints[i]; - setStartPoint( - new THREE.Vector3(allPoints[0].x, allPoints[0].y, allPoints[0].z) - ); + const computePath = (start: any, end: any) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + const { path: segmentPath } = navMeshQuery.computePath(start, end); + return segmentPath?.map(({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]) || []; + } catch { + return []; + } + }; - const end = allPoints[i + 1]; + const resetState = () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } - try { - const navMeshQuery = new NavMeshQuery(navMesh); - const { path: segmentPath } = navMeshQuery.computePath(start, end); + setPath([]); + setCurrentPhase('initial'); + setPickupDropPath([]); + setDropPickupPath([]); + distancesRef.current = []; + totalDistanceRef.current = 0; + progressRef.current = 0; + isWaiting.current = false; - if (segmentPath && segmentPath.length > 0) { - computedPath.push( - ...segmentPath.map(({ x, y, z }): [number, number, number] => [ - x, - y + 0.1, - z, - ]) - ); - } - } catch (error) {} - } + if (initialPosition && initialRotation) { + const object = scene.getObjectByProperty("uuid", id); + if (object) { + object.position.copy(initialPosition); + object.rotation.copy(initialRotation); + } + } + }; - // Set the full computed path + useEffect(() => { + if (!isPlaying) { + resetState(); + } - if (computedPath.length > 0) { - setPath(computedPath); - currentSegmentIndex.current = 0; // Reset to the first segment - } - }, [selectedPoints, navMesh, path]); + if (!navMesh || pathPoints.length < 2) return; - useFrame((_, delta) => { - if (!scene || !id || path.length < 2) return; + const [pickup, drop] = pathPoints.slice(-2); + const object = scene.getObjectByProperty("uuid", id); + if (!object) return; - // Find the object in the scene - const findObject = scene.getObjectByProperty("uuid", id); - if (activeTool === "play") { - if (!findObject) return; + const currentPosition = { x: object.position.x, y: object.position.y, z: object.position.z }; - const speed = 5; - progressRef.current += delta * speed; + const toPickupPath = computePath(currentPosition, pickup); + const pickupToDropPath = computePath(pickup, drop); + const dropToPickupPath = computePath(drop, pickup); - let coveredDistance = progressRef.current; - let accumulatedDistance = 0; - let index = 0; + if (toPickupPath.length && pickupToDropPath.length && dropToPickupPath.length) { + setPickupDropPath(pickupToDropPath); + setDropPickupPath(dropToPickupPath); + setToPickupPath(toPickupPath); + setPath(toPickupPath); + setCurrentPhase('initial'); + } + }, [navMesh, pathPoints, hitCount, isPlaying]); - // Determine the current segment of the path - while ( - index < distancesRef.current.length && - coveredDistance > accumulatedDistance + distancesRef.current[index] - ) { - accumulatedDistance += distancesRef.current[index]; - index++; - } + useEffect(() => { + if (path.length < 2) return; - // If the object has reached the end of the path, stop moving - if (index >= distancesRef.current.length) { - progressRef.current = totalDistanceRef.current; - return; - } + let total = 0; + const segmentDistances = path.slice(0, -1).map((point, i) => { + const dist = new THREE.Vector3(...point).distanceTo(new THREE.Vector3(...path[i + 1])); + total += dist; + return dist; + }); - // Interpolate position within the current segment - const start = new THREE.Vector3(...path[index]); - const end = new THREE.Vector3(...path[index + 1]); - const segmentDistance = distancesRef.current[index]; + distancesRef.current = segmentDistances; + totalDistanceRef.current = total; + progressRef.current = 0; + isWaiting.current = false; + }, [path]); - const t = (coveredDistance - accumulatedDistance) / segmentDistance; - const position = start.clone().lerp(end, t); - findObject.position.copy(position); + useFrame((_, delta) => { + if (!isPlaying || path.length < 2 || !scene || !id) return; - // Rotate the object to face the direction of movement - const direction = new THREE.Vector3().subVectors(end, start).normalize(); - const targetQuaternion = new THREE.Quaternion().setFromUnitVectors( - new THREE.Vector3(0, 0, 1), // Assuming forward direction is (0, 0, 1) - direction - ); - findObject.quaternion.slerp(targetQuaternion, 0.1); // Smoothly interpolate rotation - } else if (activeTool === "cursor") { - findObject?.position.copy(startPoint); - } - }); + const object = scene.getObjectByProperty("uuid", id); + if (!object) return; - return ( - <> - {path.length > 0 && } - {/* {path.length > 0 && ( - 0 ? path[0] : [0, 0.1, 0]}> - - - - )} */} - - ); -} + const speedFactor = speed; + progressRef.current += delta * speedFactor; + + let covered = progressRef.current; + let accumulated = 0; + let index = 0; + + while ( + index < distancesRef.current.length && + covered > accumulated + distancesRef.current[index] + ) { + accumulated += distancesRef.current[index]; + index++; + } + + if (index >= distancesRef.current.length) { + progressRef.current = totalDistanceRef.current; + + if (!isWaiting.current) { + isWaiting.current = true; + + timeoutRef.current = setTimeout(() => { + if (currentPhase === 'initial') { + setPath(pickupDropPath); + setCurrentPhase('loop'); + } else { + setPath(prevPath => + prevPath === pickupDropPath ? dropPickupPath : pickupDropPath + ); + } + + progressRef.current = 0; + isWaiting.current = false; + }, bufferTime * 1000); + } + return; + } + + const start = new THREE.Vector3(...path[index]); + const end = new THREE.Vector3(...path[index + 1]); + const dist = distancesRef.current[index]; + const t = THREE.MathUtils.clamp((covered - accumulated) / dist, 0, 1); + const position = start.clone().lerp(end, t); + + object.position.copy(position); + + const direction = new THREE.Vector3().subVectors(end, start).normalize(); + const targetRotationY = Math.atan2(direction.x, direction.z); + + let angleDifference = targetRotationY - object.rotation.y; + angleDifference = ((angleDifference + Math.PI) % (Math.PI * 2)) - Math.PI; + object.rotation.y += angleDifference * 0.1; + }); + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + return ( + + {toPickupPath.length > 0 && ( + + )} + + {pickupDropPath.length > 0 && ( + + )} + + {dropPickupPath.length > 0 && ( + + )} + + ); +} \ No newline at end of file diff --git a/app/src/modules/builder/agv/polygonGenerator.tsx b/app/src/modules/builder/agv/polygonGenerator.tsx index 8682371..2462018 100644 --- a/app/src/modules/builder/agv/polygonGenerator.tsx +++ b/app/src/modules/builder/agv/polygonGenerator.tsx @@ -6,21 +6,12 @@ import arrayLinesToObject from "../geomentries/lines/lineConvertions/arrayLinesT interface PolygonGeneratorProps { groupRef: React.MutableRefObject; lines: Types.RefLines; - plane: Types.RefMesh; } export default function PolygonGenerator({ groupRef, lines, - plane, }: PolygonGeneratorProps) { - // const [rooms, setRooms] = useState([]); - - useEffect(() => { - if (groupRef.current && plane.current) { - groupRef.current.add(plane.current.clone()); - } - }, [groupRef, plane]); useEffect(() => { let allLines = arrayLinesToObject(lines.current); @@ -37,13 +28,14 @@ export default function PolygonGenerator({ uuid: point.uuid, })) ); + if (!result || result.some((line) => !line)) { return; } const lineFeatures = result?.map((line: any) => turf.lineString(line.map((p: any) => p?.position)) - ); + ); const polygons = turf.polygonize(turf.featureCollection(lineFeatures)); renderWallGeometry(wallPoints); @@ -79,8 +71,8 @@ export default function PolygonGenerator({ groupRef.current?.add(mesh); } }); - } + }, [lines.current]); const renderWallGeometry = (walls: THREE.Vector3[][]) => { diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index a1ed87b..9fc6199 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -24,7 +24,7 @@ async function addAssetModel( socket: Socket, selectedItem: any, setSelectedItem: any, - setSimulationPaths: any, + setSimulationStates: any, plane: Types.RefMesh, ): Promise { @@ -65,7 +65,7 @@ async function addAssetModel( const cachedModel = THREE.Cache.get(selectedItem.id); if (cachedModel) { // console.log(`[Cache] Fetching ${selectedItem.name}`); - handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); + handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket); return; } else { const cachedModelBlob = await retrieveGLTF(selectedItem.id); @@ -78,7 +78,7 @@ async function addAssetModel( URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(selectedItem.id, gltf); - handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); + handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket); }, () => { TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup); @@ -90,7 +90,7 @@ async function addAssetModel( const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob()); await storeGLTF(selectedItem.id, modelBlob); THREE.Cache.add(selectedItem.id, gltf); - await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationPaths, socket); + await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket); }, () => { TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup); @@ -113,7 +113,7 @@ async function handleModelLoad( tempLoader: Types.RefMesh, isTempLoader: Types.RefBoolean, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: any, + setSimulationStates: any, socket: Socket ) { const model = gltf.scene.clone(); @@ -136,7 +136,7 @@ async function handleModelLoad( tempLoader.current = undefined; } - const newFloorItem: Types.FloorItemType = { + const newFloorItem: Types.EventData = { modeluuid: model.uuid, modelname: selectedItem.name, modelfileID: selectedItem.id, @@ -154,7 +154,7 @@ async function handleModelLoad( if (res.type === "Conveyor") { const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID()); - const backendEventData: Extract = { + const backendEventData: Extract = { type: 'Conveyor', points: res.points.map((point: any, index: number) => ({ uuid: pointUUIDs[index], @@ -167,11 +167,11 @@ async function handleModelLoad( material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', - isUsed: false + isUsed: true }], triggers: [], connections: { - source: { pathUUID: model.uuid, pointUUID: pointUUIDs[index] }, + source: { modelUUID: model.uuid, pointUUID: pointUUIDs[index] }, targets: [] } })), @@ -189,7 +189,7 @@ async function handleModelLoad( // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, // false, // true, - // newFloorItem.eventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); // SOCKET @@ -206,8 +206,7 @@ async function handleModelLoad( eventData: backendEventData, socketId: socket.id }; - - console.log('data: ', data); + setFloorItems((prevItems) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); @@ -220,9 +219,71 @@ async function handleModelLoad( eventData.position = newFloorItem.position; eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ ...(prevEvents || []), - eventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + eventData as Types.ConveyorEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + + } else if (res.type === "Vehicle") { + + const pointUUID = THREE.MathUtils.generateUUID(); + + const backendEventData: Extract = { + type: "Vehicle", + points: { + uuid: pointUUID, + position: res.points.position as [number, number, number], + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: '', start: {}, hitCount: 1, end: {}, buffer: 0 }, + connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] }, + speed: 2, + } + } + + // API + + // await setFloorItemApi( + // organization, + // newFloorItem.modeluuid, + // newFloorItem.modelname, + // newFloorItem.modelfileID, + // newFloorItem.position, + // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, + isLocked: false, + isVisible: true, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id + }; + + const eventData: any = backendEventData; + eventData.modeluuid = newFloorItem.modeluuid; + eventData.modelName = newFloorItem.modelname; + eventData.position = newFloorItem.position; + + setFloorItems((prevItems) => { + const updatedItems = [...(prevItems || []), newFloorItem]; + localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); + return updatedItems; + }); + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + ...(prevEvents || []), + eventData as Types.VehicleEventsSchema ]); socket.emit("v2:model-asset:add", data); @@ -239,7 +300,7 @@ async function handleModelLoad( // newFloorItem.position, // { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z }, // false, - // true + // true, // ); // SOCKET @@ -255,7 +316,8 @@ async function handleModelLoad( isVisible: true, socketId: socket.id }; - + + setFloorItems((prevItems) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); @@ -263,7 +325,6 @@ async function handleModelLoad( }); socket.emit("v2:model-asset:add", data); - } gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" }); diff --git a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts index a71aaea..028863c 100644 --- a/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts +++ b/app/src/modules/builder/geomentries/assets/deleteFloorItems.ts @@ -10,7 +10,7 @@ async function DeleteFloorItems( itemsGroup: Types.RefGroup, hoveredDeletableFloorItem: Types.RefMesh, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: any, + setSimulationStates: any, socket: Socket ): Promise { @@ -76,7 +76,7 @@ async function DeleteFloorItems( } setFloorItems(updatedItems); - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== removedItem.modeluuid); return updatedEvents; }); diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index 2b77fb2..96f4fad 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -10,7 +10,7 @@ import { useRenderDistance, useselectedFloorItem, useSelectedItem, - useSimulationPaths, + useSimulationStates, useSocketStore, useToggleView, useTransformMode, @@ -65,7 +65,7 @@ const FloorItemsGroup = ({ const { setselectedFloorItem } = useselectedFloorItem(); const { activeTool } = useActiveTool(); const { selectedItem, setSelectedItem } = useSelectedItem(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { setLoadingProgress } = useLoadingProgress(); const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); @@ -110,9 +110,8 @@ const FloorItemsGroup = ({ } gltfLoaderWorker.postMessage({ floorItems: data }); } else { - console.log('data: ', data); gltfLoaderWorker.postMessage({ floorItems: [] }); - loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths); + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); updateLoadingProgress(100); } }); @@ -131,7 +130,7 @@ const FloorItemsGroup = ({ updateLoadingProgress(progress); if (loadedAssets === totalAssets) { - loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationPaths); + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); updateLoadingProgress(100); } }); @@ -249,7 +248,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, setFloorItems, - setSimulationPaths, + setSimulationStates, socket ); } @@ -375,7 +374,7 @@ const FloorItemsGroup = ({ socket, selectedItem, setSelectedItem, - setSimulationPaths, + setSimulationStates, plane ); } diff --git a/app/src/modules/collaboration/collabCams.tsx b/app/src/modules/collaboration/collabCams.tsx index b15d17d..a1383f2 100644 --- a/app/src/modules/collaboration/collabCams.tsx +++ b/app/src/modules/collaboration/collabCams.tsx @@ -12,203 +12,189 @@ import CollabUserIcon from "./collabUserIcon"; import { getAvatarColor } from "./users/functions/getAvatarColor"; const CamModelsGroup = () => { - let navigate = useNavigate(); - const groupRef = useRef(null); - const email = localStorage.getItem("email"); - const { activeUsers, setActiveUsers } = useActiveUsers(); - const { socket } = useSocketStore(); - const loader = new GLTFLoader(); - const dracoLoader = new DRACOLoader(); - const [cams, setCams] = useState([]); - const [models, setModels] = useState>({}); + const navigate = useNavigate(); + const groupRef = useRef(null); + const email = localStorage.getItem("email"); + const { activeUsers, setActiveUsers } = useActiveUsers(); + const { socket } = useSocketStore(); - dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); - loader.setDRACOLoader(dracoLoader); + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader(); + dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); + loader.setDRACOLoader(dracoLoader); - useEffect(() => { - if (!email) { - navigate("/"); - } - if (!socket) return; - const organization = email!.split("@")[1].split(".")[0]; + const [cams, setCams] = useState([]); + const [models, setModels] = useState>({}); - socket.on("userConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (data.data.userData.email === email) return; - if (socket.id === data.socketId || organization !== data.organization) - return; + const dedupeCams = (cams: any[]) => { + const seen = new Set(); + return cams.filter((cam) => { + if (seen.has(cam.uuid)) return false; + seen.add(cam.uuid); + return true; + }); + }; - const model = groupRef.current.getObjectByProperty( - "uuid", - data.data.userData._id - ); - if (model) { - groupRef.current.remove(model); - } - loader.load(camModel, (gltf) => { - const newModel = gltf.scene.clone(); - newModel.uuid = data.data.userData._id; - newModel.position.set( - data.data.position.x, - data.data.position.y, - data.data.position.z - ); - newModel.rotation.set( - data.data.rotation.x, - data.data.rotation.y, - data.data.rotation.z - ); - newModel.userData = data.data.userData; - setCams((prev) => [...prev, newModel]); - setActiveUsers([...activeUsers, data.data.userData]); - }); - }); + const dedupeUsers = (users: any[]) => { + const seen = new Set(); + return users.filter((user) => { + if (seen.has(user._id)) return false; + seen.add(user._id); + return true; + }); + }; - // socket.on("users:online", (data: any) => { - // console.log('users online: ', data); - // }) + useEffect(() => { + if (!email) navigate("/"); - socket.on("userDisConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (socket.id === data.socketId || organization !== data.organization) - return; + if (!socket) return; + const organization = email!.split("@")[1].split(".")[0]; - setCams((prev) => - prev.filter((cam) => cam.uuid !== data.data.userData._id) - ); - setActiveUsers( - activeUsers.filter((user: any) => user._id !== data.data.userData._id) - ); - }); + socket.on("userConnectResponse", (data: any) => { + if (!groupRef.current) return; + if (data.data.userData.email === email) return; + if (socket.id === data.socketId || organization !== data.organization) return; - socket.on("cameraUpdateResponse", (data: any) => { - if ( - !groupRef.current || - socket.id === data.socketId || - organization !== data.organization - ) - return; + const model = groupRef.current.getObjectByProperty("uuid", data.data.userData._id); + if (model) { + groupRef.current.remove(model); + } - setModels((prev) => ({ - ...prev, - [data.data.userId]: { - targetPosition: new THREE.Vector3( - data.data.position.x, - data.data.position.y, - data.data.position.z - ), - targetRotation: new THREE.Euler( - data.data.rotation.x, - data.data.rotation.y, - data.data.rotation.z - ), - }, - })); - }); + loader.load(camModel, (gltf) => { + const newModel = gltf.scene.clone(); + newModel.uuid = data.data.userData._id; + newModel.position.set( + data.data.position.x, + data.data.position.y, + data.data.position.z + ); + newModel.rotation.set( + data.data.rotation.x, + data.data.rotation.y, + data.data.rotation.z + ); + newModel.userData = data.data.userData; - return () => { - socket.off("userConnectRespones"); - socket.off("userDisConnectRespones"); - socket.off("cameraUpdateResponse"); - }; - }, [socket, activeUsers]); + setCams((prev) => dedupeCams([...prev, newModel])); + setActiveUsers((prev: any) => + dedupeUsers([...prev, data.data.userData]) + ); + }); + }); + socket.on("userDisConnectResponse", (data: any) => { + if (!groupRef.current) return; + if (socket.id === data.socketId || organization !== data.organization) return; - // useEffect(() => { - // console.log(activeUsers); - // }, [activeUsers]) + setCams((prev) => + prev.filter((cam) => cam.uuid !== data.data.userData._id) + ); + setActiveUsers((prev: any) => + prev.filter((user: any) => user._id !== data.data.userData._id) + ); + }); - // useEffect(() => { - // console.log(models); - // }, [models]) + socket.on("cameraUpdateResponse", (data: any) => { + if ( + !groupRef.current || + socket.id === data.socketId || + organization !== data.organization + ) + return; - useFrame(() => { - if (!groupRef.current) return; - Object.keys(models).forEach((uuid) => { - const model = groupRef.current!.getObjectByProperty("uuid", uuid); - if (!model) return; + setModels((prev) => ({ + ...prev, + [data.data.userId]: { + targetPosition: new THREE.Vector3( + data.data.position.x, + data.data.position.y, + data.data.position.z + ), + targetRotation: new THREE.Euler( + data.data.rotation.x, + data.data.rotation.y, + data.data.rotation.z + ), + }, + })); + }); - const { targetPosition, targetRotation } = models[uuid]; - model.position.lerp(targetPosition, 0.1); - model.rotation.x = THREE.MathUtils.lerp( - model.rotation.x, - targetRotation.x, - 0.1 - ); - model.rotation.y = THREE.MathUtils.lerp( - model.rotation.y, - targetRotation.y, - 0.1 - ); - model.rotation.z = THREE.MathUtils.lerp( - model.rotation.z, - targetRotation.z, - 0.1 - ); - }); - }); + return () => { + socket.off("userConnectResponse"); + socket.off("userDisConnectResponse"); + socket.off("cameraUpdateResponse"); + }; + }, [socket]); - useEffect(() => { - if (!groupRef.current) return; - const organization = email!.split("@")[1].split(".")[0]; - getActiveUsersData(organization).then((data) => { - const filteredData = data.cameraDatas.filter( - (camera: any) => camera.userData.email !== email - ); - let a: any = []; - if (filteredData.length > 0) { - loader.load(camModel, (gltf) => { - const newCams = filteredData.map((cam: any) => { - const newModel = gltf.scene.clone(); - newModel.uuid = cam.userData._id; - newModel.position.set( - cam.position.x, - cam.position.y, - cam.position.z - ); - newModel.rotation.set( - cam.rotation.x, - cam.rotation.y, - cam.rotation.z - ); - newModel.userData = cam.userData; - a.push(cam.userData); - return newModel; - }); - setActiveUsers(a); - setCams((prev) => [...prev, ...newCams]); - }); - } - }); - }, []); + useFrame(() => { + if (!groupRef.current) return; + Object.keys(models).forEach((uuid) => { + const model = groupRef.current!.getObjectByProperty("uuid", uuid); + if (!model) return; - return ( - - {cams.map((cam, index) => ( - - - - - - ))} - - ); + const { targetPosition, targetRotation } = models[uuid]; + model.position.lerp(targetPosition, 0.1); + model.rotation.x = THREE.MathUtils.lerp(model.rotation.x, targetRotation.x, 0.1); + model.rotation.y = THREE.MathUtils.lerp(model.rotation.y, targetRotation.y, 0.1); + model.rotation.z = THREE.MathUtils.lerp(model.rotation.z, targetRotation.z, 0.1); + }); + }); + + useEffect(() => { + if (!groupRef.current) return; + const organization = email!.split("@")[1].split(".")[0]; + + getActiveUsersData(organization).then((data) => { + const filteredData = data.cameraDatas.filter( + (camera: any) => camera.userData.email !== email + ); + + if (filteredData.length > 0) { + loader.load(camModel, (gltf) => { + const newCams = filteredData.map((cam: any) => { + const newModel = gltf.scene.clone(); + newModel.uuid = cam.userData._id; + newModel.position.set(cam.position.x, cam.position.y, cam.position.z); + newModel.rotation.set(cam.rotation.x, cam.rotation.y, cam.rotation.z); + newModel.userData = cam.userData; + return newModel; + }); + + const users = filteredData.map((cam: any) => cam.userData); + setActiveUsers((prev: any) => dedupeUsers([...prev, ...users])); + setCams((prev) => dedupeCams([...prev, ...newCams])); + }); + } + }); + }, []); + + return ( + + {cams.map((cam, index) => ( + + + + + + ))} + + ); }; export default CamModelsGroup; diff --git a/app/src/modules/collaboration/collabUserIcon.tsx b/app/src/modules/collaboration/collabUserIcon.tsx index acc3fa0..4cea436 100644 --- a/app/src/modules/collaboration/collabUserIcon.tsx +++ b/app/src/modules/collaboration/collabUserIcon.tsx @@ -20,7 +20,24 @@ const CollabUserIcon: React.FC = ({ {userImage ? ( {userName} ) : ( - + + //
+ // {userName[0]} + //
)}
diff --git a/app/src/modules/collaboration/socketResponses.dev.tsx b/app/src/modules/collaboration/socketResponses.dev.tsx index b8cec55..ceef567 100644 --- a/app/src/modules/collaboration/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socketResponses.dev.tsx @@ -143,10 +143,6 @@ export default function SocketResponses({ isVisible: data.data.isVisible, }; - if (data.data.eventData) { - newFloorItem.eventData = data.data.eventData; - } - setFloorItems((prevItems: any) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); @@ -221,10 +217,6 @@ export default function SocketResponses({ isVisible: data.data.isVisible, }; - if (data.data.eventData) { - newFloorItem.eventData = data.data.eventData; - } - setFloorItems((prevItems: any) => { const updatedItems = [...(prevItems || []), newFloorItem]; localStorage.setItem("FloorItems", JSON.stringify(updatedItems)); @@ -241,7 +233,7 @@ export default function SocketResponses({ } } else if (data.message === "Model updated successfully") { - itemsGroup.current.children.forEach((item: THREE.Group) => { + itemsGroup.current?.children.forEach((item: THREE.Group) => { if (item.uuid === data.data.modeluuid) { item.position.set(...data.data.position as [number, number, number]); item.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z); diff --git a/app/src/modules/collaboration/users/Avatar.tsx b/app/src/modules/collaboration/users/Avatar.tsx index f08a545..b04ff7d 100644 --- a/app/src/modules/collaboration/users/Avatar.tsx +++ b/app/src/modules/collaboration/users/Avatar.tsx @@ -7,6 +7,7 @@ interface AvatarProps { size?: number; index?: number; textColor?: string; + color?: string; // Optional color prop for future use } const CustomAvatar: React.FC = ({ @@ -14,6 +15,7 @@ const CustomAvatar: React.FC = ({ size = 100, index = 0, textColor = "#ffffff", + color, // Optional color prop for future use }) => { const [imageSrc, setImageSrc] = useState(null); @@ -26,7 +28,7 @@ const CustomAvatar: React.FC = ({ const initials = getInitials(name); // Convert name to initials if needed // Draw background - ctx.fillStyle = getAvatarColor(index); + ctx.fillStyle = color || getAvatarColor(index); // Use color prop or generate color based on index ctx.fillRect(0, 0, size, size); // Draw initials @@ -40,7 +42,7 @@ const CustomAvatar: React.FC = ({ const dataURL = canvas.toDataURL("image/png"); setImageSrc(dataURL); } - }, [name, size, textColor]); + }, [name, size, textColor, index]); if (!imageSrc) { return null; // Return null while the image is being generated @@ -53,6 +55,18 @@ const CustomAvatar: React.FC = ({ alt="User Avatar" style={{ width: "100%", height: "100%" }} /> + //
+ // {name[0]} + //
); }; diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts index 3630378..ec6033b 100644 --- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts @@ -12,7 +12,7 @@ import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAss async function loadInitialFloorItems( itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void + setSimulationStates: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => void ): Promise { if (!itemsGroup.current) return; let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -23,10 +23,10 @@ async function loadInitialFloorItems( localStorage.setItem("FloorItems", JSON.stringify(items)); await initializeDB(); - if (items.message === "floorItems not found") return; + if (items.message === "floorItems not found") return; if (items) { - const storedFloorItems: Types.FloorItems = items; + const storedFloorItems: Types.EventData[] = items; const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); @@ -53,7 +53,6 @@ async function loadInitialFloorItems( }); for (const item of storedFloorItems) { - console.log('item: ', item); if (!item.modelfileID) return; const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]); let storedPosition; @@ -72,7 +71,7 @@ async function loadInitialFloorItems( const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { // console.log(`[Cache] Fetching ${item.modelname}`); - processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); + processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); return; @@ -89,7 +88,7 @@ async function loadInitialFloorItems( URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -112,7 +111,7 @@ async function loadInitialFloorItems( const modelBlob = await fetch(modelUrl).then((res) => res.blob()); await storeGLTF(item.modelfileID!, modelBlob); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationPaths); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -138,8 +137,8 @@ async function loadInitialFloorItems( }, ]); - if (item.eventData) { - processEventData(item, setSimulationPaths); + if (item.eventData || item.modelfileID === '67e3db95c2e8f37134526fb2') { + processEventData(item, setSimulationStates); } modelsLoaded++; @@ -155,10 +154,10 @@ async function loadInitialFloorItems( function processLoadedModel( gltf: any, - item: Types.FloorItemType, + item: Types.EventData, itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, - setSimulationPaths: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => void + setSimulationStates: (paths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => void ) { const model = gltf; model.uuid = item.modeluuid; @@ -193,15 +192,15 @@ function processLoadedModel( }, ]); - if (item.eventData || item.modelfileID === '67e3da19c2e8f37134526e6a') { - processEventData(item, setSimulationPaths); + if (item.eventData || item.modelfileID === '67e3db95c2e8f37134526fb2') { + processEventData(item, setSimulationStates); } gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' }); gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' }); } -function processEventData(item: Types.FloorItemType, setSimulationPaths: any) { +function processEventData(item: Types.EventData, setSimulationStates: any) { if (item.eventData?.type === 'Conveyor') { @@ -211,32 +210,45 @@ function processEventData(item: Types.FloorItemType, setSimulationPaths: any) { data.position = item.position; data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ ...(prevEvents || []), data as Types.ConveyorEventsSchema ]); - } else { + + } else if (item.eventData?.type === 'Vehicle') { + + const data: any = item.eventData; + data.modeluuid = item.modeluuid; + data.modelName = item.modelname; + data.position = item.position; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + ...(prevEvents || []), + data as Types.VehicleEventsSchema + ]); + + } else if (item.modelfileID === '67e3db95c2e8f37134526fb2') { const pointUUID = THREE.MathUtils.generateUUID(); - const pointPosition = new THREE.Vector3(0, 1.3, 0); + const pointPosition = new THREE.Vector3(0, 1.75, 0); - const newVehiclePath: Types.VehicleEventsSchema = { + const staticMachine: Types.StaticMachineEventsSchema = { modeluuid: item.modeluuid, modelName: item.modelname, - type: 'Vehicle', - point: { + type: "StaticMachine", + points: { uuid: pointUUID, position: [pointPosition.x, pointPosition.y, pointPosition.z], - actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, - connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, - speed: 2, + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', buffer: 'Inherit', material: 'Inherit', isUsed: false }, + triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, + connections: { source: { modelUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, }, - position: [...item.position], + position: item.position }; - - setSimulationPaths((prevEvents: (Types.VehicleEventsSchema)[]) => [ + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ ...(prevEvents || []), - newVehiclePath as Types.VehicleEventsSchema + staticMachine as Types.StaticMachineEventsSchema ]); } } diff --git a/app/src/modules/scene/controls/selection/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx index dea1da6..44dc2bf 100644 --- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; import { toast } from "react-toastify"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; @@ -10,7 +10,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore() @@ -151,7 +151,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -183,7 +183,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas : [defaultAction], triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers, connections: { - source: { pathUUID: obj.uuid, pointUUID }, + source: { modelUUID: obj.uuid, pointUUID }, targets: [] } }; @@ -224,22 +224,102 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ ...(prevEvents || []), - newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + newEventData as Types.ConveyorEventsSchema ]); socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'Vehicle' && eventData) { + const createVehiclePoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.VehicleEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + type: 'Inherit', + start: {}, + hitCount: 0, + end: {}, + buffer: 0 + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + }, + speed: vehiclePoint?.speed || 1 + }; + }; + + const backendEventData = { + type: 'Vehicle', + points: createVehiclePoint(), + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // 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, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.VehicleEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx index 185ca68..0c22f60 100644 --- a/app/src/modules/scene/controls/selection/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx @@ -1,16 +1,17 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; import { toast } from "react-toastify"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; +import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; 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 { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); @@ -131,7 +132,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -164,7 +165,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb : [defaultAction], triggers: (eventData as Types.ConveyorEventsSchema)?.points[index].triggers, connections: { - source: { pathUUID: obj.uuid, pointUUID }, + source: { modelUUID: newFloorItem.modeluuid, pointUUID }, targets: [] } }; @@ -182,16 +183,16 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb //REST - // await setFloorItemApi( + // setFloorItemApi( // organization, // obj.uuid, // obj.userData.name, + // obj.userData.modelId, // [worldPosition.x, worldPosition.y, worldPosition.z], // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); //SOCKET @@ -205,22 +206,102 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ ...(prevEvents || []), - newEventData as Types.ConveyorEventsSchema | Types.VehicleEventsSchema + newEventData as Types.ConveyorEventsSchema ]); socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'Vehicle' && eventData) { + const createVehiclePoint = () => { + const pointUUID = THREE.MathUtils.generateUUID(); + const vehiclePoint = (eventData as Types.VehicleEventsSchema)?.points; + const hasActions = vehiclePoint?.actions !== undefined; + + const defaultAction = { + uuid: THREE.MathUtils.generateUUID(), + name: 'Action 1', + type: 'Inherit', + start: {}, + hitCount: 0, + end: {}, + buffer: 0 + }; + + return { + uuid: pointUUID, + position: vehiclePoint?.position, + actions: hasActions + ? { + ...vehiclePoint.actions, + uuid: THREE.MathUtils.generateUUID() + } + : defaultAction, + connections: { + source: { modelUUID: obj.uuid, pointUUID }, + targets: [] + }, + speed: vehiclePoint?.speed || 2 + }; + }; + + const backendEventData = { + type: 'Vehicle', + points: createVehiclePoint() + }; + + // API + + // setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + // 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, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + ...(prevEvents || []), + newEventData as Types.VehicleEventsSchema + ]); + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/moveControls.tsx b/app/src/modules/scene/controls/selection/moveControls.tsx index 350b487..2693531 100644 --- a/app/src/modules/scene/controls/selection/moveControls.tsx +++ b/app/src/modules/scene/controls/selection/moveControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; @@ -12,7 +12,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); @@ -180,12 +180,12 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; - if (eventData) { + if (eventData && eventData.type !== 'StaticMachine') { if (eventData.type === 'Conveyor' && eventData) { const backendEventData = { @@ -205,7 +205,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje // obj.userData.modelId, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); //SOCKET @@ -219,17 +219,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -238,7 +238,59 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedEvents; }); - // socket.emit("v2:model-asset:add", data); + socket.emit("v2:model-asset:add", data); + } else if (eventData.type === 'Vehicle' && eventData) { + + const backendEventData = { + type: 'Vehicle', + points: eventData.points + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + //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, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/rotateControls.tsx b/app/src/modules/scene/controls/selection/rotateControls.tsx index 6ad8309..bff9fd6 100644 --- a/app/src/modules/scene/controls/selection/rotateControls.tsx +++ b/app/src/modules/scene/controls/selection/rotateControls.tsx @@ -1,10 +1,11 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; +import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); @@ -12,7 +13,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); @@ -183,12 +184,12 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | undefined = simulationPaths.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; - if (eventData) { + if (eventData && eventData.type !== 'StaticMachine') { if (eventData.type === 'Conveyor' && eventData) { const backendEventData = { @@ -197,18 +198,18 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo speed: (eventData as Types.ConveyorEventsSchema)?.speed }; - //REST + // REST // await setFloorItemApi( // organization, // obj.uuid, // obj.userData.name, + // obj.userData.modelId, // [worldPosition.x, worldPosition.y, worldPosition.z], // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, // false, // true, - // backendEventData + // { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed } // ); //SOCKET @@ -222,17 +223,17 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, isVisible: true, - eventData: backendEventData, + eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }, socketId: socket.id, }; - const newEventData: any = backendEventData; + const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }; newEventData.modeluuid = newFloorItem.modeluuid; newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -241,11 +242,63 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedEvents; }); - // socket.emit("v2:model-asset:add", data); + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'Vehicle' && eventData) { + + const backendEventData = { + type: 'Vehicle', + points: eventData.points + }; + + // REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // obj.userData.modelId, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // false, + // true, + // { type: backendEventData.type, points: backendEventData.points } + // ); + + //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, + eventData: { type: backendEventData.type, points: backendEventData.points }, + socketId: socket.id, + }; + + const newEventData: any = { type: backendEventData.type, points: backendEventData.points }; + newEventData.modeluuid = newFloorItem.modeluuid; + newEventData.modelName = newFloorItem.modelname; + newEventData.position = newFloorItem.position; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + } } else { - //REST // await setFloorItemApi( diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx index fd7e19e..8a464d5 100644 --- a/app/src/modules/scene/controls/selection/selectionControls.tsx +++ b/app/src/modules/scene/controls/selection/selectionControls.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; import { SelectionHelper } from "./selectionHelper"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSimulationPaths, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store"; import BoundingBox from "./boundingBoxHelper"; import { toast } from "react-toastify"; // import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi'; @@ -20,7 +20,7 @@ const SelectionControls: React.FC = () => { const itemsGroupRef = useRef(undefined); const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); - const { setSimulationPaths } = useSimulationPaths(); + const { setSimulationStates } = useSimulationStates(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const [movedObjects, setMovedObjects] = useState([]); const [rotatedObjects, setRotatedObjects] = useState([]); @@ -240,7 +240,7 @@ const SelectionControls: React.FC = () => { } }); - setSimulationPaths((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== selectedMesh.uuid); return updatedEvents; }); diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index ffa020e..30d8a8f 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -91,7 +91,7 @@ export default function PostProcessing() { )} {selectedActionSphere && ( (); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. @@ -367,7 +367,9 @@ export default function World() { /> {/* */} - {activeModule === "simulation" && } + + + ); } diff --git a/app/src/modules/simulation/behaviour/behaviour.tsx b/app/src/modules/simulation/behaviour/behaviour.tsx index 27c791f..0ddc3aa 100644 --- a/app/src/modules/simulation/behaviour/behaviour.tsx +++ b/app/src/modules/simulation/behaviour/behaviour.tsx @@ -1,14 +1,14 @@ -import { useFloorItems, useSimulationPaths } from '../../../store/store'; +import { useFloorItems, useSimulationStates } from '../../../store/store'; import * as THREE from 'three'; import * as Types from '../../../types/world/worldTypes'; import { useEffect } from 'react'; function Behaviour() { - const { setSimulationPaths } = useSimulationPaths(); + const { setSimulationStates } = useSimulationStates(); const { floorItems } = useFloorItems(); useEffect(() => { - const newPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] = []; + const newPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[] = []; // floorItems.forEach((item: Types.FloorItemType) => { // if (item.modelfileID === "672a090f80d91ac979f4d0bd") { @@ -31,7 +31,7 @@ function Behaviour() { // rotation: [0, 0, 0], // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], // triggers: [], - // connections: { source: { pathUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: point1UUID }, targets: [] }, // }, // { // uuid: middlePointUUID, @@ -39,7 +39,7 @@ function Behaviour() { // rotation: [0, 0, 0], // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], // triggers: [], - // connections: { source: { pathUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: middlePointUUID }, targets: [] }, // }, // { // uuid: point2UUID, @@ -47,7 +47,7 @@ function Behaviour() { // rotation: [0, 0, 0], // actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Inherit', material: 'Inherit', delay: 'Inherit', spawnInterval: 'Inherit', isUsed: true }], // triggers: [], - // connections: { source: { pathUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: point2UUID }, targets: [] }, // }, // ], // position: [...item.position], @@ -68,7 +68,7 @@ function Behaviour() { // uuid: pointUUID, // position: [pointPosition.x, pointPosition.y, pointPosition.z], // actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: {}, hitCount: 1, end: {}, buffer: 0 }, - // connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, + // connections: { source: { modelUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, // speed: 2, // }, // position: [...item.position], @@ -78,7 +78,7 @@ function Behaviour() { // } // }); - // setSimulationPaths(newPaths); + // setSimulationStates(newPaths); // console.log('floorItems: ', floorItems); }, [floorItems]); diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index 7352932..8a295be 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -3,17 +3,21 @@ import React, { useEffect, useState } from 'react'; import * as THREE from 'three'; import * as Types from '../../../types/world/worldTypes'; import { QuadraticBezierLine } from '@react-three/drei'; -import { useIsConnecting, useSimulationPaths } from '../../../store/store'; +import { useIsConnecting, useSimulationStates, useSocketStore } from '../../../store/store'; import useModuleStore from '../../../store/useModuleStore'; +import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; +import { setEventApi } from '../../../services/factoryBuilder/assest/floorAsset/setEventsApt'; function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject }) { const { activeModule } = useModuleStore(); const { gl, raycaster, scene, pointer, camera } = useThree(); const { setIsConnecting } = useIsConnecting(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { isPlaying } = usePlayButtonStore(); + const { socket } = useSocketStore(); const [firstSelected, setFirstSelected] = useState<{ - pathUUID: string; + modelUUID: string; sphereUUID: string; position: THREE.Vector3; isCorner: boolean; @@ -22,26 +26,26 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const [helperlineColor, setHelperLineColor] = useState('red'); const updatePathConnections = ( - fromPathUUID: string, + fromModelUUID: string, fromPointUUID: string, - toPathUUID: string, + toModelUUID: string, toPointUUID: string ) => { - const updatedPaths = simulationPaths.map(path => { + const updatedPaths = simulationStates.map(path => { if (path.type === 'Conveyor') { - if (path.modeluuid === fromPathUUID) { + if (path.modeluuid === fromModelUUID) { return { ...path, points: path.points.map(point => { if (point.uuid === fromPointUUID) { const newTarget = { - pathUUID: toPathUUID, + modelUUID: toModelUUID, pointUUID: toPointUUID }; const existingTargets = point.connections.targets || []; if (!existingTargets.some(target => - target.pathUUID === newTarget.pathUUID && + target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID )) { return { @@ -57,19 +61,19 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }) }; } - else if (path.modeluuid === toPathUUID) { + else if (path.modeluuid === toModelUUID) { return { ...path, points: path.points.map(point => { if (point.uuid === toPointUUID) { const reverseTarget = { - pathUUID: fromPathUUID, + modelUUID: fromModelUUID, pointUUID: fromPointUUID }; const existingTargets = point.connections.targets || []; if (!existingTargets.some(target => - target.pathUUID === reverseTarget.pathUUID && + target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID )) { return { @@ -86,18 +90,17 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }; } } - // In the updatePathConnections function, modify the Vehicle handling section: else if (path.type === 'Vehicle') { // Handle outgoing connections from Vehicle - if (path.modeluuid === fromPathUUID && path.point.uuid === fromPointUUID) { + if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { const newTarget = { - pathUUID: toPathUUID, + modelUUID: toModelUUID, pointUUID: toPointUUID }; - const existingTargets = path.point.connections.targets || []; + const existingTargets = path.points.connections.targets || []; // Check if target is a Conveyor - const toPath = simulationPaths.find(p => p.modeluuid === toPathUUID); + const toPath = simulationStates.find(p => p.modeluuid === toModelUUID); if (toPath?.type !== 'Conveyor') { console.log("Vehicle can only connect to Conveyors"); return path; @@ -110,15 +113,15 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } if (!existingTargets.some(target => - target.pathUUID === newTarget.pathUUID && + target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID )) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, connections: { - ...path.point.connections, + ...path.points.connections, targets: [...existingTargets, newTarget] } } @@ -126,15 +129,15 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } } // Handle incoming connections to Vehicle - else if (path.modeluuid === toPathUUID && path.point.uuid === toPointUUID) { + else if (path.modeluuid === toModelUUID && path.points.uuid === toPointUUID) { const reverseTarget = { - pathUUID: fromPathUUID, + modelUUID: fromModelUUID, pointUUID: fromPointUUID }; - const existingTargets = path.point.connections.targets || []; + const existingTargets = path.points.connections.targets || []; // Check if source is a Conveyor - const fromPath = simulationPaths.find(p => p.modeluuid === fromPathUUID); + const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); if (fromPath?.type !== 'Conveyor') { console.log("Vehicle can only connect to Conveyors"); return path; @@ -147,15 +150,15 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } if (!existingTargets.some(target => - target.pathUUID === reverseTarget.pathUUID && + target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID )) { return { ...path, - point: { - ...path.point, + points: { + ...path.points, connections: { - ...path.point.connections, + ...path.points.connections, targets: [...existingTargets, reverseTarget] } } @@ -164,14 +167,139 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } return path; } + // else if (path.type === 'StaticMachine') { + // if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { + // const newTarget = { + // modelUUID: toModelUUID, + // pointUUID: toPointUUID + // }; + // const existingTargets = path.points.connections.targets || []; + + // // Check if target is an ArmBot + // const toPath = simulationStates.find(p => p.modeluuid === toModelUUID); + // if (toPath?.type !== 'ArmBot') { + // console.log("StaticMachine can only connect to ArmBot"); + // return path; + // } + + // // Check if already has a connection + // if (existingTargets.length >= 1) { + // console.log("StaticMachine can have only one connection"); + // return path; + // } + + // if (!existingTargets.some(target => + // target.modelUUID === newTarget.modelUUID && + // target.pointUUID === newTarget.pointUUID + // )) { + // return { + // ...path, + // points: { + // ...path.points, + // connections: { + // ...path.points.connections, + // targets: [...existingTargets, newTarget] + // } + // } + // }; + // } + // } + // // Handle incoming connections to StaticMachine + // else if (path.modeluuid === toModelUUID && path.points.uuid === toPointUUID) { + // const reverseTarget = { + // modelUUID: fromModelUUID, + // pointUUID: fromPointUUID + // }; + // const existingTargets = path.points.connections.targets || []; + + // // Check if source is an ArmBot + // const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); + // if (fromPath?.type !== 'ArmBot') { + // console.log("StaticMachine can only connect to ArmBot"); + // return path; + // } + + // // Check if already has a connection + // if (existingTargets.length >= 1) { + // console.log("StaticMachine can have only one connection"); + // return path; + // } + + // if (!existingTargets.some(target => + // target.modelUUID === reverseTarget.modelUUID && + // target.pointUUID === reverseTarget.pointUUID + // )) { + // return { + // ...path, + // points: { + // ...path.points, + // connections: { + // ...path.points.connections, + // targets: [...existingTargets, reverseTarget] + // } + // } + // }; + // } + // } + // return path; + // } return path; }); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); + + const updatedPathDetails = updatedPaths.filter(path => + path.modeluuid === fromModelUUID || path.modeluuid === toModelUUID + ); + + updateBackend(updatedPathDetails); }; - const handleAddConnection = (fromPathUUID: string, fromUUID: string, toPathUUID: string, toUUID: string) => { - updatePathConnections(fromPathUUID, fromUUID, toPathUUID, toUUID); + const updateBackend = async (updatedPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + if (updatedPaths.length === 0) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + updatedPaths.forEach(async (updatedPath) => { + if (updatedPath.type === 'Conveyor') { + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } else if (updatedPath.type === 'Vehicle') { + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "Vehicle", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "Vehicle", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } + }) + + } + + const handleAddConnection = (fromModelUUID: string, fromUUID: string, toModelUUID: string, toUUID: string) => { + updatePathConnections(fromModelUUID, fromUUID, toModelUUID, toUUID); setFirstSelected(null); setCurrentLine(null); setIsConnecting(false); @@ -208,25 +336,25 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const intersected = intersects[0].object; if (intersected.name.includes("events-sphere")) { - const pathUUID = intersected.userData.path.modeluuid; + const modelUUID = intersected.userData.path.modeluuid; const sphereUUID = intersected.uuid; const worldPosition = new THREE.Vector3(); intersected.getWorldPosition(worldPosition); let isStartOrEnd = false; - if (intersected.userData.path.points) { + if (intersected.userData.path.points && intersected.userData.path.points.length > 1) { isStartOrEnd = intersected.userData.path.points.length > 0 && ( sphereUUID === intersected.userData.path.points[0].uuid || sphereUUID === intersected.userData.path.points[intersected.userData.path.points.length - 1].uuid ); - } else if (intersected.userData.path.point) { - isStartOrEnd = sphereUUID === intersected.userData.path.point.uuid; + } else if (intersected.userData.path.points) { + isStartOrEnd = sphereUUID === intersected.userData.path.points.uuid; } - if (pathUUID) { - const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected?.pathUUID); - const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID); + if (modelUUID) { + const firstPath = simulationStates.find(p => p.modeluuid === firstSelected?.modelUUID); + const secondPath = simulationStates.find(p => p.modeluuid === modelUUID); // Prevent vehicle-to-vehicle connections if (firstPath && secondPath && firstPath.type === 'Vehicle' && secondPath.type === 'Vehicle') { @@ -238,23 +366,23 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec if (firstPath && secondPath && firstPath.type === 'Conveyor' && secondPath.type === 'Conveyor' && - !firstSelected?.isCorner) { - console.log("Conveyor middle points can only connect to non-conveyor paths"); + (!firstSelected?.isCorner || !isStartOrEnd)) { + console.log("Conveyor connections must be between start/end points"); return; } // Check if this specific connection already exists const isDuplicateConnection = firstSelected - ? simulationPaths.some(path => { - if (path.modeluuid === firstSelected.pathUUID) { + ? simulationStates.some(path => { + if (path.modeluuid === firstSelected.modelUUID) { if (path.type === 'Conveyor') { const point = path.points.find(p => p.uuid === firstSelected.sphereUUID); return point?.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } else if (path.type === 'Vehicle') { - return path.point.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + return path.points.connections.targets.some(t => + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } } @@ -269,7 +397,8 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // For Vehicles, check if they're already connected to anything if (intersected.userData.path.type === 'Vehicle') { - const vehicleConnections = intersected.userData.path.point.connections.targets.length; + console.log('intersected: ', intersected); + const vehicleConnections = intersected.userData.path.points.connections.targets.length; if (vehicleConnections >= 1) { console.log("Vehicle can only have one connection"); return; @@ -278,7 +407,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // For non-Vehicle paths, check if already connected if (intersected.userData.path.type !== 'Vehicle') { - const isAlreadyConnected = simulationPaths.some(path => { + const isAlreadyConnected = simulationStates.some(path => { if (path.type === 'Conveyor') { return path.points.some(point => point.uuid === sphereUUID && @@ -303,7 +432,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } // Prevent same-path connections - if (firstSelected.pathUUID === pathUUID) { + if (firstSelected.modelUUID === modelUUID) { console.log("Cannot connect spheres on the same path."); return; } @@ -316,15 +445,15 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // All checks passed - make the connection handleAddConnection( - firstSelected.pathUUID, + firstSelected.modelUUID, firstSelected.sphereUUID, - pathUUID, + modelUUID, sphereUUID ); } else { // First selection - just store it setFirstSelected({ - pathUUID, + modelUUID, sphereUUID, position: worldPosition, isCorner: isStartOrEnd @@ -358,7 +487,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener("contextmenu", onContextMenu); }; - }, [camera, scene, raycaster, firstSelected, simulationPaths]); + }, [camera, scene, raycaster, firstSelected, simulationStates]); useFrame(() => { if (firstSelected) { @@ -372,7 +501,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec ); let point: THREE.Vector3 | null = null; - let snappedSphere: { sphereUUID: string, position: THREE.Vector3, pathUUID: string, isCorner: boolean } | null = null; + let snappedSphere: { sphereUUID: string, position: THREE.Vector3, modelUUID: string, isCorner: boolean } | null = null; let isInvalidConnection = false; if (intersects.length > 0) { @@ -392,10 +521,10 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const spherePosition = new THREE.Vector3(); sphere.getWorldPosition(spherePosition); const pathData = sphere.userData.path; - const pathUUID = pathData.modeluuid; + const modelUUID = pathData.modeluuid; - const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected.pathUUID); - const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID); + const firstPath = simulationStates.find(p => p.modeluuid === firstSelected.modelUUID); + const secondPath = simulationStates.find(p => p.modeluuid === modelUUID); const isVehicleToVehicle = firstPath?.type === 'Vehicle' && secondPath?.type === 'Vehicle'; // Inside the useFrame hook, where we check for snapped spheres: @@ -410,16 +539,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec !firstSelected.isCorner); // Check for duplicate connection (regardless of path type) - const isDuplicateConnection = simulationPaths.some(path => { - if (path.modeluuid === firstSelected.pathUUID) { + const isDuplicateConnection = simulationStates.some(path => { + if (path.modeluuid === firstSelected.modelUUID) { if (path.type === 'Conveyor') { const point = path.points.find(p => p.uuid === firstSelected.sphereUUID); return point?.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } else if (path.type === 'Vehicle') { - return path.point.connections.targets.some(t => - t.pathUUID === pathUUID && t.pointUUID === sphereUUID + return path.points.connections.targets.some(t => + t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } } @@ -428,7 +557,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // For non-Vehicle paths, check if already connected const isNonVehicleAlreadyConnected = pathData.type !== 'Vehicle' && - simulationPaths.some(path => { + simulationStates.some(path => { if (path.type === 'Conveyor') { return path.points.some(point => point.uuid === sphereUUID && @@ -440,7 +569,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec // Check vehicle connection rules const isVehicleAtMaxConnections = pathData.type === 'Vehicle' && - pathData.point.connections.targets.length >= 1; + pathData.points.connections.targets.length >= 1; const isVehicleConnectingToNonConveyor = (firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') || (secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor'); @@ -452,13 +581,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec !isVehicleAtMaxConnections && !isVehicleConnectingToNonConveyor && firstSelected.sphereUUID !== sphereUUID && - firstSelected.pathUUID !== pathUUID && - (firstSelected.isCorner || isConnectable) + firstSelected.modelUUID !== modelUUID && + (firstSelected.isCorner || isConnectable) && + !(firstPath?.type === 'Conveyor' && + pathData.type === 'Conveyor' && + !(firstSelected.isCorner && isConnectable)) ) { snappedSphere = { sphereUUID, position: spherePosition, - pathUUID, + modelUUID, isCorner: isConnectable }; } else { @@ -501,12 +633,12 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }); return ( - <> - {simulationPaths.flatMap(path => { + + {simulationStates.flatMap(path => { if (path.type === 'Conveyor') { return path.points.flatMap(point => point.connections.targets.map((target, index) => { - const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID); + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); if (targetPath?.type === 'Vehicle') return null; const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', point.uuid); @@ -545,8 +677,8 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }) ); } else if (path.type === 'Vehicle') { - return path.point.connections.targets.map((target, index) => { - const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.point.uuid); + return path.points.connections.targets.map((target, index) => { + const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.points.uuid); const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID); if (fromSphere && toSphere) { @@ -566,7 +698,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return ( )} - + ); } diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx index dd531a9..62bb79e 100644 --- a/app/src/modules/simulation/path/pathCreation.tsx +++ b/app/src/modules/simulation/path/pathCreation.tsx @@ -3,362 +3,340 @@ import * as Types from "../../../types/world/worldTypes"; import { useRef, useState, useEffect, useMemo } from "react"; import { Sphere, TransformControls } from "@react-three/drei"; import { - useEditingPoint, - useEyeDropMode, - useIsConnecting, - usePreviewPosition, - useRenderDistance, - useSelectedActionSphere, - useSelectedPath, - useSimulationPaths, + useEditingPoint, + useEyeDropMode, + useIsConnecting, + usePreviewPosition, + useRenderDistance, + useSelectedActionSphere, + useSelectedPath, + useSimulationStates, } from "../../../store/store"; import { useFrame, useThree } from "@react-three/fiber"; import { useSubModuleStore } from "../../../store/useModuleStore"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; -function PathCreation({ - pathsGroupRef, -}: { - pathsGroupRef: React.MutableRefObject; -}) { - const { isPlaying } = usePlayButtonStore(); - const { renderDistance } = useRenderDistance(); - const { setSubModule } = useSubModuleStore(); - const { setSelectedActionSphere, selectedActionSphere } = - useSelectedActionSphere(); - const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); - const { editingPoint, setEditingPoint } = useEditingPoint(); - const { previewPosition, setPreviewPosition } = usePreviewPosition(); - const { raycaster, camera, pointer, gl } = useThree(); - const plane = useMemo( - () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), - [] - ); - const { setSelectedPath } = useSelectedPath(); - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); - const { isConnecting } = useIsConnecting(); +function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObject; }) { + const { isPlaying } = usePlayButtonStore(); + const { renderDistance } = useRenderDistance(); + const { setSubModule } = useSubModuleStore(); + const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere(); + const { eyeDropMode, setEyeDropMode } = useEyeDropMode(); + const { editingPoint, setEditingPoint } = useEditingPoint(); + const { previewPosition, setPreviewPosition } = usePreviewPosition(); + const { raycaster, camera, pointer, gl } = useThree(); + const { setSelectedPath } = useSelectedPath(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { isConnecting } = useIsConnecting(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); - const groupRefs = useRef<{ [key: string]: THREE.Group }>({}); - const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); - const isMovingRef = useRef(false); - const transformRef = useRef(null); - const [transformMode, setTransformMode] = useState< - "translate" | "rotate" | null - >(null); + const groupRefs = useRef<{ [key: string]: THREE.Group }>({}); + const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); + const isMovingRef = useRef(false); + const transformRef = useRef(null); + const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); - useEffect(() => { - setTransformMode(null); - const handleKeyDown = (e: KeyboardEvent) => { - if (!selectedActionSphere) return; - if (e.key === "g") { - setTransformMode((prev) => (prev === "translate" ? null : "translate")); - } - if (e.key === "r") { - setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); - } - }; + useEffect(() => { + setTransformMode(null); + const handleKeyDown = (e: KeyboardEvent) => { + if (!selectedActionSphere) return; + if (e.key === "g") { + setTransformMode((prev) => (prev === "translate" ? null : "translate")); + } + if (e.key === "r") { + setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); + } + }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [selectedActionSphere]); + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [selectedActionSphere]); - useFrame(() => { - Object.values(groupRefs.current).forEach((group) => { - if (group) { - const distance = new THREE.Vector3( - ...group.position.toArray() - ).distanceTo(camera.position); - group.visible = ((distance <= renderDistance) && !isPlaying); - } - }); - }); + useFrame(() => { + Object.values(groupRefs.current).forEach((group) => { + if (group) { + const distance = new THREE.Vector3( + ...group.position.toArray() + ).distanceTo(camera.position); + group.visible = ((distance <= renderDistance) && !isPlaying); + } + }); + }); - const updateSimulationPaths = () => { - if (!selectedActionSphere) return; + const updateSimulationPaths = () => { + if (!selectedActionSphere) return; - const updatedPaths = simulationPaths.map((path) => { - if (path.type === "Conveyor") { - return { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.point.uuid - ? { - ...point, - position: [ - selectedActionSphere.point.position.x, - selectedActionSphere.point.position.y, - selectedActionSphere.point.position.z, - ], - rotation: [ - selectedActionSphere.point.rotation.x, - selectedActionSphere.point.rotation.y, - selectedActionSphere.point.rotation.z, - ], - } - : point - ), - }; - } - return path; - }) as Types.ConveyorEventsSchema[]; + const updatedPaths = simulationStates.map((path) => { + if (path.type === "Conveyor") { + return { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + position: [ + selectedActionSphere.points.position.x, + selectedActionSphere.points.position.y, + selectedActionSphere.points.position.z, + ], + rotation: [ + selectedActionSphere.points.rotation.x, + selectedActionSphere.points.rotation.y, + selectedActionSphere.points.rotation.z, + ], + } + : point + ), + }; + } else { + return path; + } + }) as Types.ConveyorEventsSchema[]; - setSimulationPaths(updatedPaths); - }; + const updatedPath = updatedPaths.find( + (path) => path.type === "Conveyor" && path.points.some((point) => point.uuid === selectedActionSphere.points.uuid) + ); - useFrame(() => { - if (eyeDropMode) { - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + // console.log("Updated Path:", updatedPath); - if (point) { - setPreviewPosition({ x: point.x, y: point.z }); - } - } else { - setPreviewPosition(null); - } - }); + setSimulationStates(updatedPaths); + }; - useEffect(() => { - if (!camera) return; - const canvasElement = gl.domElement; - canvasElement.tabIndex = 0; + useFrame(() => { + if (eyeDropMode) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - const onPointerDown = () => { - isMovingRef.current = false; - }; + if (point) { + setPreviewPosition({ x: point.x, y: point.z }); + } + } else { + setPreviewPosition(null); + } + }); - const onPointerMove = () => { - isMovingRef.current = true; - }; + useEffect(() => { + if (!camera) return; + const canvasElement = gl.domElement; + canvasElement.tabIndex = 0; - const onPointerUp = (event: PointerEvent) => { - if ( - !isMovingRef.current && - eyeDropMode && - event.button === 0 && - previewPosition - ) { - event.preventDefault(); - if (editingPoint) { - handlePointUpdate(editingPoint, previewPosition.x, previewPosition.y); - setEditingPoint(null); - setEyeDropMode(false); - } - } - }; + const onPointerDown = () => { + isMovingRef.current = false; + }; - if (eyeDropMode) { - canvasElement.addEventListener("pointerdown", onPointerDown); - canvasElement.addEventListener("pointermove", onPointerMove); - canvasElement.addEventListener("pointerup", onPointerUp); - } + const onPointerMove = () => { + isMovingRef.current = true; + }; - return () => { - canvasElement.removeEventListener("pointerdown", onPointerDown); - canvasElement.removeEventListener("pointermove", onPointerMove); - canvasElement.removeEventListener("pointerup", onPointerUp); - }; - }, [eyeDropMode, editingPoint, previewPosition]); + const onPointerUp = (event: PointerEvent) => { + if ( + !isMovingRef.current && + eyeDropMode && + event.button === 0 && + previewPosition + ) { + event.preventDefault(); + if (editingPoint) { + handlePointUpdate(editingPoint, previewPosition.x, previewPosition.y); + setEditingPoint(null); + setEyeDropMode(false); + } + } + }; - const handlePointUpdate = ( - pointType: "start" | "end", - x: number, - z: number - ) => { - if (!selectedActionSphere?.point?.uuid) return; + if (eyeDropMode) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + } - const updatedPaths = simulationPaths.map((path) => { - if ( - path.type === "Vehicle" && - path.point.uuid === selectedActionSphere.point.uuid - ) { - return { - ...path, - point: { - ...path.point, - actions: { - ...path.point.actions, - [pointType]: { - ...path.point.actions[pointType], - x: x, - y: z, - }, - }, - }, - }; - } - return path; - }); + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + }; + }, [eyeDropMode, editingPoint, previewPosition]); - setSimulationPaths(updatedPaths); - }; + const updateBackend = async (updatedPath: Types.VehicleEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + await setEventApi( + organization, + updatedPath.modeluuid, + { type: "Vehicle", points: updatedPath.points } + ); + } - return ( - - {simulationPaths.map((path) => { - if (path.type === "Conveyor") { - const points = path.points.map( - (point) => new THREE.Vector3(...point.position) - ); + const handlePointUpdate = (pointType: "start" | "end", x: number, z: number) => { + if (!selectedActionSphere?.points?.uuid) return; + const updatedPaths = simulationStates.map((path) => { - return ( - (groupRefs.current[path.modeluuid] = el!)} - position={path.position} - rotation={path.rotation} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedPath({ - path, - group: groupRefs.current[path.modeluuid], - }); - setSelectedActionSphere(null); - setTransformMode(null); - setSubModule("mechanics"); - }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSelectedPath(null); - setSubModule("properties"); - }} - > - {path.points.map((point, index) => ( - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedActionSphere({ - path, - point: sphereRefs.current[point.uuid], - }); - setSubModule("mechanics"); - setSelectedPath(null); - }} - userData={{ point, path }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSubModule("properties"); - setSelectedActionSphere(null); - }} - > - - - ))} + if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) { + return { + ...path, + points: { + ...path.points, + actions: { + ...path.points.actions, + [pointType]: { ...path.points.actions[pointType], x: x, y: z, }, + }, + }, + }; + } + return path; + }); - {points.slice(0, -1).map((point, index) => { - const nextPoint = points[index + 1]; - const segmentCurve = new THREE.CatmullRomCurve3([ - point, - nextPoint, - ]); - const tubeGeometry = new THREE.TubeGeometry( - segmentCurve, - 20, - 0.1, - 16, - false - ); + const updatedPath = updatedPaths.find((path): path is Types.VehicleEventsSchema => path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid); + updateBackend(updatedPath); - return ( - - - - ); - })} - - ); - } else if (path.type === "Vehicle") { - return ( - (groupRefs.current[path.modeluuid] = el!)} - position={path.position} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedPath({ - path, - group: groupRefs.current[path.modeluuid], - }); - setSelectedActionSphere(null); - setTransformMode(null); - setSubModule("mechanics"); - }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSelectedPath(null); - setSubModule("properties"); - }} - > - (sphereRefs.current[path.point.uuid] = el!)} - onClick={(e) => { - if (isConnecting || eyeDropMode) return; - e.stopPropagation(); - setSelectedActionSphere({ - path, - point: sphereRefs.current[path.point.uuid], - }); - setSubModule("mechanics"); - setSelectedPath(null); - }} - userData={{ point: path.point, path }} - onPointerMissed={() => { - if (eyeDropMode) return; - setSubModule("properties"); - setSelectedActionSphere(null); - }} - > - - - - ); - } - return null; - })} + setSimulationStates(updatedPaths); + }; - {selectedActionSphere && transformMode && ( - - )} - - ); + return ( + + {simulationStates.map((path) => { + if (path.type === "Conveyor") { + const points = path.points.map( + (point) => new THREE.Vector3(...point.position) + ); + + return ( + (groupRefs.current[path.modeluuid] = el!)} + position={path.position} + rotation={path.rotation} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedPath({ + path, + group: groupRefs.current[path.modeluuid], + }); + setSelectedActionSphere(null); + setTransformMode(null); + setSubModule("mechanics"); + }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSelectedPath(null); + setSubModule("properties"); + }} + > + {path.points.map((point, index) => ( + (sphereRefs.current[point.uuid] = el!)} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedActionSphere({ + path, + points: sphereRefs.current[point.uuid], + }); + setSubModule("mechanics"); + setSelectedPath(null); + }} + userData={{ points, path }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSubModule("properties"); + setSelectedActionSphere(null); + }} + > + + + ))} + + {points.slice(0, -1).map((point, index) => { + const nextPoint = points[index + 1]; + const segmentCurve = new THREE.CatmullRomCurve3([point, nextPoint,]); + const tubeGeometry = new THREE.TubeGeometry(segmentCurve, 20, 0.1, 16, false); + + return ( + + + + ); + })} + + ); + } else if (path.type === "Vehicle" || path.type === "StaticMachine") { + return ( + (groupRefs.current[path.modeluuid] = el!)} + position={path.position} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedPath({ + path, + group: groupRefs.current[path.modeluuid], + }); + setSelectedActionSphere(null); + setTransformMode(null); + setSubModule("mechanics"); + }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSelectedPath(null); + setSubModule("properties"); + }} + > + (sphereRefs.current[path.points.uuid] = el!)} + onClick={(e) => { + if (isConnecting || eyeDropMode) return; + e.stopPropagation(); + setSelectedActionSphere({ + path, + points: sphereRefs.current[path.points.uuid], + }); + setSubModule("mechanics"); + setSelectedPath(null); + }} + userData={{ points: path.points, path }} + onPointerMissed={() => { + if (eyeDropMode) return; + setSubModule("properties"); + setSelectedActionSphere(null); + }} + > + + + + ); + } + return null; + })} + + {selectedActionSphere && transformMode && ( + + )} + + ); } export default PathCreation; diff --git a/app/src/modules/simulation/process/animation.Worker.js b/app/src/modules/simulation/process/animation.Worker.js deleted file mode 100644 index faa6587..0000000 --- a/app/src/modules/simulation/process/animation.Worker.js +++ /dev/null @@ -1,916 +0,0 @@ -// // animation-worker.js -// // This web worker handles animation calculations off the main thread - -// /* eslint-disable no-restricted-globals */ -// // The above disables the ESLint rule for this file since 'self' is valid in web workers - -// // Store process data, animation states, and objects -// let processes = []; -// let animationStates = {}; -// let lastTimestamp = 0; - -// // Message handler for communication with main thread -// self.onmessage = function (event) { -// const { type, data } = event.data; - -// switch (type) { -// case "initialize": -// processes = data.processes; -// initializeAnimationStates(); -// break; - -// case "update": -// const { timestamp, isPlaying } = data; -// if (isPlaying) { -// const delta = (timestamp - lastTimestamp) / 1000; // Convert to seconds -// updateAnimations(delta, timestamp); -// } -// lastTimestamp = timestamp; -// break; - -// case "reset": -// resetAnimations(); -// break; - -// case "togglePlay": -// // If resuming from pause, recalculate the time delta -// lastTimestamp = data.timestamp; -// break; -// } -// }; - -// // Initialize animation states for all processes -// function initializeAnimationStates() { -// animationStates = {}; - -// processes.forEach((process) => { -// animationStates[process.id] = { -// spawnedObjects: {}, -// nextSpawnTime: 0, -// objectIdCounter: 0, -// }; -// }); - -// // Send initial states back to main thread -// self.postMessage({ -// type: "statesInitialized", -// data: { animationStates }, -// }); -// } - -// // Reset all animations -// function resetAnimations() { -// initializeAnimationStates(); -// } - -// // Find spawn point in a process -// function findSpawnPoint(process) { -// for (const path of process.paths || []) { -// for (const point of path.points || []) { -// const spawnAction = point.actions?.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (spawnAction) { -// return { point, path }; -// } -// } -// } -// return null; -// } - -// // Create a new spawned object with proper initial position -// function createSpawnedObject(process, spawnPoint, currentTime, materialType) { -// // Extract spawn position from the actual spawn point -// const position = spawnPoint.point.position -// ? [...spawnPoint.point.position] -// : [0, 0, 0]; - -// // Get the path position and add it to the spawn point position -// const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0]; -// const absolutePosition = [ -// position[0] + pathPosition[0], -// position[1] + pathPosition[1], -// position[2] + pathPosition[2], -// ]; - -// return { -// id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`, -// position: absolutePosition, -// state: { -// currentIndex: 0, -// progress: 0, -// isAnimating: true, -// speed: process.speed || 1, -// isDelaying: false, -// delayStartTime: 0, -// currentDelayDuration: 0, -// delayComplete: false, -// currentPathIndex: 0, -// // Store the spawn point index to start animation from correct path point -// spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point), -// }, -// visible: true, -// materialType: materialType || "Default", -// spawnTime: currentTime, -// }; -// } - -// // Get the index of a point within the process animation path -// function getPointIndexInProcess(process, point) { -// if (!process.paths) return 0; - -// let cumulativePoints = 0; -// for (const path of process.paths) { -// for (let i = 0; i < (path.points?.length || 0); i++) { -// if (path.points[i].uuid === point.uuid) { -// return cumulativePoints + i; -// } -// } -// cumulativePoints += path.points?.length || 0; -// } - -// return 0; -// } - -// // Get point data for current animation index -// function getPointDataForAnimationIndex(process, index) { -// if (!process.paths) return null; - -// let cumulativePoints = 0; -// for (const path of process.paths) { -// const pointCount = path.points?.length || 0; - -// if (index < cumulativePoints + pointCount) { -// const pointIndex = index - cumulativePoints; -// return path.points?.[pointIndex] || null; -// } - -// cumulativePoints += pointCount; -// } - -// return null; -// } - -// // Convert process paths to Vector3 format -// function getProcessPath(process) { -// return process.animationPath?.map((p) => ({ x: p.x, y: p.y, z: p.z })) || []; -// } - -// // Handle material swap for an object -// function handleMaterialSwap(processId, objectId, materialType) { -// const processState = animationStates[processId]; -// if (!processState || !processState.spawnedObjects[objectId]) return; - -// processState.spawnedObjects[objectId].materialType = materialType; - -// // Notify main thread about material change -// self.postMessage({ -// type: "materialChanged", -// data: { -// processId, -// objectId, -// materialType, -// }, -// }); -// } - -// // Handle point actions for an object -// function handlePointActions(processId, objectId, actions = [], currentTime) { -// let shouldStopAnimation = false; -// const processState = animationStates[processId]; - -// if (!processState || !processState.spawnedObjects[objectId]) return false; - -// const objectState = processState.spawnedObjects[objectId]; - -// actions.forEach((action) => { -// if (!action.isUsed) return; - -// switch (action.type) { -// case "Delay": -// if (objectState.state.isDelaying) return; - -// const delayDuration = -// typeof action.delay === "number" -// ? action.delay -// : parseFloat(action.delay || "0"); - -// if (delayDuration > 0) { -// objectState.state.isDelaying = true; -// objectState.state.delayStartTime = currentTime; -// objectState.state.currentDelayDuration = delayDuration; -// objectState.state.delayComplete = false; -// shouldStopAnimation = true; -// } -// break; - -// case "Despawn": -// delete processState.spawnedObjects[objectId]; -// shouldStopAnimation = true; - -// // Notify main thread about despawn -// self.postMessage({ -// type: "objectDespawned", -// data: { -// processId, -// objectId, -// }, -// }); -// break; - -// case "Swap": -// if (action.material) { -// handleMaterialSwap(processId, objectId, action.material); -// } -// break; - -// default: -// break; -// } -// }); - -// return shouldStopAnimation; -// } - -// // Check if point has non-inherit actions -// function hasNonInheritActions(actions = []) { -// return actions.some((action) => action.isUsed && action.type !== "Inherit"); -// } - -// // Calculate vector lerp (linear interpolation) -// function lerpVectors(v1, v2, alpha) { -// return { -// x: v1.x + (v2.x - v1.x) * alpha, -// y: v1.y + (v2.y - v1.y) * alpha, -// z: v1.z + (v2.z - v1.z) * alpha, -// }; -// } - -// // Calculate vector distance -// function distanceBetweenVectors(v1, v2) { -// const dx = v2.x - v1.x; -// const dy = v2.y - v1.y; -// const dz = v2.z - v1.z; -// return Math.sqrt(dx * dx + dy * dy + dz * dz); -// } - -// // Process spawn logic -// function processSpawns(currentTime) { -// processes.forEach((process) => { -// const processState = animationStates[process.id]; -// if (!processState) return; - -// const spawnPointData = findSpawnPoint(process); -// if (!spawnPointData || !spawnPointData.point.actions) return; - -// const spawnAction = spawnPointData.point.actions.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (!spawnAction) return; - -// const spawnInterval = -// typeof spawnAction.spawnInterval === "number" -// ? spawnAction.spawnInterval -// : parseFloat(spawnAction.spawnInterval || "0"); - -// if (currentTime >= processState.nextSpawnTime) { -// const newObject = createSpawnedObject( -// process, -// spawnPointData, -// currentTime, -// spawnAction.material || "Default" -// ); - -// processState.spawnedObjects[newObject.id] = newObject; -// processState.objectIdCounter++; -// processState.nextSpawnTime = currentTime + spawnInterval; - -// // Notify main thread about new object -// self.postMessage({ -// type: "objectSpawned", -// data: { -// processId: process.id, -// object: newObject, -// }, -// }); -// } -// }); -// } - -// // Update all animations -// function updateAnimations(delta, currentTime) { -// // First handle spawning of new objects -// processSpawns(currentTime); - -// // Then animate existing objects -// processes.forEach((process) => { -// const processState = animationStates[process.id]; -// if (!processState) return; - -// const path = getProcessPath(process); -// if (path.length < 2) return; - -// const updatedObjects = {}; -// let hasChanges = false; - -// Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => { -// if (!obj.visible || !obj.state.isAnimating) return; - -// const stateRef = obj.state; - -// // Use the spawnPointIndex as starting point if it's the initial movement -// if (stateRef.currentIndex === 0 && stateRef.progress === 0) { -// stateRef.currentIndex = stateRef.spawnPointIndex || 0; -// } - -// // Get current point data -// const currentPointData = getPointDataForAnimationIndex( -// process, -// stateRef.currentIndex -// ); - -// // Execute actions when arriving at a new point -// if (stateRef.progress === 0 && currentPointData?.actions) { -// const shouldStop = handlePointActions( -// process.id, -// objectId, -// currentPointData.actions, -// currentTime -// ); -// if (shouldStop) return; -// } - -// // Handle delays -// if (stateRef.isDelaying) { -// if ( -// currentTime - stateRef.delayStartTime >= -// stateRef.currentDelayDuration -// ) { -// stateRef.isDelaying = false; -// stateRef.delayComplete = true; -// } else { -// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; -// return; // Keep waiting -// } -// } - -// const nextPointIdx = stateRef.currentIndex + 1; -// const isLastPoint = nextPointIdx >= path.length; - -// if (isLastPoint) { -// if (currentPointData?.actions) { -// const shouldStop = !hasNonInheritActions(currentPointData.actions); -// if (shouldStop) { -// // Reached the end of path with no more actions -// delete processState.spawnedObjects[objectId]; - -// // Notify main thread to remove the object -// self.postMessage({ -// type: "objectCompleted", -// data: { -// processId: process.id, -// objectId, -// }, -// }); -// return; -// } -// } -// } - -// if (!isLastPoint) { -// const currentPos = path[stateRef.currentIndex]; -// const nextPos = path[nextPointIdx]; -// const distance = distanceBetweenVectors(currentPos, nextPos); -// const movement = stateRef.speed * delta; - -// // Update progress based on distance and speed -// const oldProgress = stateRef.progress; -// stateRef.progress += movement / distance; - -// if (stateRef.progress >= 1) { -// // Reached next point -// stateRef.currentIndex = nextPointIdx; -// stateRef.progress = 0; -// stateRef.delayComplete = false; -// obj.position = [nextPos.x, nextPos.y, nextPos.z]; -// } else { -// // Interpolate position -// const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress); -// obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z]; -// } - -// // Only send updates when there's meaningful movement -// if (Math.abs(oldProgress - stateRef.progress) > 0.01) { -// hasChanges = true; -// } -// } - -// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; -// }); - -// // Update animation state with modified objects -// if (Object.keys(updatedObjects).length > 0) { -// processState.spawnedObjects = { -// ...processState.spawnedObjects, -// ...updatedObjects, -// }; - -// // Only send position updates when there are meaningful changes -// if (hasChanges) { -// self.postMessage({ -// type: "positionsUpdated", -// data: { -// processId: process.id, -// objects: updatedObjects, -// }, -// }); -// } -// } -// }); -// } - -// animation-worker.js -// This web worker handles animation calculations off the main thread - -/* eslint-disable no-restricted-globals */ -// The above disables the ESLint rule for this file since 'self' is valid in web workers - -// Store process data, animation states, and objects -let processes = []; -let animationStates = {}; -let lastTimestamp = 0; -let debugMode = true; - -// Logger function for debugging -function log(...args) { - if (debugMode) { - self.postMessage({ - type: "debug", - data: { message: args.join(' ') } - }); - } -} - -// Message handler for communication with main thread -self.onmessage = function (event) { - const { type, data } = event.data; - log(`Worker received message: ${type}`); - - switch (type) { - case "initialize": - processes = data.processes; - log(`Initialized with ${processes.length} processes`); - initializeAnimationStates(); - break; - - case "update": - const { timestamp, isPlaying } = data; - if (isPlaying) { - const delta = lastTimestamp === 0 ? 0.016 : (timestamp - lastTimestamp) / 1000; // Convert to seconds - updateAnimations(delta, timestamp); - } - lastTimestamp = timestamp; - break; - - case "reset": - log("Resetting animations"); - resetAnimations(); - break; - - case "togglePlay": - // If resuming from pause, recalculate the time delta - log(`Toggle play: ${data.isPlaying}`); - lastTimestamp = data.timestamp; - break; - - case "setDebug": - debugMode = data.enabled; - log(`Debug mode: ${debugMode}`); - break; - } -}; - -// Initialize animation states for all processes -function initializeAnimationStates() { - animationStates = {}; - - processes.forEach((process) => { - if (!process || !process.id) { - log("Invalid process found:", process); - return; - } - - animationStates[process.id] = { - spawnedObjects: {}, - nextSpawnTime: 0, - objectIdCounter: 0, - }; - }); - - // Send initial states back to main thread - self.postMessage({ - type: "statesInitialized", - data: { animationStates }, - }); -} - -// Reset all animations -function resetAnimations() { - initializeAnimationStates(); -} - -// Find spawn point in a process -function findSpawnPoint(process) { - if (!process || !process.paths) { - log(`No paths found for process ${process?.id}`); - return null; - } - - for (const path of process.paths) { - if (!path || !path.points) continue; - - for (const point of path.points) { - if (!point || !point.actions) continue; - - const spawnAction = point.actions.find( - (a) => a && a.isUsed && a.type === "Spawn" - ); - if (spawnAction) { - return { point, path }; - } - } - } - log(`No spawn points found for process ${process.id}`); - return null; -} - -// Create a new spawned object with proper initial position -function createSpawnedObject(process, spawnPoint, currentTime, materialType) { - // Extract spawn position from the actual spawn point - const position = spawnPoint.point.position - ? [...spawnPoint.point.position] - : [0, 0, 0]; - - // Get the path position and add it to the spawn point position - const pathPosition = spawnPoint.path.pathPosition || [0, 0, 0]; - const absolutePosition = [ - position[0] + pathPosition[0], - position[1] + pathPosition[1], - position[2] + pathPosition[2], - ]; - - return { - id: `obj-${process.id}-${animationStates[process.id].objectIdCounter}`, - position: absolutePosition, - state: { - currentIndex: 0, - progress: 0, - isAnimating: true, - speed: process.speed || 1, - isDelaying: false, - delayStartTime: 0, - currentDelayDuration: 0, - delayComplete: false, - currentPathIndex: 0, - // Store the spawn point index to start animation from correct path point - spawnPointIndex: getPointIndexInProcess(process, spawnPoint.point), - }, - visible: true, - materialType: materialType || "Default", - spawnTime: currentTime, - }; -} - -// Get the index of a point within the process animation path -function getPointIndexInProcess(process, point) { - if (!process.paths) return 0; - - let cumulativePoints = 0; - for (const path of process.paths) { - for (let i = 0; i < (path.points?.length || 0); i++) { - if (path.points[i].uuid === point.uuid) { - return cumulativePoints + i; - } - } - cumulativePoints += path.points?.length || 0; - } - - return 0; -} - -// Get point data for current animation index -function getPointDataForAnimationIndex(process, index) { - if (!process.paths) return null; - - let cumulativePoints = 0; - for (const path of process.paths) { - const pointCount = path.points?.length || 0; - - if (index < cumulativePoints + pointCount) { - const pointIndex = index - cumulativePoints; - return path.points?.[pointIndex] || null; - } - - cumulativePoints += pointCount; - } - - return null; -} - -// Convert process paths to Vector3 format -function getProcessPath(process) { - if (!process.animationPath) { - log(`No animation path for process ${process.id}`); - return []; - } - return process.animationPath.map((p) => ({ x: p.x, y: p.y, z: p.z })) || []; -} - -// Handle material swap for an object -function handleMaterialSwap(processId, objectId, materialType) { - const processState = animationStates[processId]; - if (!processState || !processState.spawnedObjects[objectId]) return; - - processState.spawnedObjects[objectId].materialType = materialType; - - // Notify main thread about material change - self.postMessage({ - type: "materialChanged", - data: { - processId, - objectId, - materialType, - }, - }); -} - -// Handle point actions for an object -function handlePointActions(processId, objectId, actions = [], currentTime) { - let shouldStopAnimation = false; - const processState = animationStates[processId]; - - if (!processState || !processState.spawnedObjects[objectId]) return false; - - const objectState = processState.spawnedObjects[objectId]; - - actions.forEach((action) => { - if (!action || !action.isUsed) return; - - switch (action.type) { - case "Delay": - if (objectState.state.isDelaying) return; - - const delayDuration = - typeof action.delay === "number" - ? action.delay - : parseFloat(action.delay || "0"); - - if (delayDuration > 0) { - objectState.state.isDelaying = true; - objectState.state.delayStartTime = currentTime; - objectState.state.currentDelayDuration = delayDuration; - objectState.state.delayComplete = false; - shouldStopAnimation = true; - } - break; - - case "Despawn": - delete processState.spawnedObjects[objectId]; - shouldStopAnimation = true; - - // Notify main thread about despawn - self.postMessage({ - type: "objectDespawned", - data: { - processId, - objectId, - }, - }); - break; - - case "Swap": - if (action.material) { - handleMaterialSwap(processId, objectId, action.material); - } - break; - - default: - break; - } - }); - - return shouldStopAnimation; -} - -// Check if point has non-inherit actions -function hasNonInheritActions(actions = []) { - return actions.some((action) => action && action.isUsed && action.type !== "Inherit"); -} - -// Calculate vector lerp (linear interpolation) -function lerpVectors(v1, v2, alpha) { - return { - x: v1.x + (v2.x - v1.x) * alpha, - y: v1.y + (v2.y - v1.y) * alpha, - z: v1.z + (v2.z - v1.z) * alpha, - }; -} - -// Calculate vector distance -function distanceBetweenVectors(v1, v2) { - const dx = v2.x - v1.x; - const dy = v2.y - v1.y; - const dz = v2.z - v1.z; - return Math.sqrt(dx * dx + dy * dy + dz * dz); -} - -// Process spawn logic -function processSpawns(currentTime) { - processes.forEach((process) => { - const processState = animationStates[process.id]; - if (!processState) return; - - const spawnPointData = findSpawnPoint(process); - if (!spawnPointData || !spawnPointData.point.actions) return; - - const spawnAction = spawnPointData.point.actions.find( - (a) => a.isUsed && a.type === "Spawn" - ); - if (!spawnAction) return; - - const spawnInterval = - typeof spawnAction.spawnInterval === "number" - ? spawnAction.spawnInterval - : parseFloat(spawnAction.spawnInterval || "2"); // Default to 2 seconds if not specified - - if (currentTime >= processState.nextSpawnTime) { - const newObject = createSpawnedObject( - process, - spawnPointData, - currentTime, - spawnAction.material || "Default" - ); - - processState.spawnedObjects[newObject.id] = newObject; - processState.objectIdCounter++; - processState.nextSpawnTime = currentTime + spawnInterval; - - log(`Spawned object ${newObject.id} for process ${process.id}`); - - // Notify main thread about new object - self.postMessage({ - type: "objectSpawned", - data: { - processId: process.id, - object: newObject, - }, - }); - } - }); -} - -// Update all animations -function updateAnimations(delta, currentTime) { - // First handle spawning of new objects - processSpawns(currentTime); - - // Then animate existing objects - processes.forEach((process) => { - const processState = animationStates[process.id]; - if (!processState) return; - - const path = getProcessPath(process); - if (path.length < 2) { - log(`Path too short for process ${process.id}, length: ${path.length}`); - return; - } - - const updatedObjects = {}; - let hasChanges = false; - - Object.entries(processState.spawnedObjects).forEach(([objectId, obj]) => { - if (!obj.visible || !obj.state.isAnimating) return; - - const stateRef = obj.state; - - // Use the spawnPointIndex as starting point if it's the initial movement - if (stateRef.currentIndex === 0 && stateRef.progress === 0) { - stateRef.currentIndex = stateRef.spawnPointIndex || 0; - } - - // Get current point data - const currentPointData = getPointDataForAnimationIndex( - process, - stateRef.currentIndex - ); - - // Execute actions when arriving at a new point - if (stateRef.progress === 0 && currentPointData?.actions) { - const shouldStop = handlePointActions( - process.id, - objectId, - currentPointData.actions, - currentTime - ); - if (shouldStop) return; - } - - // Handle delays - if (stateRef.isDelaying) { - if ( - currentTime - stateRef.delayStartTime >= - stateRef.currentDelayDuration - ) { - stateRef.isDelaying = false; - stateRef.delayComplete = true; - } else { - updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; - return; // Keep waiting - } - } - - const nextPointIdx = stateRef.currentIndex + 1; - const isLastPoint = nextPointIdx >= path.length; - - if (isLastPoint) { - if (currentPointData?.actions) { - const shouldStop = !hasNonInheritActions(currentPointData.actions); - if (shouldStop) { - // Reached the end of path with no more actions - delete processState.spawnedObjects[objectId]; - log(`Object ${objectId} completed path`); - - // Notify main thread to remove the object - self.postMessage({ - type: "objectCompleted", - data: { - processId: process.id, - objectId, - }, - }); - return; - } - } - } - - if (!isLastPoint) { - const currentPos = path[stateRef.currentIndex]; - const nextPos = path[nextPointIdx]; - const distance = distanceBetweenVectors(currentPos, nextPos); - - // Ensure we don't divide by zero - if (distance > 0) { - const movement = stateRef.speed * delta; - - // Update progress based on distance and speed - const oldProgress = stateRef.progress; - stateRef.progress += movement / distance; - - if (stateRef.progress >= 1) { - // Reached next point - stateRef.currentIndex = nextPointIdx; - stateRef.progress = 0; - stateRef.delayComplete = false; - obj.position = [nextPos.x, nextPos.y, nextPos.z]; - } else { - // Interpolate position - const lerpedPos = lerpVectors(currentPos, nextPos, stateRef.progress); - obj.position = [lerpedPos.x, lerpedPos.y, lerpedPos.z]; - } - - // Only send updates when there's meaningful movement - if (Math.abs(oldProgress - stateRef.progress) > 0.01) { - hasChanges = true; - } - } else { - // Skip to next point if distance is zero - stateRef.currentIndex = nextPointIdx; - stateRef.progress = 0; - obj.position = [nextPos.x, nextPos.y, nextPos.z]; - hasChanges = true; - } - } - - updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; - }); - - // Update animation state with modified objects - if (Object.keys(updatedObjects).length > 0) { - processState.spawnedObjects = { - ...processState.spawnedObjects, - ...updatedObjects, - }; - - // Only send position updates when there are meaningful changes - if (hasChanges) { - self.postMessage({ - type: "positionsUpdated", - data: { - processId: process.id, - objects: updatedObjects, - }, - }); - } - } - }); -} diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx index 512b165..c8cf07f 100644 --- a/app/src/modules/simulation/process/processAnimator.tsx +++ b/app/src/modules/simulation/process/processAnimator.tsx @@ -1,590 +1,16 @@ -// import React, { useRef, useState, useEffect, useMemo } from "react"; -// import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -// import { GLTFLoader } from "three-stdlib"; -// import { useLoader, useFrame } from "@react-three/fiber"; -// import * as THREE from "three"; -// import { GLTF } from "three-stdlib"; -// import boxGltb from "../../../assets/gltf-glb/crate_box.glb"; - -// interface PointAction { -// uuid: string; -// name: string; -// type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap"; -// objectType: string; -// material: string; -// delay: string | number; -// spawnInterval: string | number; -// isUsed: boolean; -// } - -// interface ProcessPoint { -// uuid: string; -// position: number[]; -// rotation: number[]; -// actions: PointAction[]; -// connections: { -// source: { pathUUID: string; pointUUID: string }; -// targets: { pathUUID: string; pointUUID: string }[]; -// }; -// } - -// interface ProcessPath { -// modeluuid: string; -// modelName: string; -// points: ProcessPoint[]; -// pathPosition: number[]; -// pathRotation: number[]; -// speed: number; -// } - -// interface ProcessData { -// id: string; -// paths: ProcessPath[]; -// animationPath: { x: number; y: number; z: number }[]; -// pointActions: PointAction[][]; -// speed: number; -// customMaterials?: Record; -// renderAs?: "box" | "custom"; -// } - -// interface AnimationState { -// currentIndex: number; -// progress: number; -// isAnimating: boolean; -// speed: number; -// isDelaying: boolean; -// delayStartTime: number; -// currentDelayDuration: number; -// delayComplete: boolean; -// currentPathIndex: number; -// } - -// interface SpawnedObject { -// ref: React.RefObject; -// state: AnimationState; -// visible: boolean; -// material: THREE.Material; -// spawnTime: number; -// currentMaterialType: string; -// position: THREE.Vector3; // The position of the object -// } - -// interface ProcessAnimationState { -// spawnedObjects: { [objectId: string]: SpawnedObject }; -// nextSpawnTime: number; -// objectIdCounter: number; -// } - -// const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ -// processes, -// }) => { -// -// const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; -// const { isPlaying } = usePlayButtonStore(); -// const groupRef = useRef(null); - -// const [animationStates, setAnimationStates] = useState< -// Record -// >({}); - -// // Base materials -// const baseMaterials = useMemo( -// () => ({ -// Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), -// Box: new THREE.MeshStandardMaterial({ -// color: 0xcccccc, -// metalness: 0.8, -// roughness: 0.2, -// }), -// Crate: new THREE.MeshStandardMaterial({ -// color: 0x00aaff, -// metalness: 0.1, -// roughness: 0.5, -// }), -// Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), -// }), -// [] -// ); - -// // Initialize animation states when processes or play state changes -// useEffect(() => { -// if (!isPlaying) { -// setAnimationStates({}); -// return; -// } - -// const newStates: Record = {}; -// processes.forEach((process) => { -// newStates[process.id] = { -// spawnedObjects: {}, -// nextSpawnTime: 0, -// objectIdCounter: 0, -// }; -// }); -// setAnimationStates(newStates); -// }, [isPlaying, processes]); - -// // Find spawn point in a process -// const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { -// for (const path of process.paths || []) { -// for (const point of path.points || []) { -// const spawnAction = point.actions?.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (spawnAction) { -// return point; -// } -// } -// } -// return null; -// }; - -// // Find the corresponding animation path point for a spawn point -// const findAnimationPathPoint = (process: ProcessData, spawnPoint: ProcessPoint): THREE.Vector3 => { -// // If we have an animation path, use the first point -// if (process.animationPath && process.animationPath.length > 0) { -// // Find the index of this point in the path -// let pointIndex = 0; - -// // Try to find the corresponding point in the animation path -// for (const path of process.paths || []) { -// for (let i = 0; i < (path.points?.length || 0); i++) { -// const point = path.points?.[i]; -// if (point && point.uuid === spawnPoint.uuid) { -// // Found the matching point -// if (process.animationPath[pointIndex]) { -// const p = process.animationPath[pointIndex]; -// return new THREE.Vector3(p.x, p.y, p.z); -// } -// } -// pointIndex++; -// } -// } - -// // Fallback to the spawn point's position -// return new THREE.Vector3( -// spawnPoint.position[0], -// spawnPoint.position[1], -// spawnPoint.position[2] -// ); -// } - -// // If no animation path, use the spawn point's position -// return new THREE.Vector3( -// spawnPoint.position[0], -// spawnPoint.position[1], -// spawnPoint.position[2] -// ); -// }; - -// // Create a new spawned object -// const createSpawnedObject = ( -// process: ProcessData, -// currentTime: number, -// materialType: string, -// spawnPoint: ProcessPoint -// ): SpawnedObject => { -// const processMaterials = { -// ...baseMaterials, -// ...(process.customMaterials || {}), -// }; - -// // Get the position where we should spawn -// const spawnPosition = findAnimationPathPoint(process, spawnPoint); - -// return { -// ref: React.createRef(), -// state: { -// currentIndex: 0, -// progress: 0, -// isAnimating: true, -// speed: process.speed || 1, -// isDelaying: false, -// delayStartTime: 0, -// currentDelayDuration: 0, -// delayComplete: false, -// currentPathIndex: 0, -// }, -// visible: true, -// material: -// processMaterials[materialType as keyof typeof processMaterials] || -// baseMaterials.Default, -// currentMaterialType: materialType, -// spawnTime: currentTime, -// position: spawnPosition, // Store the position directly -// }; -// }; - -// // Handle material swap for an object -// const handleMaterialSwap = ( -// processId: string, -// objectId: string, -// materialType: string -// ) => { -// setAnimationStates((prev) => { -// const processState = prev[processId]; -// if (!processState || !processState.spawnedObjects[objectId]) return prev; - -// const process = processes.find((p) => p.id === processId); -// const processMaterials = { -// ...baseMaterials, -// ...(process?.customMaterials || {}), -// }; - -// const newMaterial = -// processMaterials[materialType as keyof typeof processMaterials] || -// baseMaterials.Default; - -// return { -// ...prev, -// [processId]: { -// ...processState, -// spawnedObjects: { -// ...processState.spawnedObjects, -// [objectId]: { -// ...processState.spawnedObjects[objectId], -// material: newMaterial, -// currentMaterialType: materialType, -// }, -// }, -// }, -// }; -// }); -// }; - -// // Handle point actions for an object -// const handlePointActions = ( -// processId: string, -// objectId: string, -// actions: PointAction[] = [], -// currentTime: number -// ): boolean => { -// let shouldStopAnimation = false; - -// actions.forEach((action) => { -// if (!action.isUsed) return; - -// switch (action.type) { -// case "Delay": -// setAnimationStates((prev) => { -// const processState = prev[processId]; -// if ( -// !processState || -// !processState.spawnedObjects[objectId] || -// processState.spawnedObjects[objectId].state.isDelaying -// ) { -// return prev; -// } - -// const delayDuration = -// typeof action.delay === "number" -// ? action.delay -// : parseFloat(action.delay as string) || 0; - -// if (delayDuration > 0) { -// return { -// ...prev, -// [processId]: { -// ...processState, -// spawnedObjects: { -// ...processState.spawnedObjects, -// [objectId]: { -// ...processState.spawnedObjects[objectId], -// state: { -// ...processState.spawnedObjects[objectId].state, -// isDelaying: true, -// delayStartTime: currentTime, -// currentDelayDuration: delayDuration, -// delayComplete: false, -// }, -// }, -// }, -// }, -// }; -// } -// return prev; -// }); -// shouldStopAnimation = true; -// break; - -// case "Despawn": -// setAnimationStates((prev) => { -// const processState = prev[processId]; -// if (!processState) return prev; - -// const newSpawnedObjects = { ...processState.spawnedObjects }; -// delete newSpawnedObjects[objectId]; - -// return { -// ...prev, -// [processId]: { -// ...processState, -// spawnedObjects: newSpawnedObjects, -// }, -// }; -// }); -// shouldStopAnimation = true; -// break; - -// case "Swap": -// if (action.material) { -// handleMaterialSwap(processId, objectId, action.material); -// } -// break; - -// default: -// break; -// } -// }); - -// return shouldStopAnimation; -// }; - -// // Check if point has non-inherit actions -// const hasNonInheritActions = (actions: PointAction[] = []): boolean => { -// return actions.some((action) => action.isUsed && action.type !== "Inherit"); -// }; - -// // Get point data for current animation index -// const getPointDataForAnimationIndex = ( -// process: ProcessData, -// index: number -// ): ProcessPoint | null => { -// if (!process.paths) return null; - -// let cumulativePoints = 0; -// for (const path of process.paths) { -// const pointCount = path.points?.length || 0; - -// if (index < cumulativePoints + pointCount) { -// const pointIndex = index - cumulativePoints; -// return path.points?.[pointIndex] || null; -// } - -// cumulativePoints += pointCount; -// } - -// return null; -// }; - -// // Spawn objects for all processes -// useFrame((state) => { -// if (!isPlaying) return; - -// const currentTime = state.clock.getElapsedTime(); -// setAnimationStates((prev) => { -// const newStates = { ...prev }; - -// processes.forEach((process) => { -// const processState = newStates[process.id]; -// if (!processState) return; - -// const spawnPoint = findSpawnPoint(process); -// if (!spawnPoint || !spawnPoint.actions) return; - -// const spawnAction = spawnPoint.actions.find( -// (a) => a.isUsed && a.type === "Spawn" -// ); -// if (!spawnAction) return; - -// const spawnInterval = -// typeof spawnAction.spawnInterval === "number" -// ? spawnAction.spawnInterval -// : parseFloat(spawnAction.spawnInterval as string) || 0; - -// if (currentTime >= processState.nextSpawnTime) { -// const objectId = `obj-${process.id}-${processState.objectIdCounter}`; - -// // Create the new object with the spawn point -// const newObject = createSpawnedObject( -// process, -// currentTime, -// spawnAction.material || "Default", -// spawnPoint -// ); - -// newStates[process.id] = { -// ...processState, -// spawnedObjects: { -// ...processState.spawnedObjects, -// [objectId]: newObject, -// }, -// objectIdCounter: processState.objectIdCounter + 1, -// nextSpawnTime: currentTime + spawnInterval, -// }; -// } -// }); - -// return newStates; -// }); -// }); - -// // Animate objects for all processes -// useFrame((state, delta) => { -// if (!isPlaying) return; - -// const currentTime = state.clock.getElapsedTime(); -// setAnimationStates((prev) => { -// const newStates = { ...prev }; - -// processes.forEach((process) => { -// const processState = newStates[process.id]; -// if (!processState) return; - -// const path = -// process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || -// []; -// if (path.length < 2) return; - -// const updatedObjects = { ...processState.spawnedObjects }; - -// Object.entries(processState.spawnedObjects).forEach( -// ([objectId, obj]) => { -// if (!obj.visible || !obj.state.isAnimating) return; - -// const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; -// if (!currentRef) return; - -// // Set the position when the reference is first available -// if (obj.position && obj.state.currentIndex === 0 && obj.state.progress === 0) { -// currentRef.position.copy(obj.position); -// } - -// const stateRef = obj.state; - -// // Get current point data -// const currentPointData = getPointDataForAnimationIndex( -// process, -// stateRef.currentIndex -// ); - -// // Execute actions when arriving at a new point -// if (stateRef.progress === 0 && currentPointData?.actions) { -// const shouldStop = handlePointActions( -// process.id, -// objectId, -// currentPointData.actions, -// currentTime -// ); -// if (shouldStop) return; -// } - -// // Handle delays -// if (stateRef.isDelaying) { -// if ( -// currentTime - stateRef.delayStartTime >= -// stateRef.currentDelayDuration -// ) { -// stateRef.isDelaying = false; -// stateRef.delayComplete = true; -// } else { -// return; // Keep waiting -// } -// } - -// const nextPointIdx = stateRef.currentIndex + 1; -// const isLastPoint = nextPointIdx >= path.length; - -// if (isLastPoint) { -// if (currentPointData?.actions) { -// const shouldStop = !hasNonInheritActions( -// currentPointData.actions -// ); -// if (shouldStop) { -// currentRef.position.copy(path[stateRef.currentIndex]); -// delete updatedObjects[objectId]; -// return; -// } -// } -// } - -// if (!isLastPoint) { -// const nextPoint = path[nextPointIdx]; -// const distance = -// path[stateRef.currentIndex].distanceTo(nextPoint); -// const movement = stateRef.speed * delta; -// stateRef.progress += movement / distance; - -// if (stateRef.progress >= 1) { -// stateRef.currentIndex = nextPointIdx; -// stateRef.progress = 0; -// stateRef.delayComplete = false; -// currentRef.position.copy(nextPoint); -// } else { -// currentRef.position.lerpVectors( -// path[stateRef.currentIndex], -// nextPoint, -// stateRef.progress -// ); -// } -// } - -// updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; -// } -// ); - -// newStates[process.id] = { -// ...processState, -// spawnedObjects: updatedObjects, -// }; -// }); - -// return newStates; -// }); -// }); - -// if (!processes || processes.length === 0) { -// return null; -// } - -// return ( -// <> -// {Object.entries(animationStates).flatMap(([processId, processState]) => -// Object.entries(processState.spawnedObjects) -// .filter(([_, obj]) => obj.visible) -// .map(([objectId, obj]) => { -// const process = processes.find((p) => p.id === processId); -// const renderAs = process?.renderAs || "custom"; - -// return renderAs === "box" ? ( -// } -// material={obj.material} -// position={obj.position} // Set position directly in the JSX -// > -// -// -// ) : ( -// gltf?.scene && ( -// } -// position={obj.position} // Set position directly in the JSX -// > -// -// -// ) -// ); -// }) -// )} -// -// ); -// }; - -// export default ProcessAnimator; - import React, { useRef, useState, useEffect, useMemo } from "react"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore, +} from "../../../store/usePlayButtonStore"; import { GLTFLoader } from "three-stdlib"; import { useLoader, useFrame } from "@react-three/fiber"; import * as THREE from "three"; import { GLTF } from "three-stdlib"; -import boxGltb from "../../../assets/gltf-glb/crate_box.glb"; -import camera from "../../../assets/gltf-glb/camera face 2.gltf"; +import crate from "../../../assets/gltf-glb/crate_box.glb"; +import box from "../../../assets/gltf-glb/box.glb"; interface PointAction { uuid: string; @@ -603,8 +29,8 @@ interface ProcessPoint { rotation: number[]; actions: PointAction[]; connections: { - source: { pathUUID: string; pointUUID: string }; - targets: { pathUUID: string; pointUUID: string }[]; + source: { modelUUID: string; pointUUID: string }; + targets: { modelUUID: string; pointUUID: string }[]; }; } @@ -646,14 +72,13 @@ interface SpawnedObject { material: THREE.Material; spawnTime: number; currentMaterialType: string; - position: THREE.Vector3; // The position of the object + position: THREE.Vector3; } interface ProcessAnimationState { spawnedObjects: { [objectId: string]: SpawnedObject }; nextSpawnTime: number; objectIdCounter: number; - // New fields for process-wide delay isProcessDelaying: boolean; processDelayStartTime: number; processDelayDuration: number; @@ -662,55 +87,118 @@ interface ProcessAnimationState { const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ processes, }) => { - - const gltf = useLoader(GLTFLoader, boxGltb) as GLTF; - const { isPlaying } = usePlayButtonStore(); + const gltf = useLoader(GLTFLoader, crate) as GLTF; + const { isPlaying, setIsPlaying } = usePlayButtonStore(); + const { isPaused, setIsPaused } = usePauseButtonStore(); + const { isReset, setReset } = useResetButtonStore(); const groupRef = useRef(null); - + const debugRef = useRef(false); + const clockRef = useRef(new THREE.Clock()); + const pauseTimeRef = useRef(0); + const elapsedBeforePauseRef = useRef(0); + const animationStatesRef = useRef>({}); + const { speed, setSpeed } = useAnimationPlaySpeed(); + const prevIsPlaying = useRef(null); + const [internalResetFlag, setInternalResetFlag] = useState(false); const [animationStates, setAnimationStates] = useState< Record >({}); + // Store the speed in a ref to access the latest value in animation frames + const speedRef = useRef(speed); + + // Update the ref when speed changes + useEffect(() => { + speedRef.current = speed; + }, [speed]); + + useEffect(() => { + if (prevIsPlaying.current !== null) { + setAnimationStates({}); + } + + // Update ref to current isPlaying after effect + prevIsPlaying.current = isPlaying; + + // setAnimationStates({}); + }, [isPlaying]); + + // Sync ref with state + useEffect(() => { + animationStatesRef.current = animationStates; + }, [animationStates]); + // Base materials const baseMaterials = useMemo( () => ({ - Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), - Box: new THREE.MeshPhongMaterial({ - color: 0xcccccc, - }), - Crate: new THREE.MeshStandardMaterial({ - color: 0x00aaff, - metalness: 0.1, - roughness: 0.5, - }), + Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), + Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), }), [] ); + // Replace your reset effect with this: + useEffect(() => { + if (isReset) { + // 1. Mark that we're doing an internal reset + setInternalResetFlag(true); + + // 2. Pause the animation first + setIsPlaying(false); + setIsPaused(false); + + // 3. Reset all animation states + setAnimationStates({}); + animationStatesRef.current = {}; + + // 4. Reset timing references + clockRef.current = new THREE.Clock(); + elapsedBeforePauseRef.current = 0; + pauseTimeRef.current = 0; + + // 5. Clear the external reset flag + setReset(false); + + // 6. After state updates are complete, restart + setTimeout(() => { + setInternalResetFlag(false); + setIsPlaying(true); + }, 0); + } + }, [isReset, setReset, setIsPlaying, setIsPaused]); + + // Handle pause state changes + useEffect(() => { + if (isPaused) { + pauseTimeRef.current = clockRef.current.getElapsedTime(); + } else if (pauseTimeRef.current > 0) { + const pausedDuration = + clockRef.current.getElapsedTime() - pauseTimeRef.current; + elapsedBeforePauseRef.current += pausedDuration; + } + }, [isPaused]); + // Initialize animation states when processes or play state changes useEffect(() => { - if (!isPlaying) { - setAnimationStates({}); - return; + if (isPlaying && !internalResetFlag) { + const newStates: Record = {}; + processes.forEach((process) => { + newStates[process.id] = { + spawnedObjects: {}, + nextSpawnTime: 0, + objectIdCounter: 0, + isProcessDelaying: false, + processDelayStartTime: 0, + processDelayDuration: 0, + }; + }); + setAnimationStates(newStates); + animationStatesRef.current = newStates; + clockRef.current.start(); } + }, [isPlaying, processes, internalResetFlag]); - const newStates: Record = {}; - processes.forEach((process) => { - newStates[process.id] = { - spawnedObjects: {}, - nextSpawnTime: 0, - objectIdCounter: 0, - // Initialize process-wide delay state - isProcessDelaying: false, - processDelayStartTime: 0, - processDelayDuration: 0, - }; - }); - setAnimationStates(newStates); - }, [isPlaying, processes]); - - // Find spawn point in a process const findSpawnPoint = (process: ProcessData): ProcessPoint | null => { for (const path of process.paths || []) { for (const point of path.points || []) { @@ -725,22 +213,16 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return null; }; - // Find the corresponding animation path point for a spawn point const findAnimationPathPoint = ( process: ProcessData, spawnPoint: ProcessPoint ): THREE.Vector3 => { - // If we have an animation path, use the first point if (process.animationPath && process.animationPath.length > 0) { - // Find the index of this point in the path let pointIndex = 0; - - // Try to find the corresponding point in the animation path for (const path of process.paths || []) { for (let i = 0; i < (path.points?.length || 0); i++) { const point = path.points?.[i]; if (point && point.uuid === spawnPoint.uuid) { - // Found the matching point if (process.animationPath[pointIndex]) { const p = process.animationPath[pointIndex]; return new THREE.Vector3(p.x, p.y, p.z); @@ -749,16 +231,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ pointIndex++; } } - - // Fallback to the spawn point's position - return new THREE.Vector3( - spawnPoint.position[0], - spawnPoint.position[1], - spawnPoint.position[2] - ); } - - // If no animation path, use the spawn point's position return new THREE.Vector3( spawnPoint.position[0], spawnPoint.position[1], @@ -766,7 +239,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ); }; - // Create a new spawned object const createSpawnedObject = ( process: ProcessData, currentTime: number, @@ -778,8 +250,14 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...(process.customMaterials || {}), }; - // Get the position where we should spawn const spawnPosition = findAnimationPathPoint(process, spawnPoint); + const material = + processMaterials[materialType as keyof typeof processMaterials] || + baseMaterials.Default; + + if (debugRef.current) { + console.log(`Creating object with material: ${materialType}`, material); + } return { ref: React.createRef(), @@ -787,7 +265,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ currentIndex: 0, progress: 0, isAnimating: true, - speed: process.speed || 1, + speed: process.speed || 1, // Process base speed (will be multiplied by global speed) isDelaying: false, delayStartTime: 0, currentDelayDuration: 0, @@ -795,34 +273,50 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ currentPathIndex: 0, }, visible: true, - material: - processMaterials[materialType as keyof typeof processMaterials] || - baseMaterials.Default, + material: material, currentMaterialType: materialType, spawnTime: currentTime, - position: spawnPosition, // Store the position directly + position: spawnPosition, }; }; - // Handle material swap for an object const handleMaterialSwap = ( processId: string, objectId: string, materialType: string ) => { + if (debugRef.current) { + console.log(`Attempting material swap to: ${materialType}`); + } + setAnimationStates((prev) => { const processState = prev[processId]; - if (!processState || !processState.spawnedObjects[objectId]) return prev; + if (!processState || !processState.spawnedObjects[objectId]) { + if (debugRef.current) console.log("Object not found for swap"); + return prev; + } const process = processes.find((p) => p.id === processId); + if (!process) { + if (debugRef.current) console.log("Process not found"); + return prev; + } + const processMaterials = { ...baseMaterials, - ...(process?.customMaterials || {}), + ...(process.customMaterials || {}), }; const newMaterial = - processMaterials[materialType as keyof typeof processMaterials] || - baseMaterials.Default; + processMaterials[materialType as keyof typeof processMaterials]; + if (!newMaterial) { + if (debugRef.current) console.log(`Material ${materialType} not found`); + return prev; + } + + if (debugRef.current) { + console.log(`Swapping material for ${objectId} to ${materialType}`); + } return { ...prev, @@ -841,7 +335,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ }); }; - // Handle point actions for an object const handlePointActions = ( processId: string, objectId: string, @@ -853,6 +346,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ actions.forEach((action) => { if (!action.isUsed) return; + if (debugRef.current) { + console.log(`Processing action: ${action.type} for ${objectId}`); + } + switch (action.type) { case "Delay": setAnimationStates((prev) => { @@ -871,18 +368,16 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...prev, [processId]: { ...processState, - // Set process-wide delay instead of object-specific delay isProcessDelaying: true, processDelayStartTime: currentTime, processDelayDuration: delayDuration, - // Update the specific object's state as well spawnedObjects: { ...processState.spawnedObjects, [objectId]: { ...processState.spawnedObjects[objectId], state: { ...processState.spawnedObjects[objectId].state, - isAnimating: false, // Explicitly pause animation during delay + isAnimating: false, isDelaying: true, delayStartTime: currentTime, currentDelayDuration: delayDuration, @@ -931,12 +426,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return shouldStopAnimation; }; - // Check if point has non-inherit actions const hasNonInheritActions = (actions: PointAction[] = []): boolean => { return actions.some((action) => action.isUsed && action.type !== "Inherit"); }; - // Get point data for current animation index const getPointDataForAnimationIndex = ( process: ProcessData, index: number @@ -958,11 +451,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ return null; }; - // Spawn objects for all processes - useFrame((state) => { - if (!isPlaying) return; + useFrame(() => { + if (!isPlaying || isPaused) return; + + const currentTime = + clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; - const currentTime = state.clock.getElapsedTime(); setAnimationStates((prev) => { const newStates = { ...prev }; @@ -970,18 +464,18 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const processState = newStates[process.id]; if (!processState) return; - // Skip spawning if the process is currently in a delay if (processState.isProcessDelaying) { - // Check if delay is over + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + processState.processDelayDuration / speedRef.current; + if ( currentTime - processState.processDelayStartTime >= - processState.processDelayDuration + effectiveDelayTime ) { - // Reset process delay state newStates[process.id] = { ...processState, isProcessDelaying: false, - // Reset delay state on all objects in this process spawnedObjects: Object.entries( processState.spawnedObjects ).reduce( @@ -993,8 +487,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...obj.state, isDelaying: false, delayComplete: true, - isAnimating: true, // Ensure animation resumes - // Force a small progress to ensure movement starts + isAnimating: true, progress: obj.state.progress === 0 ? 0.001 : obj.state.progress, }, @@ -1004,7 +497,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ), }; } - return; // Skip spawning while delaying + return; } const spawnPoint = findSpawnPoint(process); @@ -1020,10 +513,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ? spawnAction.spawnInterval : parseFloat(spawnAction.spawnInterval as string) || 0; + // Apply global speed to spawn intervals (faster speed = more frequent spawns) + const effectiveSpawnInterval = spawnInterval / speedRef.current; + if (currentTime >= processState.nextSpawnTime) { const objectId = `obj-${process.id}-${processState.objectIdCounter}`; - - // Create the new object with the spawn point const newObject = createSpawnedObject( process, currentTime, @@ -1038,7 +532,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ [objectId]: newObject, }, objectIdCounter: processState.objectIdCounter + 1, - nextSpawnTime: currentTime + spawnInterval, + nextSpawnTime: currentTime + effectiveSpawnInterval, }; } }); @@ -1047,11 +541,12 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ }); }); - // Animate objects for all processes - useFrame((state, delta) => { - if (!isPlaying) return; + useFrame((_, delta) => { + if (!isPlaying || isPaused) return; + + const currentTime = + clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; - const currentTime = state.clock.getElapsedTime(); setAnimationStates((prev) => { const newStates = { ...prev }; @@ -1059,18 +554,18 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const processState = newStates[process.id]; if (!processState) return; - // Check if the process-wide delay is active if (processState.isProcessDelaying) { - // Check if the delay has completed + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + processState.processDelayDuration / speedRef.current; + if ( currentTime - processState.processDelayStartTime >= - processState.processDelayDuration + effectiveDelayTime ) { - // Reset process delay state AND resume animation newStates[process.id] = { ...processState, isProcessDelaying: false, - // Reset delay state on all objects in this process AND ensure isAnimating is true spawnedObjects: Object.entries( processState.spawnedObjects ).reduce( @@ -1082,8 +577,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ...obj.state, isDelaying: false, delayComplete: true, - isAnimating: true, // Ensure animation resumes - // Important: Force progress to a small positive value to ensure movement + isAnimating: true, progress: obj.state.progress === 0 ? 0.005 : obj.state.progress, }, @@ -1092,10 +586,8 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ {} ), }; - // Skip the rest of the processing for this frame to allow the state update to take effect return newStates; } else { - // If we're still in a process-wide delay, don't animate anything return newStates; } } @@ -1109,13 +601,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ Object.entries(processState.spawnedObjects).forEach( ([objectId, obj]) => { - // Skip objects that are explicitly not visible if (!obj.visible) return; const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; if (!currentRef) return; - // Set the position when the reference is first available if ( obj.position && obj.state.currentIndex === 0 && @@ -1126,27 +616,22 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const stateRef = obj.state; - // Check if we're delaying at the object level and update accordingly if (stateRef.isDelaying) { - if ( - currentTime - stateRef.delayStartTime >= - stateRef.currentDelayDuration - ) { - // Delay is complete, resume animation + // Apply global speed to delays (faster speed = shorter delays) + const effectiveDelayTime = + stateRef.currentDelayDuration / speedRef.current; + + if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) { stateRef.isDelaying = false; stateRef.delayComplete = true; - stateRef.isAnimating = true; // Explicitly resume animation + stateRef.isAnimating = true; - // Force movement from the current point by setting progress to a small value - // if we're at the start of a segment if (stateRef.progress === 0) { stateRef.progress = 0.005; } - // Force an immediate position update to ensure visually accurate position const nextPointIdx = stateRef.currentIndex + 1; if (nextPointIdx < path.length) { - // Calculate the position slightly ahead of the current point const slightProgress = Math.max(stateRef.progress, 0.005); currentRef.position.lerpVectors( path[stateRef.currentIndex], @@ -1157,23 +642,25 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ ); } } else { - // Still delaying, don't animate this object updatedObjects[objectId] = { ...obj, state: { ...stateRef } }; return; } } - // Skip animation if the object shouldn't be animating if (!stateRef.isAnimating) return; - // Get current point data const currentPointData = getPointDataForAnimationIndex( process, stateRef.currentIndex ); - // Execute actions when arriving at a new point if (stateRef.progress === 0 && currentPointData?.actions) { + if (debugRef.current) { + console.log( + `At point ${stateRef.currentIndex} with actions:`, + currentPointData.actions + ); + } const shouldStop = handlePointActions( process.id, objectId, @@ -1195,10 +682,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ currentPointData.actions ); if (shouldStop) { - // uncomment this or write own logic to handle the object when reaching the last point of the process - - // currentRef.position.copy(path[stateRef.currentIndex]); - // delete updatedObjects[objectId]; return; } } @@ -1208,37 +691,35 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const nextPoint = path[nextPointIdx]; const distance = path[stateRef.currentIndex].distanceTo(nextPoint); - const movement = stateRef.speed * delta; - // If we just resumed from a delay, ensure we make actual progress + // Apply both process-specific speed and global speed multiplier + const effectiveSpeed = stateRef.speed * speedRef.current; + const movement = effectiveSpeed * delta; + if (stateRef.delayComplete && stateRef.progress < 0.01) { - // Boost initial movement after delay to ensure visible progress - stateRef.progress = 0.05; // Small but visible initial progress - stateRef.delayComplete = false; // Reset flag so we don't do this again + stateRef.progress = 0.05; + stateRef.delayComplete = false; } else { - // Normal progress calculation stateRef.progress += movement / distance; } if (stateRef.progress >= 1) { - // We've reached the next point stateRef.currentIndex = nextPointIdx; stateRef.progress = 0; currentRef.position.copy(nextPoint); - // Check if we need to execute actions at this new point const newPointData = getPointDataForAnimationIndex( process, stateRef.currentIndex ); - if (newPointData?.actions) { - // We've arrived at a new point with actions, handle them in the next frame - // We don't call handlePointActions directly here to avoid state update issues - // The actions will be handled in the next frame when progress is 0 + if (newPointData?.actions && debugRef.current) { + console.log( + `Reached new point with actions:`, + newPointData.actions + ); } } else { - // Normal path interpolation currentRef.position.lerpVectors( path[stateRef.currentIndex], nextPoint, @@ -1274,29 +755,40 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ const process = processes.find((p) => p.id === processId); const renderAs = process?.renderAs || "custom"; - return renderAs === "box" ? ( - } - material={obj.material} - position={obj.position} // Set position directly in the JSX - > - - - ) : ( - gltf?.scene && ( + if (renderAs === "box") { + return ( + } + material={obj.material} + position={obj.position} + > + + + ); + } + + if (gltf?.scene) { + // Clone the scene and apply the material to all meshes + const clonedScene = gltf.scene.clone(); + clonedScene.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.material = obj.material; + } + }); + + return ( } - position={obj.position} // Set position directly in the JSX + position={obj.position} > - + - ) - ); + ); + } + + return null; }) )} diff --git a/app/src/modules/simulation/process/processCreator.tsx b/app/src/modules/simulation/process/processCreator.tsx index 91bc4c1..4403a71 100644 --- a/app/src/modules/simulation/process/processCreator.tsx +++ b/app/src/modules/simulation/process/processCreator.tsx @@ -5,7 +5,7 @@ // useCallback, // useRef, // } from "react"; -// import { useSimulationPaths } from "../../../store/store"; +// import { useSimulationStates } from "../../../store/store"; // import * as THREE from "three"; // import { useThree } from "@react-three/fiber"; // import { @@ -29,7 +29,7 @@ // position: [number, number, number]; // actions: PointAction[]; // connections: { -// targets: Array<{ pathUUID: string }>; +// targets: Array<{ modelUUID: string }>; // }; // } @@ -72,7 +72,7 @@ // actions: point.actions.map(normalizeAction), // Preserve exact actions // connections: { // targets: point.connections.targets.map((target) => ({ -// pathUUID: target.pathUUID, +// modelUUID: target.modelUUID, // })), // }, // })), @@ -94,7 +94,7 @@ // : [normalizeAction(path.point.actions)], // connections: { // targets: path.point.connections.targets.map((target) => ({ -// pathUUID: target.pathUUID, +// modelUUID: target.modelUUID, // })), // }, // }, @@ -137,18 +137,18 @@ // // Check if current last connects to next last (requires reversal) // const connectsToLast = currentLastPoint.connections.targets.some( // (target) => -// target.pathUUID === nextPath.modeluuid && +// target.modelUUID === nextPath.modeluuid && // nextLastPoint.connections.targets.some( -// (t) => t.pathUUID === currentPath.modeluuid +// (t) => t.modelUUID === currentPath.modeluuid // ) // ); // // Check if current last connects to next first (no reversal needed) // const connectsToFirst = currentLastPoint.connections.targets.some( // (target) => -// target.pathUUID === nextPath.modeluuid && +// target.modelUUID === nextPath.modeluuid && // nextFirstPoint.connections.targets.some( -// (t) => t.pathUUID === currentPath.modeluuid +// (t) => t.modelUUID === currentPath.modeluuid // ) // ); @@ -249,10 +249,10 @@ // // Process outgoing connections // for (const point of currentPath.points) { // for (const target of point.connections.targets) { -// if (!visited.has(target.pathUUID)) { -// const targetPath = pathMap.get(target.pathUUID); +// if (!visited.has(target.modelUUID)) { +// const targetPath = pathMap.get(target.modelUUID); // if (targetPath) { -// visited.add(target.pathUUID); +// visited.add(target.modelUUID); // queue.push(targetPath); // } // } @@ -264,7 +264,7 @@ // if (!visited.has(uuid)) { // const hasConnectionToCurrent = path.points.some((point) => // point.connections.targets.some( -// (t) => t.pathUUID === currentPath.modeluuid +// (t) => t.modelUUID === currentPath.modeluuid // ) // ); // if (hasConnectionToCurrent) { @@ -312,19 +312,19 @@ // const ProcessCreator: React.FC = React.memo( // ({ onProcessesCreated }) => { -// const { simulationPaths } = useSimulationPaths(); +// const { simulationStates } = useSimulationStates(); // const { createProcessesFromPaths } = useProcessCreation(); // const prevPathsRef = useRef([]); // const prevProcessesRef = useRef([]); // const convertedPaths = useMemo((): SimulationPath[] => { -// if (!simulationPaths) return []; -// return simulationPaths.map((path) => +// if (!simulationStates) return []; +// return simulationStates.map((path) => // convertToSimulationPath( // path as ConveyorEventsSchema | VehicleEventsSchema // ) // ); -// }, [simulationPaths]); +// }, [simulationStates]); // const pathsDependency = useMemo(() => { // if (!convertedPaths) return null; @@ -335,7 +335,7 @@ // ), // connections: path.points // .flatMap((p: PathPoint) => -// p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) +// p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) // ) // .join(","), // })); @@ -404,7 +404,7 @@ import React, { useCallback, useRef, } from "react"; -import { useSimulationPaths } from "../../../store/store"; +import { useSimulationStates } from "../../../store/store"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { @@ -428,11 +428,12 @@ export interface PathPoint { position: [number, number, number]; actions: PointAction[]; connections: { - targets: Array<{ pathUUID: string }>; + targets: Array<{ modelUUID: string }>; }; } export interface SimulationPath { + type: string; modeluuid: string; points: PathPoint[]; pathPosition: [number, number, number]; @@ -464,6 +465,7 @@ function convertToSimulationPath( if (path.type === "Conveyor") { return { + type: path.type, modeluuid, points: path.points.map((point) => ({ uuid: point.uuid, @@ -471,7 +473,7 @@ function convertToSimulationPath( actions: point.actions.map(normalizeAction), // Preserve exact actions connections: { targets: point.connections.targets.map((target) => ({ - pathUUID: target.pathUUID, + modelUUID: target.modelUUID, })), }, })), @@ -483,23 +485,24 @@ function convertToSimulationPath( }; } else { return { + type: path.type, modeluuid, points: [ { - uuid: path.point.uuid, - position: path.point.position, - actions: Array.isArray(path.point.actions) - ? path.point.actions.map(normalizeAction) - : [normalizeAction(path.point.actions)], + uuid: path.points.uuid, + position: path.points.position, + actions: Array.isArray(path.points.actions) + ? path.points.actions.map(normalizeAction) + : [normalizeAction(path.points.actions)], connections: { - targets: path.point.connections.targets.map((target) => ({ - pathUUID: target.pathUUID, + targets: path.points.connections.targets.map((target) => ({ + modelUUID: target.modelUUID, })), }, }, ], pathPosition: path.position, - speed: path.point.speed || 1, + speed: path.points.speed || 1, }; } } @@ -536,18 +539,18 @@ function shouldReverseNextPath( // Check if current last connects to next last (requires reversal) const connectsToLast = currentLastPoint.connections.targets.some( (target) => - target.pathUUID === nextPath.modeluuid && + target.modelUUID === nextPath.modeluuid && nextLastPoint.connections.targets.some( - (t) => t.pathUUID === currentPath.modeluuid + (t) => t.modelUUID === currentPath.modeluuid ) ); // Check if current last connects to next first (no reversal needed) const connectsToFirst = currentLastPoint.connections.targets.some( (target) => - target.pathUUID === nextPath.modeluuid && + target.modelUUID === nextPath.modeluuid && nextFirstPoint.connections.targets.some( - (t) => t.pathUUID === currentPath.modeluuid + (t) => t.modelUUID === currentPath.modeluuid ) ); @@ -614,6 +617,7 @@ export function useProcessCreation() { const [processes, setProcesses] = useState([]); const hasSpawnAction = useCallback((path: SimulationPath): boolean => { + if (path.type !== "Conveyor") return false; return path.points.some((point) => point.actions.some((action) => action.type.toLowerCase() === "spawn") ); @@ -674,10 +678,10 @@ export function useProcessCreation() { // Process outgoing connections for (const point of currentPath.points) { for (const target of point.connections.targets) { - if (!visited.has(target.pathUUID)) { - const targetPath = pathMap.get(target.pathUUID); + if (!visited.has(target.modelUUID)) { + const targetPath = pathMap.get(target.modelUUID); if (targetPath) { - visited.add(target.pathUUID); + visited.add(target.modelUUID); queue.push(targetPath); } } @@ -689,7 +693,7 @@ export function useProcessCreation() { if (!visited.has(uuid)) { const hasConnectionToCurrent = path.points.some((point) => point.connections.targets.some( - (t) => t.pathUUID === currentPath.modeluuid + (t) => t.modelUUID === currentPath.modeluuid ) ); if (hasConnectionToCurrent) { @@ -737,19 +741,19 @@ export function useProcessCreation() { const ProcessCreator: React.FC = React.memo( ({ onProcessesCreated }) => { - const { simulationPaths } = useSimulationPaths(); + const { simulationStates } = useSimulationStates(); const { createProcessesFromPaths } = useProcessCreation(); const prevPathsRef = useRef([]); const prevProcessesRef = useRef([]); const convertedPaths = useMemo((): SimulationPath[] => { - if (!simulationPaths) return []; - return simulationPaths.map((path) => + if (!simulationStates) return []; + return simulationStates.map((path) => convertToSimulationPath( path as ConveyorEventsSchema | VehicleEventsSchema ) ); - }, [simulationPaths]); + }, [simulationStates]); // Enhanced dependency tracking that includes action types const pathsDependency = useMemo(() => { @@ -764,7 +768,7 @@ const ProcessCreator: React.FC = React.memo( .join(","), connections: path.points .flatMap((p: PathPoint) => - p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID) + p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID) ) .join(","), })); diff --git a/app/src/modules/simulation/process/processObjectRender.tsx b/app/src/modules/simulation/process/processObjectRender.tsx new file mode 100644 index 0000000..7f5a0cc --- /dev/null +++ b/app/src/modules/simulation/process/processObjectRender.tsx @@ -0,0 +1,114 @@ +import React, { useRef, useEffect } from "react"; +import { useFrame } from "@react-three/fiber"; +import * as THREE from "three"; +import { GLTF } from "three-stdlib"; +import { Box3Helper } from "three"; +import { SpawnedObject, ProcessData } from "./types"; + +interface ProcessObjectRendererProps { + objectId: string; + object: SpawnedObject; + process: ProcessData; + gltf: GLTF; + showBoundingBox?: boolean; +} + +export const ProcessObjectRenderer: React.FC = ({ + objectId, + object, + process, + gltf, + showBoundingBox = false, +}) => { + const meshRef = useRef(null); + const boxHelperRef = useRef(null); + const boundingBoxRef = useRef(new THREE.Box3()); + + // Issue 1: Can't assign to ref.current as it's read-only + useEffect(() => { + if (object.ref && meshRef.current) { + // Instead of direct assignment, we need to store the mesh reference another way + // Option 1: If you can modify the SpawnedObject interface, add a setMesh method + if (typeof (object as any).setMesh === 'function') { + (object as any).setMesh(meshRef.current); + } + + // Option 2: Store the mesh in a property that isn't ref.current + // This requires modifying your SpawnedObject interface to include this property + (object as any).meshInstance = meshRef.current; + + // Option 3: If you need to maintain compatibility, you could use Object.defineProperty + // But this is a hack and not recommended + // Object.defineProperty(object.ref, 'current', { value: meshRef.current, writable: true }); + } + }, [object.ref]); + + // Create a bounding box helper for visualization + useFrame(() => { + if (meshRef.current && showBoundingBox) { + // Update the bounding box to match the mesh position + if (!boxHelperRef.current) { + // Get the size of the mesh + const size = new THREE.Vector3(1, 1, 1); + + // If the mesh has geometry, use its dimensions + if (meshRef.current.geometry) { + const box = new THREE.Box3().setFromObject(meshRef.current); + box.getSize(size); + } + + // Create a new bounding box centered on the mesh + boundingBoxRef.current = new THREE.Box3().setFromCenterAndSize( + meshRef.current.position, + size + ); + + // Create a helper to visualize the box + boxHelperRef.current = new Box3Helper( + boundingBoxRef.current, + new THREE.Color(0xff0000) + ); + + // Add the helper to the scene + meshRef.current.parent?.add(boxHelperRef.current); + } else { + // Update the box position to match the mesh + boundingBoxRef.current.setFromCenterAndSize( + meshRef.current.position, + boundingBoxRef.current.getSize(new THREE.Vector3()) + ); + + // Force the helper to update + boxHelperRef.current.updateMatrixWorld(true); + } + } + }); + + if (gltf?.scene) { + return ( + + ); + } + + // Issue 2: Material color type problem + return ( + + + + + ); +}; \ No newline at end of file diff --git a/app/src/modules/simulation/process/types.ts b/app/src/modules/simulation/process/types.ts new file mode 100644 index 0000000..64a3e6a --- /dev/null +++ b/app/src/modules/simulation/process/types.ts @@ -0,0 +1,79 @@ +import * as THREE from "three"; +import React from "react"; + +export interface ProcessPoint { + uuid: string; + position: number[]; + actions?: PointAction[]; +} + +export interface PointAction { + type: string; + isUsed: boolean; + spawnInterval?: number | string; + material?: string; + delay?: number | string; +} + +export interface ProcessData { + id: string; + name: string; + paths?: { + points?: ProcessPoint[]; + }[]; + animationPath?: { x: number; y: number; z: number }[]; + speed?: number; + customMaterials?: Record; +} + +export interface ProcessAnimationState { + spawnedObjects: Record; + nextSpawnTime: number; + objectIdCounter: number; + isProcessDelaying: boolean; + processDelayStartTime: number; + processDelayDuration: number; + isCollisionPaused?: boolean; +} + +export interface SpawnedObject { + ref: React.RefObject; + state: { + currentIndex: number; + progress: number; + isAnimating: boolean; + speed: number; + isDelaying: boolean; + delayStartTime: number; + currentDelayDuration: number; + delayComplete: boolean; + currentPathIndex: number; + }; + visible: boolean; + material: THREE.Material; + currentMaterialType: string; + spawnTime: number; + position: THREE.Vector3; + collision?: { + boundingBox: THREE.Box3; + isColliding: boolean; + colliding: boolean; // Added this property + }; +} + +// For use in your processAnimator.tsx +// Update the CollisionState interface to include all required properties +interface CollisionState { + boundingBox: THREE.Box3; + isColliding: boolean; + colliding: boolean; // This was missing + collidingWith: string[]; + } +export interface SpawnedObjectWithCollision extends SpawnedObject { + collision: { + boundingBox: THREE.Box3; + isColliding: boolean; + colliding: boolean; + collidingWith: string[]; + }; + } diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 830a0b1..98fd3dd 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useMemo } from "react"; import { useSelectedActionSphere, useSelectedPath, - useSimulationPaths, + useSimulationStates, } from "../../store/store"; import * as THREE from "three"; import Behaviour from "./behaviour/behaviour"; @@ -15,12 +15,12 @@ import Agv from "../builder/agv/agv"; function Simulation() { const { activeModule } = useModuleStore(); const pathsGroupRef = useRef() as React.MutableRefObject; - const { simulationPaths, setSimulationPaths } = useSimulationPaths(); + const { simulationStates, setSimulationStates } = useSimulationStates(); const [processes, setProcesses] = useState([]); useEffect(() => { - console.log('simulationPaths: ', simulationPaths); - }, [simulationPaths]); + // console.log('simulationStates: ', simulationStates); + }, [simulationStates]); // useEffect(() => { // if (selectedActionSphere) { @@ -42,7 +42,7 @@ function Simulation() { - {/* */} + )} diff --git a/app/src/modules/simulation/simulationUI.tsx b/app/src/modules/simulation/simulationUI.tsx index 0ce9fc2..055fb36 100644 --- a/app/src/modules/simulation/simulationUI.tsx +++ b/app/src/modules/simulation/simulationUI.tsx @@ -1,5 +1,5 @@ // import { useMemo, useState } from 'react'; -// import { useSelectedActionSphere, useToggleView, useSimulationPaths, useSelectedPath, useStartSimulation, useDrawMaterialPath } from '../../store/store'; +// import { useSelectedActionSphere, useToggleView, useSimulationStates, useSelectedPath, useStartSimulation, useDrawMaterialPath } from '../../store/store'; // import * as THREE from 'three'; // import useModuleStore from '../../store/useModuleStore'; @@ -9,17 +9,17 @@ // const { startSimulation, setStartSimulation } = useStartSimulation(); // const { selectedActionSphere } = useSelectedActionSphere(); // const { selectedPath, setSelectedPath } = useSelectedPath(); -// const { simulationPaths, setSimulationPaths } = useSimulationPaths(); +// const { simulationStates, setSimulationStates } = useSimulationStates(); // const { drawMaterialPath, setDrawMaterialPath } = useDrawMaterialPath(); // const [activeButton, setActiveButton] = useState(null); // const handleAddAction = () => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => { -// if (point.uuid === selectedActionSphere.point.uuid) { +// if (point.uuid === selectedActionSphere.points.uuid) { // const actionIndex = point.actions.length; // const newAction = { // uuid: THREE.MathUtils.generateUUID(), @@ -37,31 +37,31 @@ // }), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleDeleteAction = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { ...point, actions: point.actions.filter(action => action.uuid !== uuid) } // : point // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleActionSelect = (uuid: string, actionType: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -72,16 +72,16 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleMaterialSelect = (uuid: string, material: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -92,16 +92,16 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleDelayChange = (uuid: string, delay: number | string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -112,16 +112,16 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleSpawnIntervalChange = (uuid: string, spawnInterval: number | string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => @@ -132,27 +132,27 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleSpeedChange = (speed: number) => { // if (!selectedPath) return; -// const updatedPaths = simulationPaths.map((path) => +// const updatedPaths = simulationStates.map((path) => // path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path // ); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); // }; // const handleAddTrigger = () => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => { -// if (point.uuid === selectedActionSphere.point.uuid) { +// if (point.uuid === selectedActionSphere.points.uuid) { // const triggerIndex = point.triggers.length; // const newTrigger = { // uuid: THREE.MathUtils.generateUUID(), @@ -167,31 +167,31 @@ // }), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleDeleteTrigger = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { ...point, triggers: point.triggers.filter(trigger => trigger.uuid !== uuid) } // : point // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleTriggerSelect = (uuid: string, triggerType: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // triggers: point.triggers.map((trigger) => @@ -202,7 +202,7 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleResetPath = () => { @@ -214,10 +214,10 @@ // const handleActionToggle = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // actions: point.actions.map((action) => ({ @@ -229,16 +229,16 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const handleTriggerToggle = (uuid: string) => { // if (!selectedActionSphere) return; -// const updatedPaths = simulationPaths.map((path) => ({ +// const updatedPaths = simulationStates.map((path) => ({ // ...path, // points: path.points.map((point) => -// point.uuid === selectedActionSphere.point.uuid +// point.uuid === selectedActionSphere.points.uuid // ? { // ...point, // triggers: point.triggers.map((trigger) => ({ @@ -250,13 +250,13 @@ // ), // })); -// setSimulationPaths(updatedPaths); +// setSimulationStates(updatedPaths); // }; // const selectedPoint = useMemo(() => { // if (!selectedActionSphere) return null; -// return simulationPaths.flatMap((path) => path.points).find((point) => point.uuid === selectedActionSphere.point.uuid); -// }, [selectedActionSphere, simulationPaths]); +// return simulationStates.flatMap((path) => path.points).find((point) => point.uuid === selectedActionSphere.points.uuid); +// }, [selectedActionSphere, simulationStates]); // const createPath = () => { // setActiveButton(activeButton !== 'addMaterialPath' ? 'addMaterialPath' : null); diff --git a/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx b/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx index c09b21c..c9dd36b 100644 --- a/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx +++ b/app/src/modules/simulation/simulationtemp/path/pathCreator.tsx @@ -11,13 +11,13 @@ type PathPoint = { }; type PathCreatorProps = { - simulationPaths: PathPoint[][]; - setSimulationPaths: React.Dispatch>; + simulationStates: PathPoint[][]; + setSimulationStates: React.Dispatch>; connections: { start: PathPoint; end: PathPoint }[]; setConnections: React.Dispatch> }; -const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConnections }: PathCreatorProps) => { +const PathCreator = ({ simulationStates, setSimulationStates, connections, setConnections }: PathCreatorProps) => { const { camera, scene, raycaster, pointer, gl } = useThree(); const { drawMaterialPath } = useDrawMaterialPath(); @@ -71,7 +71,7 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn e.preventDefault(); if (drag || e.button === 0) return; if (currentPath.length > 1) { - setSimulationPaths((prevPaths) => [...prevPaths, currentPath]); + setSimulationStates((prevPaths) => [...prevPaths, currentPath]); } setCurrentPath([]); setTemporaryPoint(null); @@ -125,7 +125,7 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn canvasElement.addEventListener("contextmenu", onContextMenu); } else { if (currentPath.length > 1) { - setSimulationPaths((prevPaths) => [...prevPaths, currentPath]); + setSimulationStates((prevPaths) => [...prevPaths, currentPath]); } setCurrentPath([]); setTemporaryPoint(null); @@ -179,25 +179,25 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn if (selectedPoint) { const updatedPosition = e.target.object.position.clone(); const updatedRotation = e.target.object.quaternion.clone(); - const updatedPaths = simulationPaths.map((path) => + const updatedPaths = simulationStates.map((path) => path.map((p) => p.uuid === selectedPoint.uuid ? { ...p, position: updatedPosition, rotation: updatedRotation } : p ) ); - setSimulationPaths(updatedPaths); + setSimulationStates(updatedPaths); } }; const meshContext = (uuid: string) => { - const pathIndex = simulationPaths.findIndex(path => path.some(point => point.uuid === uuid)); + const pathIndex = simulationStates.findIndex(path => path.some(point => point.uuid === uuid)); if (pathIndex === -1) return; - const clickedPoint = simulationPaths[pathIndex].find(point => point.uuid === uuid); + const clickedPoint = simulationStates[pathIndex].find(point => point.uuid === uuid); if (!clickedPoint) return; - const isStart = simulationPaths[pathIndex][0].uuid === uuid; - const isEnd = simulationPaths[pathIndex][simulationPaths[pathIndex].length - 1].uuid === uuid; + const isStart = simulationStates[pathIndex][0].uuid === uuid; + const isEnd = simulationStates[pathIndex][simulationStates[pathIndex].length - 1].uuid === uuid; if (pathIndex === 0 && isStart) { console.log("The first-ever point is not connectable."); @@ -285,8 +285,8 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn return ( <> - {/* Render finalized simulationPaths */} - {simulationPaths.map((path, pathIndex) => ( + {/* Render finalized simulationStates */} + {simulationStates.map((path, pathIndex) => ( + {simulationStates.map((path) => path.map((point) => ( ([]); + const [simulationStates, setSimulationStates] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }[][]>([]); const [connections, setConnections] = useState<{ start: PathPoint; end: PathPoint }[]>([]); return ( <> - - {simulationPaths.map((path, index) => ( + + {simulationStates.map((path, index) => ( ))} diff --git a/app/src/modules/visualization/realTimeVizSocket.dev.tsx b/app/src/modules/visualization/realTimeVizSocket.dev.tsx index ad5e226..9ee6b5b 100644 --- a/app/src/modules/visualization/realTimeVizSocket.dev.tsx +++ b/app/src/modules/visualization/realTimeVizSocket.dev.tsx @@ -5,6 +5,14 @@ import { useDroppedObjectsStore } from "../../store/useDroppedObjectsStore"; import { useZoneWidgetStore } from "../../store/useZone3DWidgetStore"; import useTemplateStore from "../../store/useTemplateStore"; +type WidgetData = { + id: string; + type: string; + position: [number, number, number]; + rotation?: [number, number, number]; + tempPosition?: [number, number, number]; +}; + export default function SocketRealTimeViz() { const { visualizationSocket } = useSocketStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); @@ -14,6 +22,7 @@ export default function SocketRealTimeViz() { const { addWidget } = useZoneWidgetStore() const { templates, removeTemplate } = useTemplateStore(); const { setTemplates } = useTemplateStore(); + const { zoneWidgetData, setZoneWidgetData, updateWidgetPosition, updateWidgetRotation } = useZoneWidgetStore(); useEffect(() => { const email = localStorage.getItem("email") || ""; @@ -132,14 +141,36 @@ export default function SocketRealTimeViz() { }); //add 3D Widget response visualizationSocket.on("viz-widget3D:response:updates", (add3DWidget: any) => { - console.log('add3DWidget: ', add3DWidget); + console.log('add3DWidget: ', add3DWidget); if (add3DWidget.success) { + console.log('add3DWidget: ', add3DWidget); if (add3DWidget.message === "Widget created successfully") { addWidget(add3DWidget.data.zoneId, add3DWidget.data.widget); } } }); + //delete 3D Widget response + visualizationSocket.on("viz-widget3D:response:delete", (delete3DWidget: any) => { + console.log('delete3DWidget: ', delete3DWidget); + // "3DWidget delete unsuccessfull" + if (delete3DWidget.success && delete3DWidget.message === "3DWidget delete successfull") { + const activeZoneWidgets = zoneWidgetData[delete3DWidget.data.zoneId] || []; + setZoneWidgetData( + delete3DWidget.data.zoneId, + activeZoneWidgets.filter((w: WidgetData) => w.id !== delete3DWidget.data.id) + ); + } + }); + //update3D widget response + visualizationSocket.on("viz-widget3D:response:modifyPositionRotation", (update3DWidget: any) => { + console.log('update3DWidget: ', update3DWidget); + + if (update3DWidget.success && update3DWidget.message==="widget update successfully") { + updateWidgetPosition(update3DWidget.data.zoneId, update3DWidget.data.widget.id, update3DWidget.data.widget.position); + updateWidgetRotation(update3DWidget.data.zoneId, update3DWidget.data.widget.id, update3DWidget.data.widget.rotation); + } + }); // add Template response visualizationSocket.on("viz-template:response:add", (addingTemplate: any) => { console.log('addingTemplate: ', addingTemplate); diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 54fe61f..88fd467 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -56,7 +56,7 @@ const Project: React.FC = () => { return (
- {loadingProgress && } + {/* {loadingProgress && } */} {!isPlaying && ( <> {toggleThreeD && } diff --git a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts index a1ac727..2685ca4 100644 --- a/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts +++ b/app/src/services/factoryBuilder/assest/assets/getCategoryAsset.ts @@ -1,8 +1,8 @@ -let BackEnd_url = `http://${process.env.REACT_APP_SERVER_ASSET_LIBRARY_URL}`; +let BackEnd_url = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; export const getCategoryAsset = async (categoryName: any) => { try { const response = await fetch( - `${BackEnd_url}/api/v2/getCatagoryAssets/${categoryName}`, + `${BackEnd_url}/api/v2/getCategoryAssets/${categoryName}`, { method: "GET", headers: { diff --git a/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts index 86c8f71..b419963 100644 --- a/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts +++ b/app/src/services/factoryBuilder/assest/floorAsset/setEventsApt.ts @@ -17,7 +17,7 @@ export const setEventApi = async ( }); if (!response.ok) { - throw new Error("Failed to set or update Floor Item"); + throw new Error("Failed to set or Update Event Data"); } const result = await response.json(); diff --git a/app/src/services/factoryBuilder/environment/findEnvironment.ts b/app/src/services/factoryBuilder/environment/findEnvironment.ts index de5b6a6..08cfe68 100644 --- a/app/src/services/factoryBuilder/environment/findEnvironment.ts +++ b/app/src/services/factoryBuilder/environment/findEnvironment.ts @@ -26,7 +26,7 @@ export const findEnvironment = async (organization: string, userId: string) => { false, false, false, - 0, + 40, true ); return userpos; diff --git a/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts b/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts index 82562b7..401a5bf 100644 --- a/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts +++ b/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts @@ -5,9 +5,6 @@ export const adding3dWidgets = async ( organization: string, widget: {} ) => { - console.log('widget: ', widget); - console.log('organization: ', organization); - console.log('zoneId: ', zoneId); try { const response = await fetch( `${url_Backend_dwinzo}/api/v2/3dwidget/save`, diff --git a/app/src/services/realTimeVisulization/zoneData/delete3dWidget.ts b/app/src/services/realTimeVisulization/zoneData/delete3dWidget.ts new file mode 100644 index 0000000..fe868f1 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/delete3dWidget.ts @@ -0,0 +1,38 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; + +export const delete3dWidgetApi = async ( + zoneId: string, + organization: string, + id: string +) => { + console.log("zoneId: ", zoneId); + console.log("organization: ", organization); + console.log("id: ", id); + + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/widget3D/delete`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, zoneId, id }), + } + ); + + if (!response.ok) { + throw new Error("Failed to delete floating widget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/update3dWidget.ts b/app/src/services/realTimeVisulization/zoneData/update3dWidget.ts new file mode 100644 index 0000000..553fc68 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/update3dWidget.ts @@ -0,0 +1,81 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const update3dWidget = async ( + zoneId: string, + organization: string, + id: string, + position?: [number, number, number] +) => { + console.log("organization: ", organization); + console.log("zoneId: ", zoneId); + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/modifyPR/widget3D`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + organization, + zoneId, + id, + position, + }), + } + ); + + if (!response.ok) { + throw new Error("Failed to add 3dwidget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; + +export const update3dWidgetRotation = async ( + zoneId: string, + organization: string, + id: string, + rotation?: [number, number, number] +) => { + console.log("organization: ", organization); + console.log("zoneId: ", zoneId); + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/modifyPR/widget3D`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + organization, + zoneId, + id, + rotation, + }), + } + ); + + if (!response.ok) { + throw new Error("Failed to add 3dwidget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/store/store.ts b/app/src/store/store.ts index 365eb97..725180b 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -14,7 +14,7 @@ export const useSocketStore = create((set: any, get: any) => ({ const socket = io( `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder`, { - reconnection: false, + reconnection: true, auth: { email, organization }, } ); @@ -22,7 +22,7 @@ export const useSocketStore = create((set: any, get: any) => ({ const visualizationSocket = io( `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization`, { - reconnection: false, + reconnection: true, auth: { email, organization }, } ); @@ -302,7 +302,13 @@ export const useDrieTemp = create((set: any) => ({ export const useActiveUsers = create((set: any) => ({ activeUsers: [], - setActiveUsers: (x: any) => set({ activeUsers: x }), + setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => + set((state: { activeUsers: any[] }) => ({ + activeUsers: + typeof callback === "function" + ? callback(state.activeUsers) + : callback, + })), })); export const useDrieUIValue = create((set: any) => ({ @@ -341,24 +347,29 @@ export const useSelectedPath = create((set: any) => ({ })); interface SimulationPathsStore { - simulationPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]; - setSimulationPaths: ( + simulationStates: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]; + setSimulationStates: ( paths: - | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] - | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[] - ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema)[]) + | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[] + | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[] + ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) ) => void; } -export const useSimulationPaths = create((set) => ({ - simulationPaths: [], - setSimulationPaths: (paths) => +export const useSimulationStates = create((set) => ({ + simulationStates: [], + setSimulationStates: (paths) => set((state) => ({ - simulationPaths: - typeof paths === "function" ? paths(state.simulationPaths) : paths, + simulationStates: + typeof paths === "function" ? paths(state.simulationStates) : paths, })), })) +export const useNavMesh = create((set: any) => ({ + navMesh: null, + setNavMesh: (x: any) => set({ navMesh: x }), +})); + export const useIsConnecting = create((set: any) => ({ isConnecting: false, setIsConnecting: (x: any) => set({ isConnecting: x }), diff --git a/app/src/store/usePlayButtonStore.ts b/app/src/store/usePlayButtonStore.ts index e975a7c..bed2bd7 100644 --- a/app/src/store/usePlayButtonStore.ts +++ b/app/src/store/usePlayButtonStore.ts @@ -4,8 +4,32 @@ type PlayButtonStore = { isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly setIsPlaying: (value: boolean) => void; // Updated setter function name for clarity }; +type PauseButtonStore = { + isPaused: boolean; // Updated state name to reflect the play/pause status more clearly + setIsPaused: (value: boolean) => void; // Updated setter function name for clarity +}; +type ResetButtonStore = { + isReset: boolean; // Updated state name to reflect the play/pause status more clearly + setReset: (value: boolean) => void; // Updated setter function name for clarity +}; +interface AnimationSpeedState { + speed: number; + setSpeed: (value: number) => void; +} export const usePlayButtonStore = create((set) => ({ isPlaying: false, // Default state for play/pause setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state })); +export const useResetButtonStore = create((set) => ({ + isReset: false, // Default state for play/pause + setReset: (value) => set({ isReset: value }), // Update isPlaying state +})); +export const usePauseButtonStore = create((set) => ({ + isPaused: false, // Default state for play/pause + setIsPaused: (value) => set({ isPaused: value }), // Update isPlaying state +})); +export const useAnimationPlaySpeed = create((set) => ({ + speed: 1, + setSpeed: (value) => set({ speed: value }), +})); diff --git a/app/src/store/useZone3DWidgetStore.ts b/app/src/store/useZone3DWidgetStore.ts index 850623f..7dcaaeb 100644 --- a/app/src/store/useZone3DWidgetStore.ts +++ b/app/src/store/useZone3DWidgetStore.ts @@ -1,10 +1,10 @@ - import { create } from "zustand"; type WidgetData = { id: string; type: string; position: [number, number, number]; + rotation?: [number, number, number]; tempPosition?: [number, number, number]; }; @@ -13,38 +13,59 @@ type ZoneWidgetStore = { setZoneWidgetData: (zoneId: string, widgets: WidgetData[]) => void; addWidget: (zoneId: string, widget: WidgetData) => void; updateWidgetPosition: (zoneId: string, widgetId: string, newPosition: [number, number, number]) => void; + updateWidgetRotation: (zoneId: string, widgetId: string, newRotation: [number, number, number]) => void; }; export const useZoneWidgetStore = create((set) => ({ zoneWidgetData: {}, - setZoneWidgetData: (zoneId, widgets) => - set((state) => ({ + setZoneWidgetData: (zoneId: string, widgets: WidgetData[]) => + set((state: ZoneWidgetStore) => ({ zoneWidgetData: { ...state.zoneWidgetData, [zoneId]: widgets }, })), - addWidget: (zoneId, widget) => - set((state) => ({ + addWidget: (zoneId: string, widget: WidgetData) => + set((state: ZoneWidgetStore) => ({ zoneWidgetData: { ...state.zoneWidgetData, - [zoneId]: [...(state.zoneWidgetData[zoneId] || []), widget], + [zoneId]: [...(state.zoneWidgetData[zoneId] || []), { ...widget, rotation: widget.rotation || [0, 0, 0] }], }, })), - updateWidgetPosition: (zoneId, widgetId, newPosition) => - set((state) => { + updateWidgetPosition: (zoneId: string, widgetId: string, newPosition: [number, number, number]) => + set((state: ZoneWidgetStore) => { const widgets = state.zoneWidgetData[zoneId] || []; return { zoneWidgetData: { ...state.zoneWidgetData, - [zoneId]: widgets.map((widget) => + [zoneId]: widgets.map((widget: WidgetData) => widget.id === widgetId ? { ...widget, position: newPosition } : widget ), }, }; }), + + updateWidgetRotation: (zoneId: string, widgetId: string, newRotation: [number, number, number]) => + set((state: ZoneWidgetStore) => { + const widgets = state.zoneWidgetData[zoneId] || []; + return { + zoneWidgetData: { + ...state.zoneWidgetData, + [zoneId]: widgets.map((widget: WidgetData) => + widget.id === widgetId ? { ...widget, rotation: newRotation } : widget + ), + }, + }; + }), })); +// export type WidgetData = { +// id: string; +// type: string; +// position: [number, number, number]; +// rotation?: [number, number, number]; +// tempPosition?: [number, number, number]; +// }; interface RightClickStore { rightClickSelected: string | null; @@ -75,3 +96,13 @@ export const useRightSelected = create((set) => ({ rightSelect: null, // Default state is null setRightSelect: (x) => set({ rightSelect: x }), })); + +interface EditWidgetOptionsStore { + editWidgetOptions: boolean; + setEditWidgetOptions: (value: boolean) => void; +} + +export const useEditWidgetOptionsStore = create((set) => ({ + editWidgetOptions: false, // Initial state + setEditWidgetOptions: (value: boolean) => set({ editWidgetOptions: value }), +})); diff --git a/app/src/styles/components/tools.scss b/app/src/styles/components/tools.scss index cb65697..5a10518 100644 --- a/app/src/styles/components/tools.scss +++ b/app/src/styles/components/tools.scss @@ -4,7 +4,7 @@ .tools-container { @include flex-center; position: fixed; - bottom: 50px; + bottom: 32px; left: 50%; transform: translate(-50%, 0); padding: 8px; diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index d4cf595..465af2c 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -472,6 +472,15 @@ font-size: var(--font-weight-regular); color: #4a4a4a; + .reviewChart { + width: 100%; + + .floating { + width: 100%; + + } + } + .selectedWidget { padding: 6px 12px; border-top: 1px solid var(--border-color); diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index ecc58ce..efacef1 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -7,7 +7,7 @@ border-radius: 20px; box-shadow: $box-shadow-medium; width: calc(100% - (320px + 270px + 90px)); - height: calc(100% - (200px + 80px)); + height: calc(100% - (250px)); position: absolute; top: 50%; left: calc(270px + 45px); @@ -16,6 +16,13 @@ transition: all 0.2s; z-index: #{$z-index-default}; + .realTime-viz-wrapper { + width: 100%; + height: 100%; + position: relative; + z-index: -1; + } + .floating { width: 100%; max-width: 250px; @@ -109,7 +116,7 @@ } .zone-wrapper.bottom { - bottom: 210px; + bottom: calc(var(--realTimeViz-container-height) * 0.27); } .content-container { @@ -182,6 +189,7 @@ .panel-content { position: relative; height: 100%; + width: 100%; padding: 10px; display: flex; flex-direction: column; @@ -194,7 +202,6 @@ .chart-container { width: 100%; - height: 25% !important; min-height: 150px; max-height: 100%; // border: 1px dashed var(--background-color-gray); @@ -203,6 +210,7 @@ padding: 6px 0; background-color: var(--background-color); position: relative; + padding: 0 10px; .kebab { width: 30px; @@ -287,15 +295,17 @@ &.bottom-panel { left: 0; right: 0; + min-height: 150px; .panel-content { display: flex; flex-direction: row; height: 100%; + width: 100%; + min-height: 150px; .chart-container { - height: 100% !important; - width: 20%; + min-width: 150px; } } @@ -314,17 +324,42 @@ top: 0; bottom: 0; - .chart-container { - width: 100%; - height: 180px; - } + } &.right-panel { right: 0; top: 0; - bottom: 0; + bottom: 0 + } + &.left-panel, + &.right-panel { + min-width: 150px; + + .panel-content { + flex-direction: column; + width: 100%; + + + gap: 6px; + + .chart-container { + width: 100%; + + + min-height: 150px; + max-height: 100%; + // border: 1px dashed var(--background-color-gray); + border-radius: 8px; + box-shadow: var(--box-shadow-medium); + padding: 6px 0; + background-color: var(--background-color); + position: relative; + } + + + } } } @@ -335,7 +370,7 @@ .playingFlase { .zone-wrapper.bottom { - bottom: 300px; + bottom: calc(var(--realTimeViz-container-height) * 0.25); } } @@ -732,9 +767,9 @@ .editWidgetOptions { position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + // top: 50%; + // left: 50%; + // transform: translate(-50%, -50%); background-color: var(--background-color); z-index: 3; display: flex; @@ -742,6 +777,8 @@ border-radius: 6px; overflow: hidden; + min-width: 150px; + .option { padding: 8px 10px; color: var(--text-color); diff --git a/app/src/types/world/worldTypes.d.ts b/app/src/types/world/worldTypes.d.ts index d1ce13f..7baf5e0 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -201,27 +201,6 @@ export type FloorItemType = { modelfileID: string; isLocked: boolean; isVisible: boolean; - eventData?: { - type: 'Conveyor'; - points: { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; - triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; - }[]; - speed: number | string; - } | { - type: 'Vehicle'; - point: { - uuid: string; - position: [number, number, number]; - actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; - speed: number; - }; - }; }; // Array of floor items for managing multiple objects on the floor @@ -292,10 +271,10 @@ export type RefCSM = React.MutableRefObject; export type RefCSMHelper = React.MutableRefObject; interface PathConnection { - fromPathUUID: string; + fromModelUUID: string; fromUUID: string; toConnections: { - toPathUUID: string; + toModelUUID: string; toUUID: string; }[]; } @@ -317,7 +296,7 @@ interface ConveyorEventsSchema { rotation: [number, number, number]; actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; }[]; position: [number, number, number]; rotation: [number, number, number]; @@ -328,12 +307,71 @@ interface VehicleEventsSchema { modeluuid: string; modelName: string; type: 'Vehicle'; - point: { + points: { uuid: string; position: [number, number, number]; actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; - connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; speed: number; }; position: [number, number, number]; -} \ No newline at end of file +} + +interface StaticMachineEventsSchema { + modeluuid: string; + modelName: string; + type: 'StaticMachine'; + points: { + uuid: string; + position: [number, number, number]; + actions: { uuid: string; name: string; buffer: number | string; material: string; isUsed: boolean }; + triggers: { uuid: string; name: string; type: string }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + }; + position: [number, number, number]; +} + +interface ArmBotEventsSchema { + modeluuid: string; + modelName: string; + type: 'ArmBot'; + points: { + uuid: string; + position: [number, number, number]; + actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[] }; + triggers: { uuid: string; name: string; type: string }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + }; + position: [number, number, number]; +} + +export type EventData = { + modeluuid: string; + modelname: string; + position: [number, number, number]; + rotation: { x: number; y: number; z: number }; + modelfileID: string; + isLocked: boolean; + isVisible: boolean; + eventData?: { + type: 'Conveyor'; + points: { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | []; + triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | []; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + }[]; + speed: number | string; + } | { + type: 'Vehicle'; + points: { + uuid: string; + position: [number, number, number]; + actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number }; + connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; + speed: number; + }; + }; +}