From 272317991ab5f5b76844d8c8b698c8cafae7a7b2 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 7 Apr 2025 18:16:34 +0530 Subject: [PATCH] feat: Extend simulation state types to include ArmBot events and update related components - Updated RotateControls and SelectionControls to handle ArmBot events in simulation states. - Enhanced PathConnector to manage connections involving ArmBot and StaticMachine types. - Added ArmBotMechanics and StaticMachineMechanics components for managing properties of ArmBot and StaticMachine. - Modified types in worldTypes to include rotation for ArmBot and StaticMachine events. - Updated store to accommodate new ArmBot event types in simulation states. --- .../layout/sidebarRight/SideBarRight.tsx | 42 +- .../mechanics/ArmBotMechanics.tsx | 90 + .../mechanics/ConveyorMechanics.tsx | 1616 ++++++++--------- .../mechanics/StaticMachineMechanics.tsx | 90 + .../ui/componets/RealTimeVisulization.tsx | 5 +- app/src/modules/builder/agv/pathNavigator.tsx | 37 +- .../geomentries/floors/addFloorToScene.ts | 2 +- .../geomentries/floors/loadOnlyFloors.ts | 2 +- .../builder/groups/floorItemsGroup.tsx | 677 +++---- .../modules/builder/groups/wallItemsGroup.tsx | 15 +- .../scene/IntialLoad/loadInitialFloorItems.ts | 36 +- .../controls/selection/copyPasteControls.tsx | 37 +- .../selection/duplicationControls.tsx | 37 +- .../scene/controls/selection/moveControls.tsx | 114 +- .../controls/selection/rotateControls.tsx | 116 +- .../controls/selection/selectionControls.tsx | 2 +- .../modules/simulation/path/pathConnector.tsx | 418 ++++- .../modules/simulation/path/pathCreation.tsx | 112 +- app/src/store/store.ts | 8 +- app/src/types/world/worldTypes.d.ts | 2 + 20 files changed, 2095 insertions(+), 1363 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx create mode 100644 app/src/components/layout/sidebarRight/mechanics/StaticMachineMechanics.tsx diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index 4821772..433052f 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -22,6 +22,8 @@ import GlobalProperties from "./properties/GlobalProperties"; import AsstePropertiies from "./properties/AssetProperties"; import ZoneProperties from "./properties/ZoneProperties"; import VehicleMechanics from "./mechanics/VehicleMechanics"; +import StaticMachineMechanics from "./mechanics/StaticMachineMechanics"; +import ArmBotMechanics from "./mechanics/ArmBotMechanics"; const SideBarRight: React.FC = () => { const { activeModule } = useModuleStore(); @@ -42,9 +44,8 @@ const SideBarRight: React.FC = () => {
{/* {activeModule === "builder" && ( */}
setSubModule("properties")} > @@ -53,25 +54,22 @@ const SideBarRight: React.FC = () => { {activeModule === "simulation" && ( <>
setSubModule("mechanics")} >
setSubModule("simulations")} >
setSubModule("analysis")} > @@ -132,10 +130,28 @@ const SideBarRight: React.FC = () => {
)} + {subModule === "mechanics" && + selectedActionSphere && + selectedActionSphere.path.type === "StaticMachine" && ( +
+
+ +
+
+ )} + {subModule === "mechanics" && + selectedActionSphere && + selectedActionSphere.path.type === "ArmBot" && ( +
+
+ +
+
+ )} {subModule === "mechanics" && !selectedActionSphere && (
- + {/* default */}
)} diff --git a/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx new file mode 100644 index 0000000..744d555 --- /dev/null +++ b/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx @@ -0,0 +1,90 @@ +import React, { useRef, useMemo } from "react"; +import { InfoIcon } from "../../../icons/ExportCommonIcons"; +import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; +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 ArmBotMechanics: React.FC = () => { + const { selectedActionSphere } = useSelectedActionSphere(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { socket } = useSocketStore(); + + const propertiesContainerRef = useRef(null); + + const { selectedPoint, connectedPointUuids } = useMemo(() => { + if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; + + const vehiclePaths = simulationStates.filter( + (path): path is Types.ArmBotEventsSchema => path.type === "ArmBot" + ); + + const points = vehiclePaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; + + if (!points) return { selectedPoint: null, connectedPointUuids: [] }; + + const connectedUuids: string[] = []; + if (points.connections?.targets) { + points.connections.targets.forEach(target => { + connectedUuids.push(target.pointUUID); + }); + } + + return { + selectedPoint: points, + connectedPointUuids: connectedUuids + }; + }, [selectedActionSphere, simulationStates]); + + const updateBackend = async (updatedPath: Types.ArmBotEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "ArmBot", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "ArmBot", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } + + + return ( +
+
+ {selectedActionSphere?.path?.modelName || "ArmBot point not found"} +
+ +
+
+
ArmBot Properties
+ + {selectedPoint && ( + <> + + + )} +
+ +
+ + Configure armbot properties. +
+
+
+ ); +}; + +export default React.memo(ArmBotMechanics); \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx index fd64db9..6c7487e 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx @@ -1,20 +1,20 @@ import React, { useRef, useState, useMemo, useEffect } from "react"; import { - AddIcon, - InfoIcon, - RemoveIcon, - ResizeHeightIcon, + AddIcon, + InfoIcon, + RemoveIcon, + ResizeHeightIcon, } from "../../../icons/ExportCommonIcons"; import RenameInput from "../../../ui/inputs/RenameInput"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import LabledDropdown from "../../../ui/inputs/LabledDropdown"; import { handleResize } from "../../../../functions/handleResizePannel"; import { - useFloorItems, - useSelectedActionSphere, - useSelectedPath, - useSimulationStates, - useSocketStore, + useFloorItems, + useSelectedActionSphere, + useSelectedPath, + useSimulationStates, + useSocketStore, } from "../../../../store/store"; import * as THREE from "three"; import * as Types from "../../../../types/world/worldTypes"; @@ -23,859 +23,857 @@ import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floo import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; const ConveyorMechanics: React.FC = () => { - const { selectedActionSphere } = useSelectedActionSphere(); - const { selectedPath, setSelectedPath } = useSelectedPath(); - const { simulationStates, setSimulationStates } = useSimulationStates(); - const { floorItems, setFloorItems } = useFloorItems(); - const { socket } = useSocketStore(); + const { selectedActionSphere } = useSelectedActionSphere(); + const { selectedPath, setSelectedPath } = useSelectedPath(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { floorItems, setFloorItems } = useFloorItems(); + const { socket } = useSocketStore(); - const actionsContainerRef = useRef(null); - const triggersContainerRef = useRef(null); + const actionsContainerRef = useRef(null); + const triggersContainerRef = useRef(null); - const selectedPoint = useMemo(() => { - if (!selectedActionSphere) return null; - return simulationStates - .filter( - (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" - ) - .flatMap((path) => path.points) - .find((point) => point.uuid === selectedActionSphere.points.uuid); - }, [selectedActionSphere, simulationStates]); + const selectedPoint = useMemo(() => { + if (!selectedActionSphere) return null; + return simulationStates + .filter( + (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" + ) + .flatMap((path) => path.points) + .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] : ""; + const updateBackend = async (updatedPath: Types.ConveyorEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; - // await setEventApi( - // organization, - // updatedPath.modeluuid, - // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } - // ); + // 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); - 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 = simulationStates.map((path) => { + if (path.type === "Conveyor") { + return { + ...path, + points: path.points.map((point) => { + if (point.uuid === selectedActionSphere.points.uuid) { + const actionIndex = point.actions.length; + const newAction = { + uuid: THREE.MathUtils.generateUUID(), + name: `Action ${actionIndex + 1}`, + type: "Inherit", + material: "Inherit", + delay: "Inherit", + spawnInterval: "Inherit", + isUsed: false, + }; - const handleAddAction = () => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationStates.map((path) => { - if (path.type === "Conveyor") { - return { - ...path, - points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.points.uuid) { - const actionIndex = point.actions.length; - const newAction = { - uuid: THREE.MathUtils.generateUUID(), - name: `Action ${actionIndex + 1}`, - type: "Inherit", - material: "Inherit", - delay: "Inherit", - spawnInterval: "Inherit", - isUsed: false, - }; - - return { ...point, actions: [...point.actions, newAction] }; + return { ...point, actions: [...point.actions, newAction] }; + } + return point; + }), + }; } - return point; - }), - }; - } - return path; - }); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationStates(updatedPaths); - }; - - const handleDeleteAction = (uuid: string) => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.filter( - (action) => action.uuid !== uuid - ), - } - : point - ), - } - : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationStates(updatedPaths); - }; - - const handleActionSelect = (uuid: string, actionType: string) => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid - ? { - ...action, - type: actionType, - material: - actionType === "Spawn" || actionType === "Swap" - ? "Inherit" - : action.material, - delay: - actionType === "Delay" ? "Inherit" : action.delay, - spawnInterval: - actionType === "Spawn" - ? "Inherit" - : action.spawnInterval, - } - : action - ), - } - : point - ), - } - : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationStates(updatedPaths); - - // Update the selected item to reflect changes - if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { - const updatedAction = updatedPaths - .filter( - (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" - ) - .flatMap((path) => path.points) - .find((p) => p.uuid === selectedActionSphere.points.uuid) - ?.actions.find((a) => a.uuid === uuid); - - if (updatedAction) { - setSelectedItem({ - type: "action", - item: updatedAction, + return path; }); - } - } - }; - // Modified handleMaterialSelect to ensure it only applies to relevant action types - const handleMaterialSelect = (uuid: string, material: string) => { - if (!selectedActionSphere) return; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid && - (action.type === "Spawn" || action.type === "Swap") - ? { ...action, material } - : action - ), - } - : point - ), - } - : path - ); + setSimulationStates(updatedPaths); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const handleDeleteAction = (uuid: string) => { + if (!selectedActionSphere) return; - setSimulationStates(updatedPaths); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.filter( + (action) => action.uuid !== uuid + ), + } + : point + ), + } + : path + ); - // Update selected item if it's the current action - if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - material, - }, - }); - } - }; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const handleDelayChange = (uuid: string, delay: number | string) => { - if (!selectedActionSphere) return; + setSimulationStates(updatedPaths); + }; - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid ? { ...action, delay } : action - ), - } - : point - ), - } - : path - ); + const handleActionSelect = (uuid: string, actionType: string) => { + if (!selectedActionSphere) return; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { + ...action, + type: actionType, + material: + actionType === "Spawn" || actionType === "Swap" + ? "Inherit" + : action.material, + delay: + actionType === "Delay" ? "Inherit" : action.delay, + spawnInterval: + actionType === "Spawn" + ? "Inherit" + : action.spawnInterval, + } + : action + ), + } + : point + ), + } + : path + ); - setSimulationStates(updatedPaths); - }; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const handleSpawnIntervalChange = ( - uuid: string, - spawnInterval: number | string - ) => { - if (!selectedActionSphere) return; + setSimulationStates(updatedPaths); - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => - action.uuid === uuid - ? { ...action, spawnInterval } - : action - ), - } - : point - ), - } - : path - ); + // Update the selected item to reflect changes + if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { + const updatedAction = updatedPaths + .filter( + (path): path is Types.ConveyorEventsSchema => path.type === "Conveyor" + ) + .flatMap((path) => path.points) + .find((p) => p.uuid === selectedActionSphere.points.uuid) + ?.actions.find((a) => a.uuid === uuid); - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationStates(updatedPaths); - }; - - const handleSpeedChange = (speed: number | string) => { - if (!selectedPath) return; - - const updatedPaths = simulationStates.map((path) => - path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationStates(updatedPaths); - setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); - }; - - const handleAddTrigger = () => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => { - if (point.uuid === selectedActionSphere.points.uuid) { - const triggerIndex = point.triggers.length; - const newTrigger = { - uuid: THREE.MathUtils.generateUUID(), - name: `Trigger ${triggerIndex + 1}`, - type: "", - bufferTime: 0, - isUsed: false, - }; - - return { ...point, triggers: [...point.triggers, newTrigger] }; + if (updatedAction) { + setSelectedItem({ + type: "action", + item: updatedAction, + }); } - return point; - }), } - : path - ); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + // Modified handleMaterialSelect to ensure it only applies to relevant action types + const handleMaterialSelect = (uuid: string, material: string) => { + if (!selectedActionSphere) return; - setSimulationStates(updatedPaths); - }; + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid && + (action.type === "Spawn" || action.type === "Swap") + ? { ...action, material } + : action + ), + } + : point + ), + } + : path + ); - const handleDeleteTrigger = (uuid: string) => { - if (!selectedActionSphere) return; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.filter( - (trigger) => trigger.uuid !== uuid - ), - } - : point - ), + setSimulationStates(updatedPaths); + + // Update selected item if it's the current action + if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + material, + }, + }); } - : path - ); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const handleDelayChange = (uuid: string, delay: number | string) => { + if (!selectedActionSphere) return; - setSimulationStates(updatedPaths); - }; + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid ? { ...action, delay } : action + ), + } + : point + ), + } + : path + ); - const handleTriggerSelect = (uuid: string, triggerType: string) => { - if (!selectedActionSphere) return; + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid - ? { ...trigger, type: triggerType } - : trigger - ), - } - : point - ), - } - : path - ); + setSimulationStates(updatedPaths); + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const handleSpawnIntervalChange = ( + uuid: string, + spawnInterval: number | string + ) => { + if (!selectedActionSphere) return; - setSimulationStates(updatedPaths); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => + action.uuid === uuid + ? { ...action, spawnInterval } + : action + ), + } + : point + ), + } + : path + ); - // Ensure the selectedItem is updated immediately - const updatedTrigger = updatedPaths - .flatMap((path) => (path.type === "Conveyor" ? path.points : [])) - .flatMap((point) => point.triggers) - .find((trigger) => trigger.uuid === uuid); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - if (updatedTrigger) { - setSelectedItem({ type: "trigger", item: updatedTrigger }); - } - }; + setSimulationStates(updatedPaths); + }; - // Update the toggle handlers to immediately update the selected item - const handleActionToggle = (uuid: string) => { - if (!selectedActionSphere) return; - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - actions: point.actions.map((action) => ({ - ...action, - isUsed: action.uuid === uuid ? !action.isUsed : false, - })), - } - : point - ), - } - : path - ); + const handleSpeedChange = (speed: number | string) => { + if (!selectedPath) return; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); + const updatedPaths = simulationStates.map((path) => + path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path + ); - setSimulationStates(updatedPaths); + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.modeluuid === selectedPath.path.modeluuid + ); + updateBackend(updatedPath); - // Immediately update the selected item if it's the one being toggled - if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - isUsed: !selectedItem.item.isUsed, - }, - }); - } - }; + setSimulationStates(updatedPaths); + setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } }); + }; - // Do the same for trigger toggle - const handleTriggerToggle = (uuid: string) => { - if (!selectedActionSphere) return; + const handleAddTrigger = () => { + if (!selectedActionSphere) return; - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => ({ - ...trigger, - isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, - })), - } - : point - ), - } - : path - ); + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => { + if (point.uuid === selectedActionSphere.points.uuid) { + const triggerIndex = point.triggers.length; + const newTrigger = { + uuid: THREE.MathUtils.generateUUID(), + name: `Trigger ${triggerIndex + 1}`, + type: "", + bufferTime: 0, + isUsed: false, + }; - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationStates(updatedPaths); - - // Immediately update the selected item if it's the one being toggled - if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - isUsed: !selectedItem.item.isUsed, - }, - }); - } - }; - - const handleTriggerBufferTimeChange = (uuid: string, bufferTime: number) => { - if (!selectedActionSphere) return; - - const updatedPaths = simulationStates.map((path) => - path.type === "Conveyor" - ? { - ...path, - points: path.points.map((point) => - point.uuid === selectedActionSphere.points.uuid - ? { - ...point, - triggers: point.triggers.map((trigger) => - trigger.uuid === uuid - ? { ...trigger, bufferTime } - : trigger - ), - } - : point - ), - } - : path - ); - - const updatedPath = updatedPaths.find( - (path): path is Types.ConveyorEventsSchema => - path.type === "Conveyor" && - path.points.some( - (point) => point.uuid === selectedActionSphere.points.uuid - ) - ); - updateBackend(updatedPath); - - setSimulationStates(updatedPaths); - - // Immediately update selectedItem if it's the currently selected trigger - if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { - setSelectedItem({ - ...selectedItem, - item: { - ...selectedItem.item, - bufferTime, - }, - }); - } - }; - - const [selectedItem, setSelectedItem] = useState<{ - type: "action" | "trigger"; - item: any; - } | null>(null); - - useEffect(() => { - setSelectedItem(null); - }, [selectedActionSphere]); - - return ( -
- {!selectedPath && ( -
- {selectedActionSphere?.path?.modelName || "point name not found"} -
- )} - - {selectedPath && ( -
- {selectedPath.path.modelName || "path name not found"} -
- )} - -
- {!selectedPath && ( - <> -
-
-
Actions
-
- Add -
-
-
-
- {selectedPoint?.actions.map((action) => ( -
-
- setSelectedItem({ type: "action", item: action }) + return { ...point, triggers: [...point.triggers, newTrigger] }; } - > - - -
-
handleDeleteAction(action.uuid)} - > - -
-
- ))} -
-
handleResize(e, actionsContainerRef)} - > - -
-
-
-
-
-
Triggers
-
- Add -
-
-
-
- {selectedPoint?.triggers.map((trigger) => ( -
-
- setSelectedItem({ type: "trigger", item: trigger }) - } - > - - -
-
handleDeleteTrigger(trigger.uuid)} - > - -
-
- ))} -
-
handleResize(e, triggersContainerRef)} - > - -
-
-
- - )} + return point; + }), + } + : path + ); -
- {selectedItem && ( - <> -
{selectedItem.item.name}
+ const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); - {selectedItem.type === "action" && ( - <> - handleActionToggle(selectedItem.item.uuid)} - /> - - handleActionSelect(selectedItem.item.uuid, option) - } - /> + setSimulationStates(updatedPaths); + }; - {/* Only show material dropdown for Spawn/Swap actions */} - {(selectedItem.item.type === "Spawn" || - selectedItem.item.type === "Swap") && ( - - handleMaterialSelect(selectedItem.item.uuid, option) - } - /> + const handleDeleteTrigger = (uuid: string) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.filter( + (trigger) => trigger.uuid !== uuid + ), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + }; + + const handleTriggerSelect = (uuid: string, triggerType: string) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, type: triggerType } + : trigger + ), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Ensure the selectedItem is updated immediately + const updatedTrigger = updatedPaths + .flatMap((path) => (path.type === "Conveyor" ? path.points : [])) + .flatMap((point) => point.triggers) + .find((trigger) => trigger.uuid === uuid); + + if (updatedTrigger) { + setSelectedItem({ type: "trigger", item: updatedTrigger }); + } + }; + + // Update the toggle handlers to immediately update the selected item + const handleActionToggle = (uuid: string) => { + if (!selectedActionSphere) return; + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + actions: point.actions.map((action) => ({ + ...action, + isUsed: action.uuid === uuid ? !action.isUsed : false, + })), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Immediately update the selected item if it's the one being toggled + if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + isUsed: !selectedItem.item.isUsed, + }, + }); + } + }; + + // Do the same for trigger toggle + const handleTriggerToggle = (uuid: string) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => ({ + ...trigger, + isUsed: trigger.uuid === uuid ? !trigger.isUsed : false, + })), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Immediately update the selected item if it's the one being toggled + if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + isUsed: !selectedItem.item.isUsed, + }, + }); + } + }; + + const handleTriggerBufferTimeChange = (uuid: string, bufferTime: number) => { + if (!selectedActionSphere) return; + + const updatedPaths = simulationStates.map((path) => + path.type === "Conveyor" + ? { + ...path, + points: path.points.map((point) => + point.uuid === selectedActionSphere.points.uuid + ? { + ...point, + triggers: point.triggers.map((trigger) => + trigger.uuid === uuid + ? { ...trigger, bufferTime } + : trigger + ), + } + : point + ), + } + : path + ); + + const updatedPath = updatedPaths.find( + (path): path is Types.ConveyorEventsSchema => + path.type === "Conveyor" && + path.points.some( + (point) => point.uuid === selectedActionSphere.points.uuid + ) + ); + updateBackend(updatedPath); + + setSimulationStates(updatedPaths); + + // Immediately update selectedItem if it's the currently selected trigger + if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) { + setSelectedItem({ + ...selectedItem, + item: { + ...selectedItem.item, + bufferTime, + }, + }); + } + }; + + const [selectedItem, setSelectedItem] = useState<{ + type: "action" | "trigger"; + item: any; + } | null>(null); + + useEffect(() => { + setSelectedItem(null); + }, [selectedActionSphere]); + + return ( +
+ {!selectedPath && ( +
+ {selectedActionSphere?.path?.modelName || "point name not found"} +
+ )} + + {selectedPath && ( +
+ {selectedPath.path.modelName || "path name not found"} +
+ )} + +
+ {!selectedPath && ( + <> +
+
+
Actions
+
+ Add +
+
+
+
+ {selectedPoint?.actions.map((action) => ( +
+
+ setSelectedItem({ type: "action", item: action }) + } + > + + +
+
handleDeleteAction(action.uuid)} + > + +
+
+ ))} +
+
handleResize(e, actionsContainerRef)} + > + +
+
+
+
+
+
Triggers
+
+ Add +
+
+
+
+ {selectedPoint?.triggers.map((trigger) => ( +
+
+ setSelectedItem({ type: "trigger", item: trigger }) + } + > + + +
+
handleDeleteTrigger(trigger.uuid)} + > + +
+
+ ))} +
+
handleResize(e, triggersContainerRef)} + > + +
+
+
+ + )} + +
+ {selectedItem && ( + <> +
{selectedItem.item.name}
+ + {selectedItem.type === "action" && ( + <> + handleActionToggle(selectedItem.item.uuid)} + /> + + handleActionSelect(selectedItem.item.uuid, option) + } + /> + + {/* Only show material dropdown for Spawn/Swap actions */} + {(selectedItem.item.type === "Spawn" || + selectedItem.item.type === "Swap") && ( + + handleMaterialSelect(selectedItem.item.uuid, option) + } + /> + )} + + {/* Only show delay input for Delay actions */} + {selectedItem.item.type === "Delay" && ( + { + const numValue = parseInt(value); + handleDelayChange( + selectedItem.item.uuid, + !value ? "Inherit" : numValue + ); + }} + /> + )} + + {/* Only show spawn interval for Spawn actions */} + {selectedItem.item.type === "Spawn" && ( + { + handleSpawnIntervalChange( + selectedItem.item.uuid, + value === "" ? "Inherit" : parseInt(value) + ); + }} + /> + )} + + )} + + {selectedItem.type === "trigger" && ( + <> + handleTriggerToggle(selectedItem.item.uuid)} + /> + + + handleTriggerSelect(selectedItem.item.uuid, option) + } + /> + + {selectedItem.item.type === "Buffer" && ( + { + handleTriggerBufferTimeChange( + selectedItem.item.uuid, + parseInt(value) + ); + }} + /> + )} + + )} + )} - {/* Only show delay input for Delay actions */} - {selectedItem.item.type === "Delay" && ( - { - const numValue = parseInt(value); - handleDelayChange( - selectedItem.item.uuid, - !value ? "Inherit" : numValue - ); - }} - /> - )} - - {/* Only show spawn interval for Spawn actions */} - {selectedItem.item.type === "Spawn" && ( - { - handleSpawnIntervalChange( - selectedItem.item.uuid, - value === "" ? "Inherit" : parseInt(value) - ); - }} - /> - )} - - )} - - {selectedItem.type === "trigger" && ( - <> - handleTriggerToggle(selectedItem.item.uuid)} - /> - - - handleTriggerSelect(selectedItem.item.uuid, option) - } - /> - - {selectedItem.item.type === "Buffer" && ( - { - handleTriggerBufferTimeChange( - selectedItem.item.uuid, - parseInt(value) - ); - }} - /> - )} - - )} - - )} - - {selectedPath && !selectedItem && ( -
- - handleSpeedChange(value === "" ? "Inherit" : parseInt(value)) - } - /> + {selectedPath && !selectedItem && ( +
+ + handleSpeedChange(value === "" ? "Inherit" : parseInt(value)) + } + /> +
+ )} +
+ {!selectedPath && ( +
+ + Configure the point's action and trigger properties. +
+ )} + {selectedPath && ( +
+ + Configure the path properties. +
+ )}
- )}
- {!selectedPath && ( -
- - Configure the point's action and trigger properties. -
- )} - {selectedPath && ( -
- - Configure the path properties. -
- )} -
-
- ); + ); }; export default ConveyorMechanics; diff --git a/app/src/components/layout/sidebarRight/mechanics/StaticMachineMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/StaticMachineMechanics.tsx new file mode 100644 index 0000000..254753e --- /dev/null +++ b/app/src/components/layout/sidebarRight/mechanics/StaticMachineMechanics.tsx @@ -0,0 +1,90 @@ +import React, { useRef, useMemo } from "react"; +import { InfoIcon } from "../../../icons/ExportCommonIcons"; +import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; +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 StaticMachineMechanics: React.FC = () => { + const { selectedActionSphere } = useSelectedActionSphere(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { socket } = useSocketStore(); + + const propertiesContainerRef = useRef(null); + + const { selectedPoint, connectedPointUuids } = useMemo(() => { + if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] }; + + const vehiclePaths = simulationStates.filter( + (path): path is Types.StaticMachineEventsSchema => path.type === "StaticMachine" + ); + + const points = vehiclePaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; + + if (!points) return { selectedPoint: null, connectedPointUuids: [] }; + + const connectedUuids: string[] = []; + if (points.connections?.targets) { + points.connections.targets.forEach(target => { + connectedUuids.push(target.pointUUID); + }); + } + + return { + selectedPoint: points, + connectedPointUuids: connectedUuids + }; + }, [selectedActionSphere, simulationStates]); + + const updateBackend = async (updatedPath: Types.StaticMachineEventsSchema | undefined) => { + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; + + // await setEventApi( + // organization, + // updatedPath.modeluuid, + // { type: "StaticMachine", points: updatedPath.points } + // ); + + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "StaticMachine", points: updatedPath.points } + } + + socket.emit('v2:model-asset:updateEventData', data); + + } + + + return ( +
+
+ {selectedActionSphere?.path?.modelName || "Machine point not found"} +
+ +
+
+
Machine Properties
+ + {selectedPoint && ( + <> + + + )} +
+ +
+ + Configure machine properties. +
+
+
+ ); +}; + +export default React.memo(StaticMachineMechanics); \ No newline at end of file diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 5a47964..c28b8ad 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -64,9 +64,7 @@ const RealTimeVisulization: React.FC = () => { const { rightClickSelected, setRightClickSelected } = useRightClickSelected() const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false); - const [floatingWidgets, setFloatingWidgets] = useState< - Record - >({}); + const [floatingWidgets, setFloatingWidgets] = useState>({}); const { widgetSelect, setWidgetSelect } = useAsset3dWidget(); const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); const { visualizationSocket } = useSocketStore(); @@ -78,7 +76,6 @@ const RealTimeVisulization: React.FC = () => { const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); - // console.log('response: ', response); if (!Array.isArray(response)) { return; diff --git a/app/src/modules/builder/agv/pathNavigator.tsx b/app/src/modules/builder/agv/pathNavigator.tsx index 7d7984c..6369a87 100644 --- a/app/src/modules/builder/agv/pathNavigator.tsx +++ b/app/src/modules/builder/agv/pathNavigator.tsx @@ -29,12 +29,15 @@ export default function PathNavigator({ const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]); const [initialPosition, setInitialPosition] = useState(null); const [initialRotation, setInitialRotation] = useState(null); - + const [targetPosition] = useState(new THREE.Vector3()); + const [smoothPosition] = useState(new THREE.Vector3()); + const [targetQuaternion] = useState(new THREE.Quaternion()); const distancesRef = useRef([]); const totalDistanceRef = useRef(0); const progressRef = useRef(0); const isWaiting = useRef(false); const timeoutRef = useRef(null); + const pathTransitionProgress = useRef(0); const { scene } = useThree(); const { isPlaying } = usePlayButtonStore(); @@ -44,6 +47,9 @@ export default function PathNavigator({ if (object) { setInitialPosition(object.position.clone()); setInitialRotation(object.rotation.clone()); + smoothPosition.copy(object.position.clone()); + targetPosition.copy(object.position.clone()); + targetQuaternion.setFromEuler(object.rotation.clone()); } }, [scene, id]); @@ -65,22 +71,23 @@ export default function PathNavigator({ setPath([]); setCurrentPhase('initial'); - setPickupDropPath([]); - setDropPickupPath([]); distancesRef.current = []; totalDistanceRef.current = 0; progressRef.current = 0; isWaiting.current = false; + pathTransitionProgress.current = 0; - if (initialPosition && initialRotation) { - const object = scene.getObjectByProperty("uuid", id); - if (object) { - object.position.copy(initialPosition); - object.rotation.copy(initialRotation); - } + const object = scene.getObjectByProperty("uuid", id); + if (object && initialPosition && initialRotation) { + object.position.copy(initialPosition); + object.rotation.copy(initialRotation); + smoothPosition.copy(initialPosition); + targetPosition.copy(initialPosition); + targetQuaternion.setFromEuler(initialRotation); } }; + useEffect(() => { if (!isPlaying) { resetState(); @@ -171,16 +178,16 @@ export default function PathNavigator({ 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); + targetPosition.copy(start).lerp(end, t); + + smoothPosition.lerp(targetPosition, 0.1); + object.position.copy(smoothPosition); 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; + targetQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), targetRotationY); + object.quaternion.slerp(targetQuaternion, 0.1); }); useEffect(() => { diff --git a/app/src/modules/builder/geomentries/floors/addFloorToScene.ts b/app/src/modules/builder/geomentries/floors/addFloorToScene.ts index e2f0baa..c951ba0 100644 --- a/app/src/modules/builder/geomentries/floors/addFloorToScene.ts +++ b/app/src/modules/builder/geomentries/floors/addFloorToScene.ts @@ -53,7 +53,7 @@ export default function addFloorToScene( const mesh = new THREE.Mesh(geometry, material); mesh.receiveShadow = true; - mesh.position.y = layer; + mesh.position.y = (layer) * CONSTANTS.wallConfig.height; mesh.rotateX(Math.PI / 2); mesh.name = `Floor_Layer_${layer}`; diff --git a/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts b/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts index 8f33b57..9400be2 100644 --- a/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts +++ b/app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts @@ -171,7 +171,7 @@ function loadOnlyFloors( mesh.castShadow = true; mesh.receiveShadow = true; - mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height + 0.03; + mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height; mesh.rotateX(Math.PI / 2); mesh.name = `Only_Floor_Line_${floor[0][0][2]}`; diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index 96f4fad..6289bd0 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -1,26 +1,11 @@ import { useFrame, useThree } from "@react-three/fiber"; -import { - useActiveTool, - useAsset3dWidget, - useCamMode, - useDeletableFloorItem, - useDeleteModels, - useFloorItems, - useLoadingProgress, - useRenderDistance, - useselectedFloorItem, - useSelectedItem, - useSimulationStates, - useSocketStore, - useToggleView, - useTransformMode, -} from "../../../store/store"; +import { useActiveTool, useAsset3dWidget, useCamMode, useDeletableFloorItem, useDeleteModels, useFloorItems, useLoadingProgress, useRenderDistance, useselectedFloorItem, useSelectedItem, useSimulationStates, useSocketStore, useToggleView, useTransformMode, } from "../../../store/store"; import assetVisibility from "../geomentries/assets/assetVisibility"; import { useEffect } from "react"; import * as THREE from "three"; import * as Types from "../../../types/world/worldTypes"; import assetManager, { - cancelOngoingTasks, + cancelOngoingTasks, } from "../geomentries/assets/assetManager"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; @@ -31,413 +16,313 @@ import addAssetModel from "../geomentries/assets/addAssetModel"; import { getFloorAssets } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi"; import useModuleStore from "../../../store/useModuleStore"; // import { retrieveGLTF } from "../../../utils/indexDB/idbUtils"; -const assetManagerWorker = new Worker( - new URL( - "../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", - import.meta.url - ) -); -const gltfLoaderWorker = new Worker( - new URL( - "../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", - import.meta.url - ) -); +const assetManagerWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", import.meta.url)); +const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url)); -const FloorItemsGroup = ({ - itemsGroup, - hoveredDeletableFloorItem, - AttachedObject, - floorGroup, - tempLoader, - isTempLoader, - plane, -}: any) => { - const state: Types.ThreeState = useThree(); - const { raycaster, controls }: any = state; - const { renderDistance } = useRenderDistance(); - const { toggleView } = useToggleView(); - const { floorItems, setFloorItems } = useFloorItems(); - const { camMode } = useCamMode(); - const { deleteModels } = useDeleteModels(); - const { setDeletableFloorItem } = useDeletableFloorItem(); - const { transformMode } = useTransformMode(); - const { setselectedFloorItem } = useselectedFloorItem(); - const { activeTool } = useActiveTool(); - const { selectedItem, setSelectedItem } = useSelectedItem(); - const { simulationStates, setSimulationStates } = useSimulationStates(); - const { setLoadingProgress } = useLoadingProgress(); - const { activeModule } = useModuleStore(); - const { socket } = useSocketStore(); - const loader = new GLTFLoader(); - const dracoLoader = new DRACOLoader(); +const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject, floorGroup, tempLoader, isTempLoader, plane, }: any) => { + const state: Types.ThreeState = useThree(); + const { raycaster, controls }: any = state; + const { renderDistance } = useRenderDistance(); + const { toggleView } = useToggleView(); + const { floorItems, setFloorItems } = useFloorItems(); + const { camMode } = useCamMode(); + const { deleteModels } = useDeleteModels(); + const { setDeletableFloorItem } = useDeletableFloorItem(); + const { transformMode } = useTransformMode(); + const { setselectedFloorItem } = useselectedFloorItem(); + const { activeTool } = useActiveTool(); + const { selectedItem, setSelectedItem } = useSelectedItem(); + const { simulationStates, setSimulationStates } = useSimulationStates(); + const { setLoadingProgress } = useLoadingProgress(); + const { activeModule } = useModuleStore(); + const { socket } = useSocketStore(); + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader(); - dracoLoader.setDecoderPath( - "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/" - ); - loader.setDRACOLoader(dracoLoader); + dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"); + loader.setDRACOLoader(dracoLoader); - useEffect(() => { - const email = localStorage.getItem("email"); - const organization = email!.split("@")[1].split(".")[0]; + useEffect(() => { + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; - let totalAssets = 0; - let loadedAssets = 0; + let totalAssets = 0; + let loadedAssets = 0; - const updateLoadingProgress = (progress: number) => { - if (progress < 100) { - setLoadingProgress(progress); - } else if (progress === 100) { - setTimeout(() => { - setLoadingProgress(100); - setTimeout(() => { - setLoadingProgress(0); - }, 1500); - }, 1000); - } - }; + const updateLoadingProgress = (progress: number) => { + if (progress < 100) { + setLoadingProgress(progress); + } else if (progress === 100) { + setTimeout(() => { + setLoadingProgress(100); + setTimeout(() => { + setLoadingProgress(0); + }, 1500); + }, 1000); + } + }; - getFloorAssets(organization).then((data) => { - if (data.length > 0) { - const uniqueItems = (data as Types.FloorItems).filter( - (item, index, self) => - index === self.findIndex((t) => t.modelfileID === item.modelfileID) - ); - totalAssets = uniqueItems.length; - if (totalAssets === 0) { - updateLoadingProgress(100); - return; - } - gltfLoaderWorker.postMessage({ floorItems: data }); - } else { - gltfLoaderWorker.postMessage({ floorItems: [] }); - loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); - updateLoadingProgress(100); - } - }); - - gltfLoaderWorker.onmessage = async (event) => { - if (event.data.message === "gltfLoaded" && event.data.modelBlob) { - const blobUrl = URL.createObjectURL(event.data.modelBlob); - - loader.load(blobUrl, (gltf) => { - URL.revokeObjectURL(blobUrl); - THREE.Cache.remove(blobUrl); - THREE.Cache.add(event.data.modelID, gltf); - - loadedAssets++; - const progress = Math.round((loadedAssets / totalAssets) * 100); - updateLoadingProgress(progress); - - if (loadedAssets === totalAssets) { - loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); - updateLoadingProgress(100); - } + getFloorAssets(organization).then((data) => { + if (data.length > 0) { + const uniqueItems = (data as Types.FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.modelfileID === item.modelfileID)); + totalAssets = uniqueItems.length; + if (totalAssets === 0) { + updateLoadingProgress(100); + return; + } + gltfLoaderWorker.postMessage({ floorItems: data }); + } else { + gltfLoaderWorker.postMessage({ floorItems: [] }); + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); + updateLoadingProgress(100); + } }); - } - }; - }, []); - useEffect(() => { - assetManagerWorker.onmessage = async (event) => { - cancelOngoingTasks(); // Cancel the ongoing process - await assetManager(event.data, itemsGroup, loader); - }; - }, [assetManagerWorker]); + gltfLoaderWorker.onmessage = async (event) => { + if (event.data.message === "gltfLoaded" && event.data.modelBlob) { + const blobUrl = URL.createObjectURL(event.data.modelBlob); - useEffect(() => { - if (toggleView) return; + loader.load(blobUrl, (gltf) => { + URL.revokeObjectURL(blobUrl); + THREE.Cache.remove(blobUrl); + THREE.Cache.add(event.data.modelID, gltf); - const uuids: string[] = []; - itemsGroup.current?.children.forEach((child: any) => { - uuids.push(child.uuid); - }); - const cameraPosition = state.camera.position; + loadedAssets++; + const progress = Math.round((loadedAssets / totalAssets) * 100); + updateLoadingProgress(progress); - assetManagerWorker.postMessage({ - floorItems, - cameraPosition, - uuids, - renderDistance, - }); - }, [camMode, renderDistance]); + if (loadedAssets === totalAssets) { + loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates); + updateLoadingProgress(100); + } + }); + } + }; + }, []); - useEffect(() => { - const controls: any = state.controls; - const camera: any = state.camera; + useEffect(() => { + assetManagerWorker.onmessage = async (event) => { + cancelOngoingTasks(); // Cancel the ongoing process + await assetManager(event.data, itemsGroup, loader); + }; + }, [assetManagerWorker]); - if (controls) { - let intervalId: NodeJS.Timeout | null = null; - - const handleChange = () => { + useEffect(() => { if (toggleView) return; const uuids: string[] = []; - itemsGroup.current?.children.forEach((child: any) => { - uuids.push(child.uuid); - }); - const cameraPosition = camera.position; + itemsGroup.current?.children.forEach((child: any) => { uuids.push(child.uuid); }); + const cameraPosition = state.camera.position; - assetManagerWorker.postMessage({ - floorItems, - cameraPosition, - uuids, - renderDistance, - }); - }; + assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance, }); + }, [camMode, renderDistance]); - const startInterval = () => { - if (!intervalId) { - intervalId = setInterval(handleChange, 50); + useEffect(() => { + const controls: any = state.controls; + const camera: any = state.camera; + + if (controls) { + let intervalId: NodeJS.Timeout | null = null; + + const handleChange = () => { + if (toggleView) return; + + const uuids: string[] = []; + itemsGroup.current?.children.forEach((child: any) => { uuids.push(child.uuid); }); + const cameraPosition = camera.position; + + assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance, }); + }; + + const startInterval = () => { + if (!intervalId) { + intervalId = setInterval(handleChange, 50); + } + }; + + const stopInterval = () => { + handleChange(); + if (intervalId) { + clearInterval(intervalId); + intervalId = null; + } + }; + + controls.addEventListener("rest", handleChange); + controls.addEventListener("rest", stopInterval); + controls.addEventListener("control", startInterval); + controls.addEventListener("controlend", stopInterval); + + return () => { + controls.removeEventListener("rest", handleChange); + controls.removeEventListener("rest", stopInterval); + controls.removeEventListener("control", startInterval); + controls.removeEventListener("controlend", stopInterval); + if (intervalId) { + clearInterval(intervalId); + } + }; } - }; + }, [state.controls, floorItems, toggleView, renderDistance]); - const stopInterval = () => { - handleChange(); - if (intervalId) { - clearInterval(intervalId); - intervalId = null; - } - }; + useEffect(() => { + const canvasElement = state.gl.domElement; + let drag = false; + let isLeftMouseDown = false; - controls.addEventListener("rest", handleChange); - controls.addEventListener("rest", stopInterval); - controls.addEventListener("control", startInterval); - controls.addEventListener("controlend", stopInterval); - - return () => { - controls.removeEventListener("rest", handleChange); - controls.removeEventListener("rest", stopInterval); - controls.removeEventListener("control", startInterval); - controls.removeEventListener("controlend", stopInterval); - if (intervalId) { - clearInterval(intervalId); - } - }; - } - }, [state.controls, floorItems, toggleView, renderDistance]); - - useEffect(() => { - const canvasElement = state.gl.domElement; - let drag = false; - let isLeftMouseDown = false; - - const onMouseDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = true; - drag = false; - } - }; - - const onMouseMove = () => { - if (isLeftMouseDown) { - drag = true; - } - }; - - const onMouseUp = async (evt: any) => { - if (controls) { - (controls as any).enabled = true; - } - if (evt.button === 0) { - isLeftMouseDown = false; - if (drag) return; - - if (deleteModels) { - DeleteFloorItems( - itemsGroup, - hoveredDeletableFloorItem, - setFloorItems, - setSimulationStates, - socket - ); - } - const Mode = transformMode; - - if (Mode !== null || activeTool === "cursor") { - if (!itemsGroup.current) return; - let intersects = raycaster.intersectObjects( - itemsGroup.current.children, - true - ); - if ( - intersects.length > 0 && - intersects[0]?.object?.parent?.parent?.position && - intersects[0]?.object?.parent?.parent?.scale && - intersects[0]?.object?.parent?.parent?.rotation - ) { - // let currentObject = intersects[0].object; - // while (currentObject) { - // if (currentObject.name === "Scene") { - // break; - // } - // currentObject = currentObject.parent as THREE.Object3D; - // } - // if (currentObject) { - // AttachedObject.current = currentObject as any; - // setselectedFloorItem(AttachedObject.current!); - // } - } else { - const target = controls.getTarget(new THREE.Vector3()); - await controls.setTarget(target.x, 0, target.z, true); - setselectedFloorItem(null); - } - } - } - }; - - const onDblClick = async (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown = false; - if (drag) return; - - const Mode = transformMode; - - if (Mode !== null || activeTool === "cursor") { - if (!itemsGroup.current) return; - let intersects = raycaster.intersectObjects( - itemsGroup.current.children, - true - ); - if ( - intersects.length > 0 && - intersects[0]?.object?.parent?.parent?.position && - intersects[0]?.object?.parent?.parent?.scale && - intersects[0]?.object?.parent?.parent?.rotation - ) { - let currentObject = intersects[0].object; - - while (currentObject) { - if (currentObject.name === "Scene") { - break; - } - currentObject = currentObject.parent as THREE.Object3D; + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown = true; + drag = false; } - if (currentObject) { - AttachedObject.current = currentObject as any; - // controls.fitToSphere(AttachedObject.current!, true); + }; - const bbox = new THREE.Box3().setFromObject( - AttachedObject.current - ); - const size = bbox.getSize(new THREE.Vector3()); - const center = bbox.getCenter(new THREE.Vector3()); - - const front = new THREE.Vector3(0, 0, 1); - AttachedObject.current.localToWorld(front); - front.sub(AttachedObject.current.position).normalize(); - - const distance = Math.max(size.x, size.y, size.z) * 2; - const newPosition = center - .clone() - .addScaledVector(front, distance); - - controls.setPosition( - newPosition.x, - newPosition.y, - newPosition.z, - true - ); - controls.setTarget(center.x, center.y, center.z, true); - controls.fitToBox(AttachedObject.current!, true, { - cover: true, - paddingTop: 5, - paddingLeft: 5, - paddingBottom: 5, - paddingRight: 5, - }); - - setselectedFloorItem(AttachedObject.current!); + const onMouseMove = () => { + if (isLeftMouseDown) { + drag = true; + } + }; + + const onMouseUp = async (evt: any) => { + if (controls) { + (controls as any).enabled = true; + } + if (evt.button === 0) { + isLeftMouseDown = false; + if (drag) return; + + if (deleteModels) { + DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, setSimulationStates, socket); + } + const Mode = transformMode; + + if (Mode !== null || activeTool === "cursor") { + if (!itemsGroup.current) return; + let intersects = raycaster.intersectObjects( + itemsGroup.current.children, + true + ); + if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { + // let currentObject = intersects[0].object; + // while (currentObject) { + // if (currentObject.name === "Scene") { + // break; + // } + // currentObject = currentObject.parent as THREE.Object3D; + // } + // if (currentObject) { + // AttachedObject.current = currentObject as any; + // setselectedFloorItem(AttachedObject.current!); + // } + } else { + const target = controls.getTarget(new THREE.Vector3()); + await controls.setTarget(target.x, 0, target.z, true); + setselectedFloorItem(null); + } + } + } + }; + + const onDblClick = async (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown = false; + if (drag) return; + + const Mode = transformMode; + + if (Mode !== null || activeTool === "cursor") { + if (!itemsGroup.current) return; + let intersects = raycaster.intersectObjects(itemsGroup.current.children, true); + if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { + let currentObject = intersects[0].object; + + while (currentObject) { + if (currentObject.name === "Scene") { + break; + } + currentObject = currentObject.parent as THREE.Object3D; + } + if (currentObject) { + AttachedObject.current = currentObject as any; + // controls.fitToSphere(AttachedObject.current!, true); + + const bbox = new THREE.Box3().setFromObject(AttachedObject.current); + const size = bbox.getSize(new THREE.Vector3()); + const center = bbox.getCenter(new THREE.Vector3()); + + const front = new THREE.Vector3(0, 0, 1); + AttachedObject.current.localToWorld(front); + front.sub(AttachedObject.current.position).normalize(); + + const distance = Math.max(size.x, size.y, size.z) * 2; + const newPosition = center.clone().addScaledVector(front, distance); + + controls.setPosition(newPosition.x, newPosition.y, newPosition.z, true); + controls.setTarget(center.x, center.y, center.z, true); + controls.fitToBox(AttachedObject.current!, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, }); + + setselectedFloorItem(AttachedObject.current!); + } + } else { + const target = controls.getTarget(new THREE.Vector3()); + await controls.setTarget(target.x, 0, target.z, true); + setselectedFloorItem(null); + } + } + } + }; + + const onDrop = (event: any) => { + if (!event.dataTransfer?.files[0]) return; + + if (selectedItem.id !== "" && event.dataTransfer?.files[0]) { + addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, setSimulationStates, plane); + } + }; + + const onDragOver = (event: any) => { + event.preventDefault(); + }; + + if (activeModule === "builder") { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener("dblclick", onDblClick); + canvasElement.addEventListener("drop", onDrop); + canvasElement.addEventListener("dragover", onDragOver); + } else { + if (controls) { + const target = controls.getTarget(new THREE.Vector3()); + controls.setTarget(target.x, 0, target.z, true); + setselectedFloorItem(null); } - } else { - const target = controls.getTarget(new THREE.Vector3()); - await controls.setTarget(target.x, 0, target.z, true); - setselectedFloorItem(null); - } } - } - }; - const onDrop = (event: any) => { - if (!event.dataTransfer?.files[0]) return; + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("dblclick", onDblClick); + canvasElement.removeEventListener("drop", onDrop); + canvasElement.removeEventListener("dragover", onDragOver); + }; + }, [deleteModels, transformMode, controls, selectedItem, state.camera, state.pointer, activeTool, activeModule,]); - if (selectedItem.id !== "" && event.dataTransfer?.files[0]) { - addAssetModel( - raycaster, - state.camera, - state.pointer, - floorGroup, - setFloorItems, - itemsGroup, - isTempLoader, - tempLoader, - socket, - selectedItem, - setSelectedItem, - setSimulationStates, - plane - ); - } - }; + useFrame(() => { + if (controls) + assetVisibility(itemsGroup, state.camera.position, renderDistance); + if (deleteModels && activeModule === "builder") { + DeletableHoveredFloorItems(state, itemsGroup, hoveredDeletableFloorItem, setDeletableFloorItem); + } else if (!deleteModels) { + if (hoveredDeletableFloorItem.current) { + hoveredDeletableFloorItem.current = undefined; + setDeletableFloorItem(null); + } + } + }); - const onDragOver = (event: any) => { - event.preventDefault(); - }; - - if (activeModule === "builder") { - canvasElement.addEventListener("mousedown", onMouseDown); - canvasElement.addEventListener("mouseup", onMouseUp); - canvasElement.addEventListener("mousemove", onMouseMove); - canvasElement.addEventListener("dblclick", onDblClick); - canvasElement.addEventListener("drop", onDrop); - canvasElement.addEventListener("dragover", onDragOver); - } else { - if (controls) { - const target = controls.getTarget(new THREE.Vector3()); - controls.setTarget(target.x, 0, target.z, true); - setselectedFloorItem(null); - } - } - - return () => { - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener("dblclick", onDblClick); - canvasElement.removeEventListener("drop", onDrop); - canvasElement.removeEventListener("dragover", onDragOver); - }; - }, [ - deleteModels, - transformMode, - controls, - selectedItem, - state.camera, - state.pointer, - activeTool, - activeModule, - ]); - - - useFrame(() => { - if (controls) - assetVisibility(itemsGroup, state.camera.position, renderDistance); - if (deleteModels) { - DeletableHoveredFloorItems( - state, - itemsGroup, - hoveredDeletableFloorItem, - setDeletableFloorItem - ); - } else if (!deleteModels) { - if (hoveredDeletableFloorItem.current) { - hoveredDeletableFloorItem.current = undefined; - setDeletableFloorItem(null); - } - } - }); - - return ; + return ; }; export default FloorItemsGroup; diff --git a/app/src/modules/builder/groups/wallItemsGroup.tsx b/app/src/modules/builder/groups/wallItemsGroup.tsx index c79adde..1439ef5 100644 --- a/app/src/modules/builder/groups/wallItemsGroup.tsx +++ b/app/src/modules/builder/groups/wallItemsGroup.tsx @@ -9,9 +9,13 @@ import handleMeshMissed from "../eventFunctions/handleMeshMissed"; import DeleteWallItems from "../geomentries/walls/deleteWallItems"; import loadInitialWallItems from "../../scene/IntialLoad/loadInitialWallItems"; import AddWallItems from "../geomentries/walls/addWallItems"; +import useModuleStore from "../../../store/useModuleStore"; const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletableWallItem, selectedItemsIndex, setSelectedItemsIndex, CSGGroup }: any) => { + const state = useThree(); + const { socket } = useSocketStore(); + const { pointer, camera, raycaster } = state; const { deleteModels, setDeleteModels } = useDeleteModels(); const { wallItems, setWallItems } = useWallItems(); const { objectPosition, setObjectPosition } = useObjectPosition(); @@ -19,10 +23,7 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable const { objectRotation, setObjectRotation } = useObjectRotation(); const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); - const { socket } = useSocketStore(); - const state = useThree(); - const { pointer, camera, raycaster } = state; - + const { activeModule } = useModuleStore(); useEffect(() => { // Load Wall Items from the backend @@ -209,7 +210,7 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable const onMouseUp = (evt: any) => { if (evt.button === 0) { isLeftMouseDown = false; - if (!drag && deleteModels) { + if (!drag && deleteModels && activeModule === "builder") { DeleteWallItems(hoveredDeletableWallItem, setWallItems, wallItems, socket); } } @@ -224,7 +225,7 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable const onDrop = (event: any) => { if (!event.dataTransfer?.files[0]) return - + pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, camera); @@ -259,7 +260,7 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable }, [deleteModels, wallItems]) useEffect(() => { - if (deleteModels) { + if (deleteModels && activeModule === "builder") { handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex); setSelectedWallItem(null); setSelectedItemsIndex(null); diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts index ec6033b..22b9a83 100644 --- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts @@ -192,7 +192,7 @@ function processLoadedModel( }, ]); - if (item.eventData || item.modelfileID === '67e3db95c2e8f37134526fb2') { + if (item.eventData || item.modelfileID === '67e3db5ac2e8f37134526f40' || item.modelfileID === '67eb7904c2e8f37134527eae') { processEventData(item, setSimulationStates); } @@ -227,10 +227,10 @@ function processEventData(item: Types.EventData, setSimulationStates: any) { data as Types.VehicleEventsSchema ]); - } else if (item.modelfileID === '67e3db95c2e8f37134526fb2') { + } else if (item.modelfileID === '67e3db5ac2e8f37134526f40') { const pointUUID = THREE.MathUtils.generateUUID(); - const pointPosition = new THREE.Vector3(0, 1.75, 0); + const pointPosition = new THREE.Vector3(0, 1.5, -0.5); const staticMachine: Types.StaticMachineEventsSchema = { modeluuid: item.modeluuid, @@ -243,13 +243,39 @@ function processEventData(item: Types.EventData, setSimulationStates: any) { triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, connections: { source: { modelUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, }, - position: item.position + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], }; - + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ ...(prevEvents || []), staticMachine as Types.StaticMachineEventsSchema ]); + + } else if (item.modelfileID === '67eb7904c2e8f37134527eae') { + const pointUUID = THREE.MathUtils.generateUUID(); + const pointPosition = new THREE.Vector3(0, 2.75, -0.5); + + const armBot: Types.ArmBotEventsSchema = { + modeluuid: item.modeluuid, + modelName: item.modelname, + type: "ArmBot", + points: { + uuid: pointUUID, + position: [pointPosition.x, pointPosition.y, pointPosition.z], + actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', speed: 1, processes: [] }, + triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, + connections: { source: { modelUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] }, + }, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + } + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + ...(prevEvents || []), + armBot as Types.ArmBotEventsSchema + ]); + } } diff --git a/app/src/modules/scene/controls/selection/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx index 7b494cd..3d2acee 100644 --- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx @@ -151,7 +151,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -234,7 +234,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.ConveyorEventsSchema ]); @@ -313,13 +313,44 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.VehicleEventsSchema ]); socket.emit("v2:model-asset:add", data); + } else { + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx index 852b541..a71412d 100644 --- a/app/src/modules/scene/controls/selection/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx @@ -132,7 +132,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : "default"; @@ -216,7 +216,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.ConveyorEventsSchema ]); @@ -295,13 +295,44 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => [ + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => [ ...(prevEvents || []), newEventData as Types.VehicleEventsSchema ]); socket.emit("v2:model-asset:add", data); + } else { + + //REST + + // await setFloorItemApi( + // organization, + // obj.uuid, + // obj.userData.name, + // [worldPosition.x, worldPosition.y, worldPosition.z], + // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, + // obj.userData.modelId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modeluuid: newFloorItem.modeluuid, + modelname: newFloorItem.modelname, + modelfileID: newFloorItem.modelfileID, + position: newFloorItem.position, + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + }; + + socket.emit("v2:model-asset:add", data); + } } else { diff --git a/app/src/modules/scene/controls/selection/moveControls.tsx b/app/src/modules/scene/controls/selection/moveControls.tsx index 2693531..5883b62 100644 --- a/app/src/modules/scene/controls/selection/moveControls.tsx +++ b/app/src/modules/scene/controls/selection/moveControls.tsx @@ -180,12 +180,12 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | 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 && eventData.type !== 'StaticMachine') { + if (eventData) { if (eventData.type === 'Conveyor' && eventData) { const backendEventData = { @@ -229,7 +229,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -280,7 +280,113 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'StaticMachine' && eventData) { + + const backendEventData = { + type: 'StaticMachine', + 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; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'ArmBot' && eventData) { + + const backendEventData = { + type: 'ArmBot', + 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; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } diff --git a/app/src/modules/scene/controls/selection/rotateControls.tsx b/app/src/modules/scene/controls/selection/rotateControls.tsx index 020705d..cf1ac83 100644 --- a/app/src/modules/scene/controls/selection/rotateControls.tsx +++ b/app/src/modules/scene/controls/selection/rotateControls.tsx @@ -184,13 +184,12 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedItems; }); - let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid); - console.log('eventData: ', eventData); + let eventData: Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema | 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 && eventData.type !== 'StaticMachine') { + if (eventData) { if (eventData.type === 'Conveyor' && eventData) { const backendEventData = { @@ -233,9 +232,8 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - console.log('newEventData: ', newEventData); - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } @@ -287,7 +285,113 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo newEventData.modelName = newFloorItem.modelname; newEventData.position = newFloorItem.position; - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'StaticMachine' && eventData) { + + const backendEventData = { + type: 'StaticMachine', + 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; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { + const updatedEvents = (prevEvents || []).map(event => + event.modeluuid === newFloorItem.modeluuid + ? { ...event, ...newEventData } + : event + ); + return updatedEvents; + }); + + socket.emit("v2:model-asset:add", data); + + } else if (eventData.type === 'ArmBot' && eventData) { + + const backendEventData = { + type: 'ArmBot', + 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; + newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; + + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).map(event => event.modeluuid === newFloorItem.modeluuid ? { ...event, ...newEventData } diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx index 8a464d5..686bf42 100644 --- a/app/src/modules/scene/controls/selection/selectionControls.tsx +++ b/app/src/modules/scene/controls/selection/selectionControls.tsx @@ -240,7 +240,7 @@ const SelectionControls: React.FC = () => { } }); - setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + setSimulationStates((prevEvents: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== selectedMesh.uuid); return updatedEvents; }); diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index 8a295be..805821c 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -1,9 +1,9 @@ import { useFrame, useThree } from '@react-three/fiber'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; import * as Types from '../../../types/world/worldTypes'; import { QuadraticBezierLine } from '@react-three/drei'; -import { useIsConnecting, useSimulationStates, useSocketStore } from '../../../store/store'; +import { useIsConnecting, useRenderDistance, useSimulationStates, useSocketStore } from '../../../store/store'; import useModuleStore from '../../../store/useModuleStore'; import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; import { setEventApi } from '../../../services/factoryBuilder/assest/floorAsset/setEventsApt'; @@ -11,28 +11,21 @@ import { setEventApi } from '../../../services/factoryBuilder/assest/floorAsset/ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject }) { const { activeModule } = useModuleStore(); const { gl, raycaster, scene, pointer, camera } = useThree(); + const { renderDistance } = useRenderDistance(); const { setIsConnecting } = useIsConnecting(); const { simulationStates, setSimulationStates } = useSimulationStates(); const { isPlaying } = usePlayButtonStore(); const { socket } = useSocketStore(); + const groupRefs = useRef<{ [key: string]: any }>({}); - const [firstSelected, setFirstSelected] = useState<{ - modelUUID: string; - sphereUUID: string; - position: THREE.Vector3; - isCorner: boolean; - } | null>(null); + const [firstSelected, setFirstSelected] = useState<{ modelUUID: string; sphereUUID: string; position: THREE.Vector3; isCorner: boolean; } | null>(null); const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3, end: THREE.Vector3, mid: THREE.Vector3 } | null>(null); const [helperlineColor, setHelperLineColor] = useState('red'); - const updatePathConnections = ( - fromModelUUID: string, - fromPointUUID: string, - toModelUUID: string, - toPointUUID: string - ) => { + const updatePathConnections = (fromModelUUID: string, fromPointUUID: string, toModelUUID: string, toPointUUID: string) => { const updatedPaths = simulationStates.map(path => { if (path.type === 'Conveyor') { + // Handle outgoing connections from Conveyor if (path.modeluuid === fromModelUUID) { return { ...path, @@ -61,6 +54,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }) }; } + // Handle incoming connections to Conveyor else if (path.modeluuid === toModelUUID) { return { ...path, @@ -167,82 +161,170 @@ 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 || []; + else if (path.type === 'StaticMachine') { + // Handle outgoing connections from StaticMachine + if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { + const newTarget = { + modelUUID: toModelUUID, + pointUUID: toPointUUID + }; - // // 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; - // } + // Ensure 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; - // } + const existingTargets = path.points.connections.targets || []; - // 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 || []; + // Allow only one connection + if (existingTargets.length >= 1) { + console.log("StaticMachine can only have one connection"); + return path; + } - // // 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; - // } + if (!existingTargets.some(target => + target.modelUUID === newTarget.modelUUID && + target.pointUUID === newTarget.pointUUID + )) { + return { + ...path, + points: { + ...path.points, + connections: { + ...path.points.connections, + targets: [...existingTargets, newTarget] + } + } + }; + } + } - // // Check if already has a connection - // if (existingTargets.length >= 1) { - // console.log("StaticMachine can have only one connection"); - // return path; - // } + // Handle incoming connections to StaticMachine + else if (path.modeluuid === toModelUUID && path.points.uuid === toPointUUID) { + const reverseTarget = { + modelUUID: fromModelUUID, + pointUUID: fromPointUUID + }; + + const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); + if (fromPath?.type !== 'ArmBot') { + console.log("StaticMachine can only be connected from ArmBot"); + return path; + } + + const existingTargets = path.points.connections.targets || []; + + if (existingTargets.length >= 1) { + console.log("StaticMachine can only have 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; + } + else if (path.type === 'ArmBot') { + // Handle outgoing connections from ArmBot + if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) { + const newTarget = { + modelUUID: toModelUUID, + pointUUID: toPointUUID + }; + + const toPath = simulationStates.find(p => p.modeluuid === toModelUUID); + if (!toPath) return path; + + const existingTargets = path.points.connections.targets || []; + + // Check if connecting to a StaticMachine and already connected to one + const alreadyConnectedToStatic = existingTargets.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + + if (toPath.type === 'StaticMachine') { + if (alreadyConnectedToStatic) { + console.log("ArmBot can only connect to one StaticMachine"); + 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 ArmBot + else if (path.modeluuid === toModelUUID && path.points.uuid === toPointUUID) { + const reverseTarget = { + modelUUID: fromModelUUID, + pointUUID: fromPointUUID + }; + + const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID); + if (!fromPath) return path; + + const existingTargets = path.points.connections.targets || []; + + const alreadyConnectedFromStatic = existingTargets.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + + if (fromPath.type === 'StaticMachine') { + if (alreadyConnectedFromStatic) { + console.log("ArmBot can only be connected from one StaticMachine"); + 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; + } - // 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; }); @@ -252,10 +334,10 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec path.modeluuid === fromModelUUID || path.modeluuid === toModelUUID ); - updateBackend(updatedPathDetails); + // updateBackend(updatedPathDetails); }; - const updateBackend = async (updatedPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => { + const updateBackend = async (updatedPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { if (updatedPaths.length === 0) return; const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : ""; @@ -437,6 +519,69 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return; } + // Check if StaticMachine is involved in the connection + if ((firstPath?.type === 'StaticMachine' && secondPath?.type !== 'ArmBot') || + (secondPath?.type === 'StaticMachine' && firstPath?.type !== 'ArmBot')) { + console.log("StaticMachine can only connect to ArmBot"); + return; + } + + // Check if StaticMachine already has a connection + if (firstPath?.type === 'StaticMachine') { + const staticConnections = firstPath.points.connections.targets.length; + if (staticConnections >= 1) { + console.log("StaticMachine can only have one connection"); + return; + } + } + if (secondPath?.type === 'StaticMachine') { + const staticConnections = secondPath.points.connections.targets.length; + if (staticConnections >= 1) { + console.log("StaticMachine can only have one connection"); + return; + } + } + + // Check if ArmBot is involved + if ((firstPath?.type === 'ArmBot' && secondPath?.type === 'StaticMachine') || + (secondPath?.type === 'ArmBot' && firstPath?.type === 'StaticMachine')) { + + const armBotPath = firstPath?.type === 'ArmBot' ? firstPath : secondPath; + const staticPath = firstPath?.type === 'StaticMachine' ? firstPath : secondPath; + + const armBotConnections = armBotPath.points.connections.targets || []; + const alreadyConnectedToStatic = armBotConnections.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + + if (alreadyConnectedToStatic) { + console.log("ArmBot can only connect to one StaticMachine"); + return; + } + + const staticConnections = staticPath.points.connections.targets.length; + if (staticConnections >= 1) { + console.log("StaticMachine can only have one connection"); + return; + } + } + + // Prevent ArmBot ↔ ArmBot + if (firstPath?.type === 'ArmBot' && secondPath?.type === 'ArmBot') { + console.log("Cannot connect two ArmBots together"); + return; + } + + // If one is ArmBot, ensure the other is StaticMachine or Conveyor + if (firstPath?.type === 'ArmBot' || secondPath?.type === 'ArmBot') { + const otherType = firstPath?.type === 'ArmBot' ? secondPath?.type : firstPath?.type; + if (otherType !== 'StaticMachine' && otherType !== 'Conveyor') { + console.log("ArmBot can only connect to Conveyors or one StaticMachine"); + return; + } + } + // At least one must be start/end point if (!firstSelected.isCorner && !isStartOrEnd) { console.log("At least one of the selected spheres must be a start or end point."); @@ -489,6 +634,15 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }; }, [camera, scene, raycaster, firstSelected, simulationStates]); + 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(() => { if (firstSelected) { raycaster.setFromCamera(pointer, camera); @@ -574,12 +728,50 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec (firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') || (secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor'); + // Check if StaticMachine is connecting to non-ArmBot + const isStaticMachineToNonArmBot = + (firstPath?.type === 'StaticMachine' && secondPath?.type !== 'ArmBot') || + (secondPath?.type === 'StaticMachine' && firstPath?.type !== 'ArmBot'); + + // Check if StaticMachine already has a connection + const isStaticMachineAtMaxConnections = + (firstPath?.type === 'StaticMachine' && firstPath.points.connections.targets.length >= 1) || + (secondPath?.type === 'StaticMachine' && secondPath.points.connections.targets.length >= 1); + + // Check if ArmBot is connecting to StaticMachine + const isArmBotToStaticMachine = + (firstPath?.type === 'ArmBot' && secondPath?.type === 'StaticMachine') || + (secondPath?.type === 'ArmBot' && firstPath?.type === 'StaticMachine'); + + // Prevent multiple StaticMachine connections to ArmBot + let isArmBotAlreadyConnectedToStatic = false; + if (isArmBotToStaticMachine) { + const armBotPath = firstPath?.type === 'ArmBot' ? firstPath : secondPath; + isArmBotAlreadyConnectedToStatic = armBotPath.points.connections.targets.some(target => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + return targetPath?.type === 'StaticMachine'; + }); + } + + // Prevent ArmBot to ArmBot + const isArmBotToArmBot = firstPath?.type === 'ArmBot' && secondPath?.type === 'ArmBot'; + + // If ArmBot is involved, other must be Conveyor or StaticMachine + const isArmBotToInvalidType = (firstPath?.type === 'ArmBot' || secondPath?.type === 'ArmBot') && + !(firstPath?.type === 'Conveyor' || firstPath?.type === 'StaticMachine' || + secondPath?.type === 'Conveyor' || secondPath?.type === 'StaticMachine'); + if ( !isDuplicateConnection && !isVehicleToVehicle && !isNonVehicleAlreadyConnected && !isVehicleAtMaxConnections && !isVehicleConnectingToNonConveyor && + !isStaticMachineToNonArmBot && + !isStaticMachineAtMaxConnections && + !isArmBotToArmBot && + !isArmBotToInvalidType && + !isArmBotAlreadyConnectedToStatic && firstSelected.sphereUUID !== sphereUUID && firstSelected.modelUUID !== modelUUID && (firstSelected.isCorner || isConnectable) && @@ -596,6 +788,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec } else { isInvalidConnection = true; } + } if (snappedSphere) { @@ -633,7 +826,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec }); return ( - + {simulationStates.flatMap(path => { if (path.type === 'Conveyor') { return path.points.flatMap(point => @@ -652,7 +845,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const distance = fromWorldPosition.distanceTo(toWorldPosition); const heightFactor = Math.max(0.5, distance * 0.2); - const midPoint = new THREE.Vector3( (fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, @@ -662,6 +854,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return ( (groupRefs.current[`${point.uuid}-${target.pointUUID}-${index}`] = el!)} start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} @@ -676,7 +869,9 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return null; }) ); - } else if (path.type === 'Vehicle') { + } + + if (path.type === 'Vehicle') { 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); @@ -689,7 +884,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec const distance = fromWorldPosition.distanceTo(toWorldPosition); const heightFactor = Math.max(0.5, distance * 0.2); - const midPoint = new THREE.Vector3( (fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, @@ -699,6 +893,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return ( (groupRefs.current[`${path.points.uuid}-${target.pointUUID}-${index}`] = el!)} start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} @@ -713,6 +908,48 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec return null; }); } + + if (path.type === 'StaticMachine') { + return path.points.connections.targets.map((target, index) => { + const targetPath = simulationStates.find(p => p.modeluuid === target.modelUUID); + if (targetPath?.type !== 'ArmBot') return null; + + const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.points.uuid); + const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID); + + if (fromSphere && toSphere) { + const fromWorldPosition = new THREE.Vector3(); + const toWorldPosition = new THREE.Vector3(); + fromSphere.getWorldPosition(fromWorldPosition); + toSphere.getWorldPosition(toWorldPosition); + + const distance = fromWorldPosition.distanceTo(toWorldPosition); + const heightFactor = Math.max(0.5, distance * 0.2); + const midPoint = new THREE.Vector3( + (fromWorldPosition.x + toWorldPosition.x) / 2, + Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, + (fromWorldPosition.z + toWorldPosition.z) / 2 + ); + + return ( + (groupRefs.current[`${path.points.uuid}-${target.pointUUID}-${index}`] = el!)} + start={fromWorldPosition.toArray()} + end={toWorldPosition.toArray()} + mid={midPoint.toArray()} + color="#42a5f5" + lineWidth={4} + dashed + dashSize={0.75} + dashScale={20} + /> + ); + } + return null; + }); + } + return []; })} @@ -730,6 +967,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec )} ); + } export default PathConnector; \ No newline at end of file diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx index 62bb79e..839cf39 100644 --- a/app/src/modules/simulation/path/pathCreation.tsx +++ b/app/src/modules/simulation/path/pathCreation.tsx @@ -206,6 +206,7 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec return ( (groupRefs.current[path.modeluuid] = el!)} position={path.position} @@ -271,10 +272,11 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec })} ); - } else if (path.type === "Vehicle" || path.type === "StaticMachine") { + } else if (path.type === "Vehicle") { return ( (groupRefs.current[path.modeluuid] = el!)} position={path.position} @@ -323,6 +325,114 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec ); + } else if (path.type === "StaticMachine") { + 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"); + }} + > + (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); + }} + > + + + + ); + } else if (path.type === "ArmBot") { + 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"); + }} + > + (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; })} diff --git a/app/src/store/store.ts b/app/src/store/store.ts index 725180b..89cd1a7 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -347,12 +347,12 @@ export const useSelectedPath = create((set: any) => ({ })); interface SimulationPathsStore { - simulationStates: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]; + simulationStates: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]; setSimulationStates: ( paths: - | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[] - | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[] - ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) + | (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[] + | ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[] + ) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) ) => void; } diff --git a/app/src/types/world/worldTypes.d.ts b/app/src/types/world/worldTypes.d.ts index 7baf5e0..fce09b1 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -329,6 +329,7 @@ interface StaticMachineEventsSchema { connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; }; position: [number, number, number]; + rotation: [number, number, number]; } interface ArmBotEventsSchema { @@ -343,6 +344,7 @@ interface ArmBotEventsSchema { connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] }; }; position: [number, number, number]; + rotation: [number, number, number]; } export type EventData = {