diff --git a/app/.env b/app/.env index 36093e3..4bfb63a 100644 --- a/app/.env +++ b/app/.env @@ -1,11 +1,11 @@ # PORT for the main application (frontend/backend or another service). -PORT=8200 +PORT=8400 # Base URL for the server socket API, used for real-time communication (e.g., WebSockets). -REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:7999 +REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:9902 # Base URL for the server REST API, used for HTTP requests to the backend server. -REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:4999 +REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:9901 # Base URL for the server marketplace, used for market place model blob. REACT_APP_SERVER_MARKETPLACE_URL=185.100.212.76:50011 diff --git a/app/src/assets/cursors/ask.svg b/app/src/assets/cursors/ask.svg new file mode 100644 index 0000000..39f66a1 --- /dev/null +++ b/app/src/assets/cursors/ask.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/assets/cursors/cell.svg b/app/src/assets/cursors/cell.svg new file mode 100644 index 0000000..2632892 --- /dev/null +++ b/app/src/assets/cursors/cell.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/assets/cursors/close.svg b/app/src/assets/cursors/close.svg new file mode 100644 index 0000000..6fdebed --- /dev/null +++ b/app/src/assets/cursors/close.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/assets/cursors/comment.svg b/app/src/assets/cursors/comment.svg new file mode 100644 index 0000000..07c5294 --- /dev/null +++ b/app/src/assets/cursors/comment.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/src/assets/cursors/cross.svg b/app/src/assets/cursors/cross.svg new file mode 100644 index 0000000..1ac52f1 --- /dev/null +++ b/app/src/assets/cursors/cross.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/assets/cursors/default.svg b/app/src/assets/cursors/default.svg new file mode 100644 index 0000000..9e4309f --- /dev/null +++ b/app/src/assets/cursors/default.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/assets/cursors/export.svg b/app/src/assets/cursors/export.svg new file mode 100644 index 0000000..a560635 --- /dev/null +++ b/app/src/assets/cursors/export.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/assets/cursors/move.svg b/app/src/assets/cursors/move.svg new file mode 100644 index 0000000..12b1692 --- /dev/null +++ b/app/src/assets/cursors/move.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/assets/cursors/open.svg b/app/src/assets/cursors/open.svg new file mode 100644 index 0000000..695087a --- /dev/null +++ b/app/src/assets/cursors/open.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/assets/cursors/pen.svg b/app/src/assets/cursors/pen.svg new file mode 100644 index 0000000..ff956b5 --- /dev/null +++ b/app/src/assets/cursors/pen.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/src/assets/cursors/pointing.svg b/app/src/assets/cursors/pointing.svg new file mode 100644 index 0000000..4a50a4b --- /dev/null +++ b/app/src/assets/cursors/pointing.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/components/Dashboard/SidePannel.tsx b/app/src/components/Dashboard/SidePannel.tsx index 5cfaeb0..89ce5ea 100644 --- a/app/src/components/Dashboard/SidePannel.tsx +++ b/app/src/components/Dashboard/SidePannel.tsx @@ -38,9 +38,11 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { const handleCreateNewProject = async () => { const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken") + console.log('refreshToken: ', refreshToken); try { const projectId = generateProjectId(); - useSocketStore.getState().initializeSocket(email, organization, token); + useSocketStore.getState().initializeSocket(email, organization, token, refreshToken); //API for creating new Project // const project = await createProject( @@ -56,7 +58,8 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { organization: organization, projectUuid: projectId, }; - + + console.log('projectSocket: ', projectSocket); if (projectSocket) { const handleResponse = (data: any) => { if (data.message === "Project created successfully") { diff --git a/app/src/components/footer/Footer.tsx b/app/src/components/footer/Footer.tsx index 737ee26..94ee0b9 100644 --- a/app/src/components/footer/Footer.tsx +++ b/app/src/components/footer/Footer.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { HelpIcon } from "../icons/DashboardIcon"; import { useLogger } from "../ui/log/LoggerContext"; import { GetLogIcon } from "./getLogIcons"; @@ -8,10 +8,14 @@ import { CurserRightIcon, } from "../icons/LogIcons"; import ShortcutHelper from "./shortcutHelper"; -import useVersionHistoryVisibleStore, { useShortcutStore } from "../../store/builder/store"; +import useVersionHistoryVisibleStore, { + useShortcutStore, +} from "../../store/builder/store"; import { usePlayButtonStore } from "../../store/usePlayButtonStore"; import useModuleStore, { useSubModuleStore } from "../../store/useModuleStore"; import { useVersionContext } from "../../modules/builder/version/versionContext"; +import { mouseActionHelper } from "../../utils/mouseUtils/mouseHelper"; +import { useMouseNoteStore } from "../../store/useUIToggleStore"; const Footer: React.FC = () => { const { logs, setIsLogListVisible } = useLogger(); @@ -25,28 +29,41 @@ const Footer: React.FC = () => { const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore(); + + const mouseButtons = [ + { + icon: , + label: Leftnote !== "" ? Leftnote : "Pan", + mouse: "left", + }, + { + icon: , + label: Middlenote !== "" ? Middlenote : "Scroll Zoom", + mouse: "middle", + }, + { + icon: , + label: Rightnote !== "" ? Rightnote : "Orbit / Cancel action", + mouse: "right", + }, + ]; + + useEffect(() => { + const cleanup = mouseActionHelper(); + return () => cleanup(); + }, []); + return (
-
-
- + {mouseButtons.map(({ icon, label, mouse }) => ( +
+
{icon}
+
{label}
-
Selection
-
-
-
- -
-
Rotate/Zoom
-
-
-
- -
-
Pan/Context Menu
-
+ ))}
@@ -68,12 +85,15 @@ const Footer: React.FC = () => { )}
-
{ - setVersionHistoryVisible(true); - setSubModule("properties"); - setActiveModule('builder'); - }}> - {(selectedVersion?.version) ?? 'v 0.0.0'} +
{ + setVersionHistoryVisible(true); + setSubModule("properties"); + setActiveModule("builder"); + }} + > + {selectedVersion?.version ?? "v 0.0.0"}
@@ -83,8 +103,9 @@ const Footer: React.FC = () => { {!isPlaying && (
diff --git a/app/src/components/icons/DashboardIcon.tsx b/app/src/components/icons/DashboardIcon.tsx index f1f4d7d..1aa94b5 100644 --- a/app/src/components/icons/DashboardIcon.tsx +++ b/app/src/components/icons/DashboardIcon.tsx @@ -9,13 +9,13 @@ export function NotificationIcon() { > @@ -34,7 +34,7 @@ export function HomeIcon() { > ); @@ -51,7 +51,7 @@ export function ProjectsIcon() { > ); @@ -70,103 +70,103 @@ export function TutorialsIcon() { cx="8.157" cy="8.35866" r="6.17928" - stroke="var(--text-color)" + stroke="var(--text-button-color)" strokeWidth="0.562865" /> @@ -184,12 +184,12 @@ export function DocumentationIcon() { > @@ -208,7 +208,7 @@ export function HelpIcon() { @@ -236,17 +236,17 @@ export function LogoutIcon() { > diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index d129dd5..4247316 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -1,11 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import RenameInput from "../../../ui/inputs/RenameInput"; import Vector3Input from "../customInput/Vector3Input"; import { useSelectedZoneStore } from "../../../../store/visualization/useZoneStore"; import { useEditPosition, usezonePosition, - useZones, usezoneTarget, } from "../../../../store/builder/store"; import { zoneCameraUpdate } from "../../../../services/visulization/zone/zoneCameraUpdation"; @@ -19,12 +18,11 @@ const ZoneProperties: React.FC = () => { const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { zonePosition, setZonePosition } = usezonePosition(); const { zoneTarget, setZoneTarget } = usezoneTarget(); - // const { zones, setZones } = useZones(); - const { assetStore, zoneStore } = useSceneContext(); + const { zoneStore } = useSceneContext(); const { zones, setZoneName } = zoneStore() const { projectId } = useParams(); - const { userName, userId, organization, email } = getUserData(); + const { organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index ed9ef9f..252e44d 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -28,7 +28,6 @@ const EventProperties: React.FC = () => { const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); - useEffect(() => { const event = getCurrentEventData(); setCurrentEventData(event); diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx index cc7be80..ba74c7c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx @@ -13,6 +13,7 @@ interface AssemblyActionProps { swapOptions: string[]; swapDefaultOption: string; onSwapSelect: (value: string) => void; + clearPoints: () => void; } const AssemblyAction: React.FC = ({ @@ -20,6 +21,7 @@ const AssemblyAction: React.FC = ({ swapOptions, swapDefaultOption, onSwapSelect, + clearPoints }) => { return ( <> @@ -37,6 +39,19 @@ const AssemblyAction: React.FC = ({ defaultOption={swapDefaultOption} onSelect={onSwapSelect} /> +
+
+
Reset Points
+ +
+
); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx index 6cda9a0..7d52d70 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx @@ -8,18 +8,29 @@ interface WorkerActionProps { max: number; step: number; defaultValue: string; - disabled?: boolean, + disabled?: boolean; onChange: (value: string) => void; }; + loadCount?: { + value: number; + min: number; + max: number; + step: number; + defaultValue: string, + disabled: false, + onChange: (value: number) => void; + }; clearPoints: () => void; } const WorkerAction: React.FC = ({ loadCapacity, + loadCount, clearPoints, }) => { return ( - <> +
+ {/* Load Capacity Input */} = ({ onClick={() => { }} onChange={loadCapacity.onChange} /> + + {/* Load Count Input */} + {loadCount && ( + { }} + onChange={(value) => loadCount.onChange(parseInt(value))} + /> + )} + + {/* Clear Points Button */}
-
Reset
+
Reset Points
- +
); }; -export default WorkerAction; +export default WorkerAction; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx index 3c1a2e4..990d046 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -18,10 +18,17 @@ import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; function ConveyorMechanics() { const [activeOption, setActiveOption] = useState<"default" | "spawn" | "swap" | "delay" | "despawn">("default"); + const [speed, setSpeed] = useState("0.5"); + const [actionName, setActionName] = useState("Action Name"); + const [material, setMaterial] = useState("Default material"); + const [spawnCount, setSpawnCount] = useState("1"); + const [spawnInterval, setSpawnInterval] = useState("1"); + const [delay, setDelay] = useState("0"); const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); const { productStore } = useSceneContext(); - const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = productStore(); + const { getPointByUuid, updateEvent, updateAction, getEventByModelUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { setSelectedAction, clearSelectedAction } = useSelectedAction(); @@ -36,9 +43,21 @@ function ConveyorMechanics() { selectedEventData?.data.modelUuid, selectedEventData?.selectedPoint ) as ConveyorPointSchema | undefined; - if (point && "action" in point) { + + const event = getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData?.data.modelUuid + ) as ConveyorEventSchema | undefined; + + if (point && "action" in point && event) { setSelectedPointData(point); - setActiveOption(point.action.actionType as | "default" | "spawn" | "swap" | "delay" | "despawn"); + setActiveOption(point.action.actionType); + setActionName(point.action.actionName); + setSpeed(event.speed?.toString() || "0.5"); + setMaterial(point.action.material || "Default material"); + setSpawnCount(point.action.spawnCount?.toString() || "1"); + setSpawnInterval(point.action.spawnInterval?.toString() || "1"); + setDelay(point.action.delay?.toString() || "0"); setSelectedAction(point.action.actionUuid, point.action.actionName); } } else { @@ -53,19 +72,25 @@ function ConveyorMechanics() { eventData: EventsSchema ) => { upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, + productName, + productUuid, + projectId, eventDatas: eventData, - versionId: selectedVersion?.versionId || '', - }) - } + versionId: selectedVersion?.versionId || "", + }); + }; const handleSpeedChange = (value: string) => { if (!selectedEventData) return; - const event = updateEvent(selectedProduct.productUuid, selectedEventData.data.modelUuid, { - speed: parseFloat(value), - }); + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + { speed: numericValue } + ); if (event) { updateBackend( @@ -75,16 +100,20 @@ function ConveyorMechanics() { event ); } + setSpeed(value); }; const handleActionTypeChange = (option: string) => { - if (!selectedEventData || !selectedPointData) return; - const validOption = option as | "default" | "spawn" | "swap" | "delay" | "despawn"; + if (!selectedPointData) return; + + const validOption = option as "default" | "spawn" | "swap" | "delay" | "despawn"; setActiveOption(validOption); - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - actionType: validOption, - }); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { actionType: validOption } + ); if (event) { updateBackend( @@ -97,8 +126,14 @@ function ConveyorMechanics() { }; const handleRenameAction = (newName: string) => { - if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { actionName: newName }); + if (!selectedPointData) return; + + setActionName(newName); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { actionName: newName } + ); if (event) { updateBackend( @@ -111,10 +146,17 @@ function ConveyorMechanics() { }; const handleSpawnCountChange = (value: string) => { - if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - spawnCount: parseFloat(value), - }); + if (!selectedPointData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setSpawnCount(value); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { spawnCount: numericValue } + ); if (event) { updateBackend( @@ -127,10 +169,17 @@ function ConveyorMechanics() { }; const handleSpawnIntervalChange = (value: string) => { - if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - spawnInterval: parseFloat(value), - }); + if (!selectedPointData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setSpawnInterval(value); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { spawnInterval: numericValue } + ); if (event) { updateBackend( @@ -142,9 +191,15 @@ function ConveyorMechanics() { } }; - const handleMaterialSelect = (material: string) => { - if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { material }); + const handleMaterialSelect = (selectedMaterial: string) => { + if (!selectedPointData) return; + + setMaterial(selectedMaterial); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { material: selectedMaterial } + ); if (event) { updateBackend( @@ -157,10 +212,17 @@ function ConveyorMechanics() { }; const handleDelayChange = (value: string) => { - if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - delay: parseFloat(value), - }); + if (!selectedPointData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setDelay(value); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { delay: numericValue } + ); if (event) { updateBackend( @@ -177,31 +239,6 @@ function ConveyorMechanics() { options: ["default", "spawn", "swap", "delay", "despawn"], }; - // Get current values from store - const currentSpeed = (getEventByModelUuid( - selectedProduct.productUuid, selectedEventData?.data.modelUuid || "" - ) as ConveyorEventSchema | undefined)?.speed?.toString() || "0.5"; - - const currentActionName = selectedPointData - ? selectedPointData.action.actionName - : "Action Name"; - - const currentMaterial = selectedPointData - ? selectedPointData.action.material - : "Default material"; - - const currentSpawnCount = selectedPointData - ? selectedPointData.action.spawnCount?.toString() || "1" - : "1"; - - const currentSpawnInterval = selectedPointData - ? selectedPointData.action.spawnInterval?.toString() || "1" - : "1"; - - const currentDelay = selectedPointData - ? selectedPointData.action.delay?.toString() || "0" - : "0"; - return ( <>
@@ -209,10 +246,10 @@ function ConveyorMechanics() {
{ }} @@ -222,24 +259,18 @@ function ConveyorMechanics() {
- +
@@ -248,11 +279,11 @@ function ConveyorMechanics() { )} {activeOption === "despawn" && } {activeOption === "delay" && ( ("worker"); const [speed, setSpeed] = useState("0.5"); + const [loadCount, setLoadCount] = useState(0); const [loadCapacity, setLoadCapacity] = useState("1"); const [processTime, setProcessTime] = useState(10); const [swappedMaterial, setSwappedMaterial] = useState("Default material"); @@ -24,7 +27,7 @@ function HumanMechanics() { const [selectedPointData, setSelectedPointData] = useState(); const { selectedEventData } = useSelectedEventData(); const { productStore } = useSceneContext(); - const { getPointByUuid, updateEvent, updateAction, addAction, removeAction, getEventByModelUuid } = productStore(); + const { getPointByUuid, updateEvent, updateAction, addAction, removeAction, getEventByModelUuid, getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); @@ -40,19 +43,21 @@ function HumanMechanics() { selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.action) { + if (point?.actions?.length) { setSelectedPointData(point); - setCurrentAction(point.action); - setSelectedAction(point.action.actionUuid, point.action.actionName); + const firstAction = point.actions[0]; + setCurrentAction(firstAction); setSpeed(( getEventByModelUuid( selectedProduct.productUuid, selectedEventData?.data.modelUuid || "" ) as HumanEventSchema | undefined )?.speed?.toString() || "1"); - setLoadCapacity(point.action.loadCapacity.toString()); - setProcessTime(point.action.processTime || 10); - setSwappedMaterial(point.action.swapMaterial || "Default material"); + setLoadCapacity(firstAction.loadCapacity.toString()); + setActiveOption(firstAction.actionType); + setLoadCount(firstAction.loadCount || 0); + setProcessTime(firstAction.processTime || 10); + setSwappedMaterial(firstAction.swapMaterial || "Default material"); } } else { clearSelectedAction(); @@ -60,26 +65,36 @@ function HumanMechanics() { }, [selectedEventData, selectedProduct]); useEffect(() => { - if (selectedEventData && selectedProduct.productUuid) { + if (selectedEventData && selectedEventData.data.type === "human") { const point = getPointByUuid( selectedProduct.productUuid, selectedEventData.data.modelUuid, selectedEventData.selectedPoint ) as HumanPointSchema | undefined; - if (point?.action) { - setSelectedPointData(point); - setCurrentAction(point.action); - setActiveOption(point.action.actionType); - setSelectedAction(point.action.actionUuid, point.action.actionName); + const actionUuid = selectedAction.actionId || point?.actions[0].actionUuid || ''; + + const newCurrentAction = getActionByUuid(selectedProduct.productUuid, actionUuid); + + if (newCurrentAction && (newCurrentAction.actionType === 'assembly' || newCurrentAction?.actionType === 'worker')) { + if (!selectedAction.actionId) { + setSelectedAction(newCurrentAction.actionUuid, newCurrentAction.actionName); + } + setCurrentAction(newCurrentAction); + setActiveOption(newCurrentAction.actionType); + setLoadCapacity(newCurrentAction.loadCapacity.toString()); + setLoadCount(newCurrentAction.loadCount || 0); + + if (newCurrentAction.actionType === 'assembly') { + setProcessTime(newCurrentAction.processTime || 10); + setSwappedMaterial(newCurrentAction.swapMaterial || "Default material"); + } + } else { + clearSelectedAction(); + setCurrentAction(undefined); } - } else { - clearSelectedAction(); - setCurrentAction(undefined); - setSpeed("0.5"); - setLoadCapacity("1"); } - }, [selectedEventData, selectedProduct, selectedAction]); + }, [selectedAction, selectedProduct, selectedEventData]); const updateBackend = ( productName: string, @@ -99,8 +114,9 @@ function HumanMechanics() { const handleSelectActionType = (actionType: string) => { if (!selectedAction.actionId || !currentAction || !selectedPointData) return; - const updatedAction = { ...currentAction, actionType: actionType as "worker" }; - const updatedPoint = { ...selectedPointData, action: updatedAction }; + const updatedAction = { ...currentAction, actionType: actionType as "worker" | "assembly" }; + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; const event = updateAction( selectedProduct.productUuid, @@ -143,10 +159,9 @@ function HumanMechanics() { const handleLoadCapacityChange = (value: string) => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - const updatedAction = { ...currentAction }; - updatedAction.loadCapacity = parseInt(value) - - const updatedPoint = { ...selectedPointData, action: updatedAction }; + const updatedAction = { ...currentAction, loadCapacity: parseInt(value) }; + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; const event = updateAction( selectedProduct.productUuid, @@ -163,13 +178,34 @@ function HumanMechanics() { setLoadCapacity(value); }; + const handleLoadCountChange = (value: number) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAction = { ...currentAction, loadCount: value }; + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + setLoadCount(value); + }; + const handleProcessTimeChange = (value: number) => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - const updatedAction = { ...currentAction }; - updatedAction.processTime = value - - const updatedPoint = { ...selectedPointData, action: updatedAction }; + const updatedAction = { ...currentAction, processTime: value }; + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; const event = updateAction( selectedProduct.productUuid, @@ -189,10 +225,9 @@ function HumanMechanics() { const handleMaterialChange = (value: string) => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - const updatedAction = { ...currentAction }; - updatedAction.swapMaterial = value - - const updatedPoint = { ...selectedPointData, action: updatedAction }; + const updatedAction = { ...currentAction, swapMaterial: value }; + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; const event = updateAction( selectedProduct.productUuid, @@ -212,11 +247,17 @@ function HumanMechanics() { const handleClearPoints = () => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; - const updatedAction = { ...currentAction }; - delete updatedAction.pickUpPoint; - delete updatedAction.dropPoint; + const updatedAction: HumanAction = JSON.parse(JSON.stringify(currentAction)); - const updatedPoint = { ...selectedPointData, action: updatedAction }; + if (updatedAction.actionType === 'assembly') { + updatedAction.assemblyPoint = { position: null, rotation: null, } + } else { + updatedAction.pickUpPoint = { position: null, rotation: null, }; + updatedAction.dropPoint = { position: null, rotation: null, } + } + + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; const event = updateAction( selectedProduct.productUuid, @@ -237,12 +278,17 @@ function HumanMechanics() { const newAction: HumanAction = { actionUuid: MathUtils.generateUUID(), - actionName: `Action`, + actionName: `Action ${selectedPointData.actions.length + 1}`, actionType: "worker", + loadCount: 1, loadCapacity: 1, + processTime: 10, triggers: [], }; + const updatedActions = [...(selectedPointData.actions || []), newAction]; + const updatedPoint = { ...selectedPointData, actions: updatedActions }; + const event = addAction( selectedProduct.productUuid, selectedEventData.data.modelUuid, @@ -254,27 +300,39 @@ function HumanMechanics() { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } - const updatedPoint = { ...selectedPointData, action: newAction }; setSelectedPointData(updatedPoint); setSelectedAction(newAction.actionUuid, newAction.actionName); }; const handleDeleteAction = () => { - if (!selectedPointData) return; + if (!selectedPointData || !selectedAction.actionId) return; + + const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== selectedAction.actionId); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; const event = removeAction( selectedProduct.productUuid, - selectedPointData.action.actionUuid + selectedAction.actionId ); if (event) { updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); } - const updatedPoint = { ...selectedPointData, action: undefined as any }; setSelectedPointData(updatedPoint); - clearSelectedAction(); - setCurrentAction(undefined); + + const index = selectedPointData.actions.findIndex((a) => a.actionUuid === selectedAction.actionId); + const nextAction = updatedPoint.actions[index] || updatedPoint.actions[index - 1]; + if (nextAction) { + setSelectedAction(nextAction.actionUuid, nextAction.actionName); + const action = getActionByUuid(selectedProduct.productUuid, nextAction.actionUuid); + if (action) { + setCurrentAction(action as HumanAction); + } + } else { + clearSelectedAction(); + setCurrentAction(undefined); + } }; return ( @@ -299,7 +357,7 @@ function HumanMechanics() {
@@ -315,7 +373,7 @@ function HumanMechanics() { defaultOption={activeOption} options={["worker", "assembly"]} onSelect={handleSelectActionType} - disabled={true} + disabled={false} />
{currentAction.actionType === 'worker' && @@ -323,12 +381,21 @@ function HumanMechanics() { loadCapacity={{ value: loadCapacity, min: 1, - max: 5, + max: 20, step: 1, - defaultValue: "10", + defaultValue: "1", disabled: true, onChange: handleLoadCapacityChange, }} + loadCount={{ + value: loadCount, + min: 0, + max: 20, + step: 1, + defaultValue: "1", + disabled: false, + onChange: handleLoadCountChange, + }} clearPoints={handleClearPoints} /> } @@ -343,6 +410,7 @@ function HumanMechanics() { swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]} swapDefaultOption={swappedMaterial} onSwapSelect={handleMaterialChange} + clearPoints={handleClearPoints} /> }
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx index f714d69..22b3277 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -12,8 +12,12 @@ import { useVersionContext } from "../../../../../../modules/builder/version/ver import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; function MachineMechanics() { - const [activeOption, setActiveOption] = useState<"default" | "process">("default"); + const [activeOption, setActiveOption] = useState<"process">("process"); + const [actionName, setActionName] = useState("Action Name"); + const [processTime, setProcessTime] = useState("1"); + const [material, setMaterial] = useState("Default material"); const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); const { productStore } = useSceneContext(); const { getPointByUuid, updateAction } = productStore(); @@ -31,9 +35,13 @@ function MachineMechanics() { selectedEventData?.data.modelUuid, selectedEventData?.selectedPoint ) as MachinePointSchema | undefined; + if (point && "action" in point) { setSelectedPointData(point); setActiveOption(point.action.actionType as "process"); + setActionName(point.action.actionName); + setProcessTime(point.action.processTime?.toString() || "1"); + setMaterial(point.action.swapMaterial || "Default material"); setSelectedAction(point.action.actionUuid, point.action.actionName); } } else { @@ -48,22 +56,25 @@ function MachineMechanics() { eventData: EventsSchema ) => { upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, + productName, + productUuid, + projectId, eventDatas: eventData, - versionId: selectedVersion?.versionId || '', - }) - } + versionId: selectedVersion?.versionId || "", + }); + }; const handleActionTypeChange = (option: string) => { - if (!selectedEventData || !selectedPointData) return; + if (!selectedPointData) return; + const validOption = option as "process"; setActiveOption(validOption); - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - actionType: validOption, - }); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { actionType: validOption } + ); if (event) { updateBackend( @@ -77,7 +88,13 @@ function MachineMechanics() { const handleRenameAction = (newName: string) => { if (!selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { actionName: newName }); + + setActionName(newName); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { actionName: newName } + ); if (event) { updateBackend( @@ -91,9 +108,16 @@ function MachineMechanics() { const handleProcessTimeChange = (value: string) => { if (!selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - processTime: parseFloat(value), - }); + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setProcessTime(value); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { processTime: numericValue } + ); if (event) { updateBackend( @@ -105,11 +129,15 @@ function MachineMechanics() { } }; - const handleMaterialSelect = (material: string) => { + const handleMaterialSelect = (selectedMaterial: string) => { if (!selectedPointData) return; - const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { - swapMaterial: material, - }); + + setMaterial(selectedMaterial); + const event = updateAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { swapMaterial: selectedMaterial } + ); if (event) { updateBackend( @@ -121,19 +149,6 @@ function MachineMechanics() { } }; - // Get current values from store - const currentActionName = selectedPointData - ? selectedPointData.action.actionName - : "Action Name"; - - const currentProcessTime = selectedPointData - ? selectedPointData.action.processTime.toString() - : "1"; - - const currentMaterial = selectedPointData - ? selectedPointData.action.swapMaterial - : "Default material"; - const availableActions = { defaultOption: "process", options: ["process"], @@ -146,28 +161,26 @@ function MachineMechanics() {
- +
{activeOption === "process" && ( )} @@ -182,4 +195,4 @@ function MachineMechanics() { ); } -export default MachineMechanics; +export default MachineMechanics; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx index d70f6ac..0c7c6e3 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -14,11 +14,13 @@ import { useVersionContext } from "../../../../../../modules/builder/version/ver import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; function RoboticArmMechanics() { - const [activeOption, setActiveOption] = useState<"default" | "pickAndPlace">("default"); + const [activeOption, setActiveOption] = useState<"pickAndPlace">("pickAndPlace"); + const [speed, setSpeed] = useState("0.5"); const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); const { productStore } = useSceneContext(); - const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction, } = productStore(); + const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); @@ -33,16 +35,20 @@ function RoboticArmMechanics() { selectedEventData.data.modelUuid, selectedEventData.selectedPoint ) as RoboticArmPointSchema | undefined; + if (point?.actions) { setSelectedPointData(point); + setSpeed( + (getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid + ) as RoboticArmEventSchema | undefined)?.speed?.toString() || "0.5" + ); + if (point.actions.length > 0) { - setActiveOption( - point.actions[0].actionType as "default" | "pickAndPlace" - ); - setSelectedAction( - point.actions[0].actionUuid, - point.actions[0].actionName - ); + const firstAction = point.actions[0]; + setActiveOption(firstAction.actionType); + setSelectedAction(firstAction.actionUuid, firstAction.actionName); } } } else { @@ -57,33 +63,33 @@ function RoboticArmMechanics() { eventData: EventsSchema ) => { upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, + productName, + productUuid, + projectId, eventDatas: eventData, - versionId: selectedVersion?.versionId || '', + versionId: selectedVersion?.versionId || "", }); }; const handleRenameAction = (newName: string) => { - if (!selectedAction.actionId) return; + if (!selectedAction.actionId || !selectedPointData) return; + const event = updateAction( selectedProduct.productUuid, selectedAction.actionId, { actionName: newName } ); - if (selectedPointData) { - const updatedActions = selectedPointData.actions.map((action) => - action.actionUuid === selectedAction.actionId - ? { ...action, actionName: newName } - : action - ); - setSelectedPointData({ - ...selectedPointData, - actions: updatedActions, - }); - } + const updatedActions = selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions, + }); if (event) { updateBackend( @@ -97,10 +103,15 @@ function RoboticArmMechanics() { const handleSpeedChange = (value: string) => { if (!selectedEventData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setSpeed(value); const event = updateEvent( selectedProduct.productUuid, selectedEventData.data.modelUuid, - { speed: parseFloat(value), } + { speed: numericValue } ); if (event) { @@ -112,6 +123,7 @@ function RoboticArmMechanics() { ); } }; + const handleClearPoints = () => { if (!selectedAction.actionId || !selectedPointData) return; @@ -166,15 +178,20 @@ function RoboticArmMechanics() { ); } - const updatedPoint = { ...selectedPointData, actions: [...selectedPointData.actions, newAction], }; - setSelectedPointData(updatedPoint); + setSelectedPointData({ + ...selectedPointData, + actions: [...selectedPointData.actions, newAction], + }); setSelectedAction(newAction.actionUuid, newAction.actionName); }; const handleDeleteAction = (actionUuid: string) => { if (!selectedPointData) return; - const event = removeAction(selectedProduct.productUuid, actionUuid); + const event = removeAction( + selectedProduct.productUuid, + actionUuid + ); if (event) { updateBackend( @@ -185,14 +202,13 @@ function RoboticArmMechanics() { ); } - const index = selectedPointData.actions.findIndex((a) => a.actionUuid === actionUuid); - const newActions = selectedPointData.actions.filter((a) => a.actionUuid !== actionUuid); + const index = selectedPointData.actions.findIndex(a => a.actionUuid === actionUuid); + const newActions = selectedPointData.actions.filter(a => a.actionUuid !== actionUuid); - const updatedPoint = { + setSelectedPointData({ ...selectedPointData, actions: newActions, - }; - setSelectedPointData(updatedPoint); + }); if (selectedAction.actionId === actionUuid) { const nextAction = newActions[index] || newActions[index - 1]; @@ -209,16 +225,7 @@ function RoboticArmMechanics() { options: ["pickAndPlace"], }; - const currentSpeed = (getEventByModelUuid(selectedProduct.productUuid, selectedEventData?.data.modelUuid || "") as RoboticArmEventSchema | undefined)?.speed?.toString() || "0.5"; - - const currentAction = selectedPointData?.actions.find((a) => a.actionUuid === selectedAction.actionId); - - const currentPickPoint = currentAction?.process.startPoint - ? `${currentAction.process.startPoint[0]},${currentAction.process.startPoint[1]},${currentAction.process.startPoint[2]}` - : ""; - const currentPlacePoint = currentAction?.process.endPoint - ? `${currentAction.process.endPoint[0]},${currentAction.process.endPoint[1]},${currentAction.process.endPoint[2]}` - : ""; + const currentAction = selectedPointData?.actions.find(a => a.actionUuid === selectedAction.actionId); return ( <> @@ -227,10 +234,10 @@ function RoboticArmMechanics() {
{ }} @@ -252,7 +259,7 @@ function RoboticArmMechanics() {
@@ -277,4 +284,4 @@ function RoboticArmMechanics() { ); } -export default RoboticArmMechanics; +export default RoboticArmMechanics; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx index b29feb5..796dbc5 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -192,7 +192,7 @@ function StorageMechanics() {
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx index eb9e41c..fada871 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -3,10 +3,7 @@ import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; -import { - useSelectedAction, - useSelectedEventData, -} from "../../../../../../store/simulation/useSimulationStore"; +import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; import TravelAction from "../actions/TravelAction"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; @@ -14,19 +11,26 @@ import { useProductContext } from "../../../../../../modules/simulation/products import { useParams } from "react-router-dom"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; +import { useSelectedPath } from "../../../../../../store/builder/store"; function VehicleMechanics() { - const [activeOption, setActiveOption] = useState<"default" | "travel">("default"); + const [activeOption, setActiveOption] = useState<"travel">("travel"); + const [speed, setSpeed] = useState("0.5"); + const [actionName, setActionName] = useState("Action Name"); + const [loadCapacity, setLoadCapacity] = useState("1"); + const [unloadDuration, setUnloadDuration] = useState("1"); const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); const { productStore } = useSceneContext(); - const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = productStore(); + const { getPointByUuid, updateEvent, updateAction, getEventByModelUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { setSelectedAction, clearSelectedAction } = useSelectedAction(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); + const { setSelectedPath } = useSelectedPath(); useEffect(() => { if (selectedEventData && selectedEventData.data.type === "vehicle") { @@ -39,6 +43,15 @@ function VehicleMechanics() { if (point) { setSelectedPointData(point); setActiveOption(point.action.actionType as "travel"); + setActionName(point.action.actionName); + setSpeed( + (getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid + ) as VehicleEventSchema | undefined)?.speed?.toString() || "0.5" + ); + setLoadCapacity(point.action.loadCapacity?.toString() || "1"); + setUnloadDuration(point.action.unLoadDuration?.toString() || "1"); setSelectedAction(point.action.actionUuid, point.action.actionName); } } else { @@ -53,22 +66,25 @@ function VehicleMechanics() { eventData: EventsSchema ) => { upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, + productName, + productUuid, + projectId, eventDatas: eventData, - versionId: selectedVersion?.versionId || '', + versionId: selectedVersion?.versionId || "", }); }; const handleSpeedChange = (value: string) => { if (!selectedEventData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setSpeed(value); const event = updateEvent( selectedProduct.productUuid, selectedEventData.data.modelUuid, - { - speed: parseFloat(value), - } + { speed: numericValue } ); if (event) { @@ -82,16 +98,15 @@ function VehicleMechanics() { }; const handleActionTypeChange = (option: string) => { - if (!selectedEventData || !selectedPointData) return; + if (!selectedPointData) return; + const validOption = option as "travel"; setActiveOption(validOption); const event = updateAction( selectedProduct.productUuid, selectedPointData.action.actionUuid, - { - actionType: validOption, - } + { actionType: validOption } ); if (event) { @@ -106,6 +121,8 @@ function VehicleMechanics() { const handleRenameAction = (newName: string) => { if (!selectedPointData) return; + + setActionName(newName); const event = updateAction( selectedProduct.productUuid, selectedPointData.action.actionUuid, @@ -124,12 +141,15 @@ function VehicleMechanics() { const handleLoadCapacityChange = (value: string) => { if (!selectedPointData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setLoadCapacity(value); const event = updateAction( selectedProduct.productUuid, selectedPointData.action.actionUuid, - { - loadCapacity: parseFloat(value), - } + { loadCapacity: numericValue } ); if (event) { @@ -144,12 +164,15 @@ function VehicleMechanics() { const handleUnloadDurationChange = (value: string) => { if (!selectedPointData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + setUnloadDuration(value); const event = updateAction( selectedProduct.productUuid, selectedPointData.action.actionUuid, - { - unLoadDuration: parseFloat(value), - } + { unLoadDuration: numericValue } ); if (event) { @@ -162,46 +185,18 @@ function VehicleMechanics() { } }; - const handlePickPointChange = (value: string) => { + const handleClearPoints = () => { if (!selectedPointData) return; - }; - - const handleUnloadPointChange = (value: string) => { - if (!selectedPointData) return; - }; - - // Get current values from store - - const currentSpeed = - ( - getEventByModelUuid( - selectedProduct.productUuid, - selectedEventData?.data.modelUuid || "" - ) as VehicleEventSchema | undefined - )?.speed?.toString() || "0.5"; - - const currentActionName = selectedPointData - ? selectedPointData.action.actionName - : "Action Name"; - - const currentLoadCapacity = selectedPointData - ? selectedPointData.action.loadCapacity.toString() - : "1"; - - const currentUnloadDuration = selectedPointData - ? selectedPointData.action.unLoadDuration.toString() - : "1"; - - function handleClearPoints() { - - if (!selectedEventData || !selectedPointData?.action.actionUuid) return; const event = updateAction( - selectedProduct.productUuid, selectedPointData.action.actionUuid, { - pickUpPoint: null, - unLoadPoint: null, - steeringAngle: 0, - }) + selectedProduct.productUuid, + selectedPointData.action.actionUuid, + { + pickUpPoint: null, + unLoadPoint: null, + steeringAngle: 0, + } + ); if (event) { updateBackend( @@ -211,7 +206,7 @@ function VehicleMechanics() { event ); } - } + }; const availableActions = { defaultOption: "travel", @@ -227,10 +222,10 @@ function VehicleMechanics() {
{ }} @@ -244,13 +239,13 @@ function VehicleMechanics() {
@@ -258,14 +253,14 @@ function VehicleMechanics() { {activeOption === "travel" && (
+
+ + +
)} @@ -289,4 +321,4 @@ function VehicleMechanics() { ); } -export default VehicleMechanics; +export default VehicleMechanics; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx index eb4b0ac..0697c7b 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -65,8 +65,11 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { const action = getActionByUuid(selectedProduct.productUuid, currentAction); const actionTriggers = action?.triggers || []; setTriggers(actionTriggers); + if (actionTriggers.length === 0) { + setSelectedTrigger(undefined); + } setSelectedTrigger(actionTriggers[0]); - }, [currentAction, selectedProduct]); + }, [currentAction, selectedProduct, selectedTrigger, selectedPointData]); const triggeredModel = useMemo(() => { if (!selectedProduct || !selectedTrigger?.triggeredAsset?.triggeredModel?.modelUuid) diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 1aeb646..e834f28 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -22,7 +22,6 @@ import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; import { useActiveTool, useAddAction, - useRefTextUpdate, useSelectedWallItem, useSocketStore, useToggleView, @@ -75,7 +74,6 @@ const Tools: React.FC = () => { const { setActiveSubTool, activeSubTool } = useActiveSubTool(); const { setSelectedWallItem } = useSelectedWallItem(); - const { setRefTextUpdate } = useRefTextUpdate(); const { setToggleUI } = useToggleStore(); const { setToggleView, toggleView } = useToggleView(); @@ -131,7 +129,6 @@ const Tools: React.FC = () => { const resetTools = () => { setToolMode(null); setAddAction(null); - setRefTextUpdate((prev) => prev - 1); }; const updateToolBehavior = (tool: string, is2D: boolean) => { @@ -406,7 +403,6 @@ const useStoreHooks = () => { ...useActiveTool(), ...useToolMode(), ...useAddAction(), - ...useRefTextUpdate(), }; }; diff --git a/app/src/components/ui/inputs/InputWithDropDown.tsx b/app/src/components/ui/inputs/InputWithDropDown.tsx index 61fbaf4..486faea 100644 --- a/app/src/components/ui/inputs/InputWithDropDown.tsx +++ b/app/src/components/ui/inputs/InputWithDropDown.tsx @@ -2,93 +2,89 @@ import React, { useState } from "react"; import RenameInput from "./RenameInput"; type InputWithDropDownProps = { - label: string; - value: string; - min?: number; - max?: number; - step?: number; - defaultValue?: string; - disabled?: boolean; - options?: string[]; // Array of dropdown options - activeOption?: string; // The currently active dropdown option - onClick?: () => void; - onChange: (newValue: string) => void; - editableLabel?: boolean; - placeholder?: string; // New placeholder prop + label: string; + value: string; + min?: number; + max?: number; + step?: number; + defaultValue?: string; + disabled?: boolean; + options?: string[]; + activeOption?: string; + onClick?: () => void; + onChange: (newValue: string) => void; + editableLabel?: boolean; + placeholder?: string; }; const InputWithDropDown: React.FC = ({ - label, - value, - min, - max, - step, - defaultValue, - disabled = false, - options, - activeOption, - onClick, - onChange, - editableLabel = false, - placeholder = "Inherit", // Default empty placeholder + label, + value, + min, + max, + step, + defaultValue, + disabled = false, + options, + activeOption, + onClick, + onChange, + editableLabel = false, + placeholder = "Inherit", }) => { - const separatedWords = label - .split(/(?=[A-Z])/) - .map((word) => word.trim()) - .toString(); + const [openDropdown, setOpenDropdown] = useState(false); - const [openDropdown, setOpenDropdown] = useState(false); + const separatedWords = label + .split(/(?=[A-Z])/) + .map((word) => word.trim()) + .join(" "); - return ( -
- {editableLabel ? ( - - ) : ( - - )} -
- { - onChange(e.target.value); - }} - placeholder={placeholder} // Added placeholder prop - /> - - {activeOption && ( -
{ - setOpenDropdown(true); - }} - > -
{activeOption}
- {options && openDropdown && ( -
- {options.map((option, index) => ( -
- {option} -
- ))} -
+ return ( +
+ {editableLabel ? ( + + ) : ( + )} -
- )} -
-
- ); + +
+ onChange(e.target.value)} + placeholder={placeholder} + /> + + {activeOption && ( +
setOpenDropdown((prev) => !prev)} + > +
{activeOption}
+ {options && openDropdown && ( +
+ {options.map((option, index) => ( +
+ {option} +
+ ))} +
+ )} +
+ )} +
+
+ ); }; -export default InputWithDropDown; \ No newline at end of file +export default InputWithDropDown; diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index d75387d..b158c80 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import List from "./List"; import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons"; import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect"; -import { useZones } from "../../../store/builder/store"; import { useSceneContext } from "../../../modules/scene/sceneContext"; interface DropDownListProps { @@ -44,14 +43,12 @@ const DropDownList: React.FC = ({ remove, }) => { const [isOpen, setIsOpen] = useState(defaultOpen); - // const { zones } = useZones(); const handleToggle = () => { setIsOpen((prev) => !prev); // Toggle the state }; const [zoneDataList, setZoneDataList] = useState([]); - // const { assetStore } = useSceneContext(); const { assetStore, zoneStore } = useSceneContext(); const { assets } = assetStore(); const { zones } = zoneStore() diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index dac4b40..fc0f5ec 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -14,7 +14,6 @@ import { } from "../../icons/ExportCommonIcons"; import { useZoneAssetId, - useZones, } from "../../../store/builder/store"; import { zoneCameraUpdate } from "../../../services/visulization/zone/zoneCameraUpdation"; import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index 5fc8790..e2ec99f 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -34,7 +34,7 @@ import { useComparisonProduct } from "../../../store/simulation/useSimulationSto import InputToggle from "../inputs/InputToggle"; const SimulationPlayer: React.FC = () => { - const MAX_SPEED = 4; // Maximum speed + const MAX_SPEED = 8; // Maximum speed const isDragging = useRef(false); const sliderRef = useRef(null); @@ -109,12 +109,24 @@ const SimulationPlayer: React.FC = () => { isDragging.current = false; }; + const handleVisibility = () => { + if (document.visibilityState !== 'visible' && isPlaying) { + setIsPaused(!isPaused); + if (isPaused) { + setIsPlaying(true); + } + echo.warn(`Simulation is ${isPaused ? "Resumed" : "Paused"}`); + } + } + useEffect(() => { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); + document.addEventListener('visibilitychange', handleVisibility); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); + document.removeEventListener('visibilitychange', handleVisibility); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index f51edf1..a05e423 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -139,6 +139,11 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { steeringAngle: 0, pickUpPoint: null, unLoadPoint: null, + paths: { + initPickup: [], + pickupDrop: [], + dropPickup: [], + }, triggers: [] } } @@ -256,14 +261,17 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "worker", - loadCapacity: 1, - triggers: [] - } - + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCount: 1, + loadCapacity: 1, + processTime: 10, + triggers: [] + } + ] } } addEvent(humanEvent); diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 8a09bb7..4b72cd3 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -262,6 +262,11 @@ async function handleModelLoad( steeringAngle: 0, pickUpPoint: null, unLoadPoint: null, + paths: { + initPickup: [], + pickupDrop: [], + dropPickup: [], + }, triggers: [], }, }, @@ -373,14 +378,17 @@ async function handleModelLoad( uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], rotation: [0, 0, 0], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "worker", - loadCapacity: 1, - triggers: [] - } - + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCount: 1, + loadCapacity: 1, + processTime: 10, + triggers: [] + } + ] } } addEvent(humanEvent); diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 4cc4ad5..7c3f580 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -17,7 +17,7 @@ import { useParams } from 'react-router-dom'; import { getUserData } from '../../../../../functions/getUserData'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; -import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore'; +import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore'; import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; import ConveyorCollider from './conveyorCollider'; @@ -31,6 +31,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); const { assetStore, eventStore, productStore } = useSceneContext(); const { removeAsset, setAnimations, resetAnimation, setAnimationComplete } = assetStore(); const { setTop } = useTopData(); @@ -395,7 +396,7 @@ function Model({ asset }: { readonly asset: Asset }) { const currentAction = actions.current[current]; const previousAction = previousAnimation ? actions.current[previousAnimation] : null; - if (isPlaying && currentAction) { + if (isPlaying && currentAction && activeModule === 'simulation' && !isPaused) { blendFactor.current = 0; currentAction.reset(); @@ -418,7 +419,7 @@ function Model({ asset }: { readonly asset: Asset }) { mixerRef.current.removeEventListener('finished', handleAnimationComplete); } }; - }, [asset.animationState?.current, asset.animationState?.isPlaying]); + }, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]); useEffect(() => { const canvasElement = gl.domElement; @@ -526,15 +527,7 @@ function Model({ asset }: { readonly asset: Asset }) { { - // Option A: Reset the same object - rigidBody.setTranslation({ x: 0, y: 10, z: 0 }, true); - rigidBody.setLinvel({ x: 0, y: 0, z: 0 }, true); - rigidBody.setAngvel({ x: 0, y: 0, z: 0 }, true); - rigidBody.wakeUp(); - - // Option B: You can also call a function that sets a state to "add a new rigid body" - }} /> + /> )} diff --git a/app/src/modules/builder/dfx/LoadBlueprint.tsx b/app/src/modules/builder/dfx/LoadBlueprint.tsx index 5763d26..216f2fe 100644 --- a/app/src/modules/builder/dfx/LoadBlueprint.tsx +++ b/app/src/modules/builder/dfx/LoadBlueprint.tsx @@ -1,9 +1,8 @@ import { useEffect, useRef } from 'react'; -import { useActiveLayer, useDfxUpload, useSocketStore, useToggleView, useUpdateScene } from '../../../store/builder/store'; +import { useActiveLayer, useDfxUpload, useSocketStore, useToggleView } from '../../../store/builder/store'; import { LineBasicMaterial, Line } from 'three'; import { TransformControls } from '@react-three/drei'; import { getWallPointsFromBlueprint } from './functions/getWallPointsFromBlueprint'; -import * as Types from '../../../types/world/worldTypes'; import { useParams } from 'react-router-dom'; import { getUserData } from '../../../functions/getUserData'; import { useVersionContext } from '../version/versionContext'; diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx index cc53b9d..a0470aa 100644 --- a/app/src/modules/builder/line/line.tsx +++ b/app/src/modules/builder/line/line.tsx @@ -9,6 +9,8 @@ import * as Constants from '../../../types/world/worldConstants'; import { useVersionContext } from '../version/versionContext'; import { useParams } from 'react-router-dom'; import { getUserData } from '../../../functions/getUserData'; +import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors'; +import { useSelectedPoints } from '../../../store/simulation/useSimulationStore'; // import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; // import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi'; @@ -23,7 +25,7 @@ interface LineProps { function Line({ points }: Readonly) { const [isHovered, setIsHovered] = useState(false); - const { raycaster, camera, pointer, gl } = useThree(); + const { raycaster, camera, pointer } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const [isDeletable, setIsDeletable] = useState(false); const { socket } = useSocketStore(); @@ -38,6 +40,7 @@ function Line({ points }: Readonly) { const { projectId } = useParams(); const [dragOffset, setDragOffset] = useState(null); const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore(); + const { selectedPoints } = useSelectedPoints(); const path = useMemo(() => { const [start, end] = points.map(p => new THREE.Vector3(...p.position)); @@ -213,7 +216,7 @@ function Line({ points }: Readonly) { }); } } - gl.domElement.style.cursor = 'default'; + handleCanvasCursors('default'); } } @@ -224,7 +227,7 @@ function Line({ points }: Readonly) { const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit) { - gl.domElement.style.cursor = 'move'; + handleCanvasCursors('grabbing'); const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset); const start = new THREE.Vector3(...points[0].position); @@ -269,7 +272,7 @@ function Line({ points }: Readonly) { const handleDragEnd = (points: [Point, Point]) => { if (toolMode !== 'move' || !dragOffset) return; - gl.domElement.style.cursor = 'default'; + handleCanvasCursors('default'); setDragOffset(null); if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { const updatedWalls1 = getWallsByPointId(points[0].pointUuid); @@ -372,19 +375,21 @@ function Line({ points }: Readonly) { onClick={() => { handlePointClick(points); }} - onPointerOver={() => { - if (!hoveredLine) { + onPointerOver={(e) => { + if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { setHoveredLine(points); setIsHovered(true) if (toolMode === 'move' && !hoveredPoint) { - gl.domElement.style.cursor = 'pointer'; + handleCanvasCursors('grab'); } } }} onPointerOut={() => { - if (hoveredLine) { + if (hoveredLine && isHovered) { setHoveredLine(null); - gl.domElement.style.cursor = 'default'; + if (!hoveredPoint) { + handleCanvasCursors('default'); + } } setIsHovered(false) }} diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index 73dec77..5c62dd5 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -5,6 +5,7 @@ import { useSocketStore, useToolMode } from '../../../store/builder/store'; import { DragControls } from '@react-three/drei'; import { useThree } from '@react-three/fiber'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; +import { useSelectedPoints } from '../../../store/simulation/useSimulationStore'; import { usePointSnapping } from './helpers/usePointSnapping'; import { useParams } from 'react-router-dom'; import { useVersionContext } from '../version/versionContext'; @@ -20,12 +21,14 @@ import { useSceneContext } from '../../scene/sceneContext'; // import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi'; import { getUserData } from '../../../functions/getUserData'; +import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors'; function Point({ point }: { readonly point: Point }) { const materialRef = useRef(null); - const { raycaster, camera, pointer, gl } = useThree(); + const { raycaster, camera, pointer } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const [isHovered, setIsHovered] = useState(false); + const [isSelected, setIsSelected] = useState(false); const [dragOffset, setDragOffset] = useState(null); const { socket } = useSocketStore(); const { toolMode } = useToolMode(); @@ -35,7 +38,8 @@ function Point({ point }: { readonly point: Point }) { const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore(); const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore(); const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position }); - const { hoveredPoint, setHoveredPoint } = useBuilderStore(); + const { hoveredPoint,hoveredLine, setHoveredPoint } = useBuilderStore(); + const { selectedPoints } = useSelectedPoints(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -44,7 +48,7 @@ function Point({ point }: { readonly point: Point }) { const colors = getColor(point); useEffect(() => { - gl.domElement.style.cursor = 'default'; + handleCanvasCursors('default'); }, [toolMode]) function getColor(point: Point) { @@ -114,7 +118,7 @@ function Point({ point }: { readonly point: Point }) { const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit) { - gl.domElement.style.cursor = 'move'; + handleCanvasCursors('grabbing'); const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset); const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z]; @@ -152,7 +156,7 @@ function Point({ point }: { readonly point: Point }) { }; const handleDragEnd = (point: Point) => { - gl.domElement.style.cursor = 'default'; + handleCanvasCursors('default'); setDragOffset(null); if (toolMode !== 'move') return; if (point.pointType === 'Aisle') { @@ -396,7 +400,7 @@ function Point({ point }: { readonly point: Point }) { }); } } - gl.domElement.style.cursor = 'default'; + handleCanvasCursors('default'); } } @@ -406,50 +410,63 @@ function Point({ point }: { readonly point: Point }) { } }, [hoveredPoint]) + useEffect(() => { + if (selectedPoints.length > 0 && selectedPoints.some((selectedPoint) => (selectedPoint.userData.pointUuid && selectedPoint.userData.pointUuid === point.pointUuid))) { + setIsSelected(true); + } else { + setIsSelected(false); + } + }, [selectedPoints]) + if (!point) { return null; } + return ( - handleDragStart(point)} - onDrag={() => handleDrag(point)} - onDragEnd={() => handleDragEnd(point)} - > - { - handlePointClick(point); - }} - onPointerOver={() => { - if (!hoveredPoint) { - setHoveredPoint(point); - setIsHovered(true); - if (toolMode === 'move') { - gl.domElement.style.cursor = 'pointer'; - } - } - }} - onPointerOut={() => { - if (hoveredPoint) { - setHoveredPoint(null); - gl.domElement.style.cursor = 'default'; - } - setIsHovered(false) - }} - userData={point} - > - - + {!isSelected ? + handleDragStart(point)} + onDrag={() => handleDrag(point)} + onDragEnd={() => handleDragEnd(point)} + > + { + handlePointClick(point); + }} + onPointerOver={(e) => { + if (!hoveredPoint && selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { + setHoveredPoint(point); + setIsHovered(true); + if (toolMode === 'move') { + handleCanvasCursors('grab'); + } + } + }} + onPointerOut={() => { + if (hoveredPoint) { + setHoveredPoint(null); + if(!hoveredLine){ + handleCanvasCursors('default'); + } + } + setIsHovered(false) + }} + userData={point} + > + + - - + } + /> + + + : + + + + + + + + + + + + + } + ); } diff --git a/app/src/modules/builder/wall/Instances/instance/helpers/useWallClassification.ts b/app/src/modules/builder/wall/Instances/instance/helpers/useWallClassification.ts index bc910b5..8ba211a 100644 --- a/app/src/modules/builder/wall/Instances/instance/helpers/useWallClassification.ts +++ b/app/src/modules/builder/wall/Instances/instance/helpers/useWallClassification.ts @@ -85,13 +85,45 @@ export function useWallClassification(walls: Walls) { })); } - const allCoords = mergedLineStrings.flatMap(ls => ls.geometry.coordinates); - const uniqueCoords = Array.from(new Set(allCoords.map(coord => coord.join(',')))); - if (uniqueCoords.length < 4) return []; + const validLineStrings = mergedLineStrings.map(ls => { + const coords = ls.geometry.coordinates.map(coord => coord.join(',')); - const lineStrings = turf.featureCollection(mergedLineStrings); + if (coords.length < 2) return null; - const polygons = turf.polygonize(lineStrings); + const start = coords[0]; + const end = coords[coords.length - 1]; + const middle = coords.slice(1, -1); + + const seen = new Set([start, end]); + const filteredMiddle: string[] = []; + + for (const point of middle) { + if (!seen.has(point)) { + seen.add(point); + filteredMiddle.push(point); + } + } + + const newCoords = [start, ...filteredMiddle, end]; + + if (newCoords.length >= 4) { + const resultCoords = newCoords.map(str => str.split(',').map(Number)); + return { + ...ls, + geometry: { + ...ls.geometry, + coordinates: resultCoords, + }, + }; + } + + return null; + }).filter(Boolean); + + if (validLineStrings.length === 0) return []; + + const lineStrings = turf.featureCollection(validLineStrings as any); + const polygons = turf.polygonize(lineStrings as any); const rooms: Point[][] = []; diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index 452b69a..a441ea8 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -152,9 +152,9 @@ function Wall({ wall }: { readonly wall: Wall }) { > - {wall.decals.map((decal) => ( + {/* {wall.decals.map((decal) => ( - ))} + ))} */} ); diff --git a/app/src/modules/collaboration/camera/collabCams.tsx b/app/src/modules/collaboration/camera/collabCams.tsx index 0fd3569..2cf09b1 100644 --- a/app/src/modules/collaboration/camera/collabCams.tsx +++ b/app/src/modules/collaboration/camera/collabCams.tsx @@ -255,6 +255,8 @@ const CamModelsGroup = () => { setCams((prev) => dedupeCams([...prev, ...newCams])); }); } + }).catch(() => { + console.log('Error fetching active users data') }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index f0bbc29..975ab54 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -9,10 +9,11 @@ import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi" import updateCamPosition from "../camera/updateCameraPosition"; import CamMode from "../camera/camMode"; import SwitchView from "../camera/switchView"; -import SelectionControls from "./selectionControls/selectionControls"; +import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D"; import TransformControl from "./transformControls/transformControls"; import { useParams } from "react-router-dom"; import { getUserData } from "../../../functions/getUserData"; +import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; export default function Controls() { const controlsRef = useRef(null); @@ -137,7 +138,9 @@ export default function Controls() { - + + + diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx new file mode 100644 index 0000000..790a330 --- /dev/null +++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx @@ -0,0 +1,341 @@ +import * as THREE from "three"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useFrame, useThree } from "@react-three/fiber"; +import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore"; +import useModuleStore from "../../../../../store/useModuleStore"; + +// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; +// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi"; +// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi"; +// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; + +function MoveControls2D({ + movedObjects, + setMovedObjects, + pastedObjects, + setpastedObjects, + duplicatedObjects, + setDuplicatedObjects, + rotatedObjects, + setRotatedObjects, +}: any) { + const { camera, controls, gl, scene, pointer, raycaster } = useThree(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); + const { toolMode } = useToolMode(); + const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); + const { selectedPoints, clearSelectedPoints } = useSelectedPoints(); + const { socket } = useSocketStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); + const { setPosition: setAislePosition, getAislesByPointId } = aisleStore(); + const { setPosition: setWallPosition, getWallsByPointId } = wallStore(); + const { setPosition: setFloorPosition, getFloorsByPointId } = floorStore(); + const { setPosition: setZonePosition, getZonesByPointId } = zoneStore(); + const [dragOffset, setDragOffset] = useState(null); + const [initialPositions, setInitialPositions] = useState>({}); + const [initialStates, setInitialStates] = useState>({}); + const [isMoving, setIsMoving] = useState(false); + + useEffect(() => { + if (!camera || !scene || !toggleView) return; + + const canvasElement = gl.domElement; + canvasElement.tabIndex = 0; + + let isMoving = false; + + const onPointerDown = () => { + isMoving = false; + }; + + const onPointerMove = () => { + isMoving = true; + }; + + const onPointerUp = (event: PointerEvent) => { + if (!isMoving && movedObjects.length > 0 && event.button === 0) { + event.preventDefault(); + placeMovedAssets(); + } + if (!isMoving && movedObjects.length > 0 && event.button === 2) { + event.preventDefault(); + + clearSelection(); + setMovedObjects([]); + resetToInitialPositions(); + } + }; + + const onKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; + + if (keyCombination === "G") { + if (selectedPoints.length > 0) { + moveAssets(); + } + } + + if (keyCombination === "ESCAPE") { + event.preventDefault(); + + clearSelection(); + setMovedObjects([]); + resetToInitialPositions(); + } + }; + + if (toggleView && selectedPoints.length > 0) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + canvasElement.addEventListener("keydown", onKeyDown); + } + + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + canvasElement.removeEventListener("keydown", onKeyDown); + }; + }, [camera, controls, scene, toggleView, selectedPoints, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]); + + useEffect(() => { + if (toolMode !== 'move' || !toggleView) { + if (movedObjects.length > 0) { + resetToInitialPositions(); + } + clearSelection(); + } + }, [activeModule, toolMode, toggleView, movedObjects]); + + useFrame(() => { + if (!isMoving || movedObjects.length === 0 || !dragOffset) return; + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit) { + const baseNewPosition = new THREE.Vector3().addVectors(hit, dragOffset); + + movedObjects.forEach((movedPoint: THREE.Object3D) => { + if (movedPoint.userData.pointUuid) { + const point: Point = movedPoint.userData as Point; + const initialPosition = initialPositions[movedPoint.uuid]; + + if (initialPosition) { + const relativeOffset = new THREE.Vector3().subVectors( + initialPosition, + initialPositions[movedObjects[0].uuid] + ); + + const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); + const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; + + if (point.pointType === 'Aisle') { + setAislePosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Wall') { + setWallPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Floor') { + setFloorPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Zone') { + setZonePosition(point.pointUuid, positionArray); + } + } + } + }); + } + }); + + const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { + const pointPosition = new THREE.Vector3().copy(point.position); + return new THREE.Vector3().subVectors(pointPosition, hitPoint); + }, []); + + const moveAssets = useCallback(() => { + if (selectedPoints.length === 0) return; + + const states: Record = {}; + + selectedPoints.forEach((point: THREE.Object3D) => { + states[point.uuid] = { + position: new THREE.Vector3().copy(point.position), + rotation: point.rotation ? new THREE.Euler().copy(point.rotation) : undefined + }; + }); + setInitialStates(states); + + const positions: Record = {}; + selectedPoints.forEach((point: THREE.Object3D) => { positions[point.uuid] = new THREE.Vector3().copy(point.position); }); + setInitialPositions(positions); + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit && selectedPoints[0]) { + const offset = calculateDragOffset(selectedPoints[0], hit); + setDragOffset(offset); + } + + setMovedObjects(selectedPoints); + setIsMoving(true); + }, [selectedPoints, camera, pointer, plane, raycaster, calculateDragOffset]); + + const resetToInitialPositions = useCallback(() => { + setTimeout(() => { + movedObjects.forEach((movedPoint: THREE.Object3D) => { + if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) { + const point: Point = movedPoint.userData as Point; + const initialState = initialStates[movedPoint.uuid]; + const positionArray: [number, number, number] = [ + initialState.position.x, + initialState.position.y, + initialState.position.z + ]; + + if (point.pointType === 'Aisle') { + setAislePosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Wall') { + setWallPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Floor') { + setFloorPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Zone') { + setZonePosition(point.pointUuid, positionArray); + } + } + }); + }, 0) + }, [movedObjects, initialStates, setAislePosition, setWallPosition, setFloorPosition, setZonePosition]); + + const placeMovedAssets = () => { + if (movedObjects.length === 0) return; + + movedObjects.forEach((movedObject: THREE.Object3D) => { + if (movedObject.userData.pointUuid) { + const point: Point = movedObject.userData as Point; + + if (point.pointType === 'Aisle') { + const updatedAisles = getAislesByPointId(point.pointUuid); + if (updatedAisles.length > 0 && projectId) { + updatedAisles.forEach((updatedAisle) => { + + // API + + // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: updatedAisle.aisleUuid, + points: updatedAisle.points, + type: updatedAisle.type + }) + }) + } + } else if (point.pointType === 'Wall') { + const updatedWalls = getWallsByPointId(point.pointUuid); + if (updatedWalls && updatedWalls.length > 0 && projectId) { + updatedWalls.forEach((updatedWall) => { + + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + }); + } + } else if (point.pointType === 'Floor') { + const updatedFloors = getFloorsByPointId(point.pointUuid); + if (updatedFloors && updatedFloors.length > 0 && projectId) { + updatedFloors.forEach((updatedFloor) => { + + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); + + // SOCKET + + const data = { + floorData: updatedFloor, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + }); + } + } else if (point.pointType === 'Zone') { + const updatedZones = getZonesByPointId(point.pointUuid); + if (updatedZones && updatedZones.length > 0 && projectId) { + updatedZones.forEach((updatedZone) => { + + // API + + // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone); + + // SOCKET + + const data = { + zoneData: updatedZone, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:zone:add', data); + }); + } + } + } + }) + + echo.success("Object moved!"); + + clearSelection(); + }; + + const clearSelection = () => { + setpastedObjects([]); + setDuplicatedObjects([]); + setMovedObjects([]); + setRotatedObjects([]); + clearSelectedPoints(); + }; + + return ( + <> + + ); +} + +export default MoveControls2D; diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx new file mode 100644 index 0000000..9d90d3c --- /dev/null +++ b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx @@ -0,0 +1,389 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import * as THREE from "three"; +import { useThree } from "@react-three/fiber"; +import { SelectionHelper } from "../selectionHelper"; +import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; + +import useModuleStore from "../../../../../store/useModuleStore"; +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; +import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore"; +import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; +import MoveControls2D from "./moveControls2D"; + +// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; +// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi"; +// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi"; +// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi"; +// import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi"; +// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; + +const SelectionControls2D: React.FC = () => { + const { camera, controls, gl, scene, raycaster, pointer } = useThree(); + const { toggleView } = useToggleView(); + const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints(); + const [movedObjects, setMovedObjects] = useState([]); + const [rotatedObjects, setRotatedObjects] = useState([]); + const [copiedObjects, setCopiedObjects] = useState([]); + const [pastedObjects, setpastedObjects] = useState([]); + const [duplicatedObjects, setDuplicatedObjects] = useState([]); + const { activeModule } = useModuleStore(); + const { socket } = useSocketStore(); + const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); + const { toolMode } = useToolMode(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + const { hoveredLine, hoveredPoint } = useBuilderStore(); + const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); + const { removePoint: removeAislePoint } = aisleStore(); + const { removePoint: removeWallPoint } = wallStore(); + const { removePoint: removeFloorPoint } = floorStore(); + const { removePoint: removeZonePoint } = zoneStore(); + + const isDragging = useRef(false); + const isLeftMouseDown = useRef(false); + const isSelecting = useRef(false); + const isRightClick = useRef(false); + const rightClickMoved = useRef(false); + const isCtrlSelecting = useRef(false); + const isShiftSelecting = useRef(false); + const { userId, organization } = getUserData(); + + useEffect(() => { + if (!camera || !scene || !toggleView) return; + + const canvasElement = gl.domElement; + canvasElement.tabIndex = 0; + + const helper = new SelectionHelper(gl); + + const onPointerDown = (event: PointerEvent) => { + if (event.button === 2) { + isRightClick.current = true; + rightClickMoved.current = false; + } else if (event.button === 0) { + isSelecting.current = false; + isCtrlSelecting.current = event.ctrlKey; + isShiftSelecting.current = event.shiftKey; + isLeftMouseDown.current = true; + isDragging.current = false; + if (event.ctrlKey && duplicatedObjects.length === 0) { + if (controls) (controls as any).enabled = false; + selectionBox.startPoint.set(pointer.x, pointer.y, 0); + } + } + }; + + const onPointerMove = (event: PointerEvent) => { + if (isRightClick.current) { + rightClickMoved.current = true; + } + if (isLeftMouseDown.current) { + isDragging.current = true; + } + isSelecting.current = true; + if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting.current) { + selectionBox.endPoint.set(pointer.x, pointer.y, 0); + } + }; + + const onPointerUp = (event: PointerEvent) => { + if (event.button === 2 && !event.ctrlKey && !event.shiftKey) { + isRightClick.current = false; + if (!rightClickMoved.current) { + clearSelection(); + } + return; + } + + if (isSelecting.current && isCtrlSelecting.current) { + isCtrlSelecting.current = false; + isSelecting.current = false; + if (event.ctrlKey && duplicatedObjects.length === 0) { + selectAssets(); + } + } else if (!isSelecting.current && selectedPoints.length > 0 && ((!event.ctrlKey && !event.shiftKey && pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) { + clearSelection(); + helper.enabled = true; + isCtrlSelecting.current = false; + } else if (controls) { + (controls as any).enabled = true; + } + + if (!isDragging.current && isLeftMouseDown.current && isShiftSelecting.current && event.shiftKey) { + isShiftSelecting.current = false; + isLeftMouseDown.current = false; + isDragging.current = false; + + } else if (controls) { + (controls as any).enabled = true; + } + }; + + const onKeyDown = (event: KeyboardEvent) => { + if (movedObjects.length > 0 || rotatedObjects.length > 0) return; + if (event.key.toLowerCase() === "escape") { + event.preventDefault(); + clearSelection(); + } + if (event.key.toLowerCase() === "delete") { + event.preventDefault(); + deleteSelection(); + } + }; + + const onContextMenu = (event: MouseEvent) => { + event.preventDefault(); + if (!rightClickMoved.current) { + clearSelection(); + } + rightClickMoved.current = false; + }; + + if (toggleView && toolMode === 'move') { + helper.enabled = true; + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("contextmenu", onContextMenu); + canvasElement.addEventListener("keydown", onKeyDown); + } else { + helper.enabled = false; + helper.dispose(); + } + + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("contextmenu", onContextMenu); + canvasElement.removeEventListener("pointerup", onPointerUp); + canvasElement.removeEventListener("keydown", onKeyDown); + helper.enabled = false; + helper.dispose(); + }; + }, [camera, controls, scene, toggleView, selectedPoints, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, toolMode, hoveredLine, hoveredPoint]); + + useEffect(() => { + if (toolMode !== 'move' || !toggleView) { + clearSelection(); + } + }, [activeModule, toolMode, toggleView]); + + const selectAssets = useCallback(() => { + selectionBox.endPoint.set(pointer.x, pointer.y, 0); + if (controls) (controls as any).enabled = true; + + let selectedObjects = selectionBox.select(); + let Objects = new Set(); + + selectedObjects.forEach((object) => { + let currentObject: THREE.Object3D | null = object; + while (currentObject) { + if (currentObject.userData.pointUuid) { + Objects.add(currentObject); + break; + } + currentObject = currentObject.parent || null; + } + }); + + if (Objects.size === 0) { + clearSelection(); + return; + } + + const updatedSelections = new Set(selectedPoints); + + Objects.forEach((obj) => { + const existing = Array.from(updatedSelections).find((o) => o.userData?.pointUuid === obj.userData?.pointUuid); + + if (existing) { + updatedSelections.delete(existing); + } else { + updatedSelections.add(obj); + } + }); + + const selected = Array.from(updatedSelections); + + setSelectedPoints(selected); + + }, [selectionBox, pointer, controls, selectedPoints, setSelectedPoints]); + + const clearSelection = () => { + setpastedObjects([]); + setDuplicatedObjects([]); + clearSelectedPoints(); + }; + + const deleteSelection = () => { + if (selectedPoints.length > 0 && duplicatedObjects.length === 0) { + + selectedPoints.forEach((selectedPoint) => { + if (selectedPoint.userData.pointUuid) { + const point: Point = selectedPoint.userData as Point; + if (point.pointType === 'Aisle') { + const removedAisles = removeAislePoint(point.pointUuid); + if (removedAisles.length > 0) { + removedAisles.forEach(aisle => { + if (projectId) { + + // API + + // deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + const data = { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid + } + + socket.emit('v1:model-aisle:delete', data); + } + }); + } + } + if (point.pointType === 'Wall') { + const removedWalls = removeWallPoint(point.pointUuid); + if (removedWalls.length > 0) { + removedWalls.forEach(wall => { + if (projectId) { + + // API + + // deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid); + + // SOCKET + + const data = { + wallUuid: wall.wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:delete', data); + } + }); + } + } + if (point.pointType === 'Floor') { + const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); + if (removedFloors.length > 0) { + removedFloors.forEach(floor => { + if (projectId) { + + // API + + // deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid); + + // SOCKET + + const data = { + floorUuid: floor.floorUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:delete', data); + } + }); + } + if (updatedFloors.length > 0) { + updatedFloors.forEach(floor => { + if (projectId) { + + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', floor); + + // SOCKET + + const data = { + floorData: floor, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + }); + } + } + if (point.pointType === 'Zone') { + const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); + if (removedZones.length > 0) { + removedZones.forEach(zone => { + if (projectId) { + + // API + + // deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid); + + // SOCKET + + const data = { + zoneUuid: zone.zoneUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:zone:delete', data); + } + }); + } + if (updatedZones.length > 0) { + updatedZones.forEach(zone => { + if (projectId) { + + // API + + // upsertZoneApi(projectId, selectedVersion?.versionId || '', zone); + + // SOCKET + + const data = { + zoneData: zone, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:zone:add', data); + } + }); + } + } + } + }) + + } + echo.success("Selected points removed!"); + clearSelection(); + }; + + return ( + <> + + + + ); +}; + +export default SelectionControls2D; diff --git a/app/src/modules/scene/controls/selectionControls/boundingBoxHelper.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/boundingBoxHelper3D.tsx similarity index 98% rename from app/src/modules/scene/controls/selectionControls/boundingBoxHelper.tsx rename to app/src/modules/scene/controls/selectionControls/selection3D/boundingBoxHelper3D.tsx index 43e044c..dde668e 100644 --- a/app/src/modules/scene/controls/selectionControls/boundingBoxHelper.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/boundingBoxHelper3D.tsx @@ -1,7 +1,7 @@ import { Line } from "@react-three/drei"; import { useMemo } from "react"; import * as THREE from "three"; -import { useSelectedAssets } from "../../../../store/builder/store"; +import { useSelectedAssets } from "../../../../../store/builder/store"; interface BoundingBoxProps { boundingBoxRef?: any; diff --git a/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx similarity index 94% rename from app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx rename to app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index 6a48465..9c81a82 100644 --- a/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -2,17 +2,17 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; -import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/builder/store"; -import * as Types from "../../../../types/world/worldTypes"; -import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys"; +import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; +import * as Types from "../../../../../types/world/worldTypes"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { useParams } from "react-router-dom"; -import { getUserData } from "../../../../functions/getUserData"; -import { useSceneContext } from "../../sceneContext"; -import { useVersionContext } from "../../../builder/version/versionContext"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; -// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; +// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; -const CopyPasteControls = ({ +const CopyPasteControls3D = ({ copiedObjects, setCopiedObjects, pastedObjects, @@ -239,6 +239,11 @@ const CopyPasteControls = ({ steeringAngle: 0, pickUpPoint: null, unLoadPoint: null, + paths: { + initPickup: [], + pickupDrop: [], + dropPickup: [], + }, triggers: [] } } @@ -352,13 +357,17 @@ const CopyPasteControls = ({ uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "worker", - loadCapacity: 1, - triggers: [] - } + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + loadCount: 1, + processTime: 10, + triggers: [] + } + ] } } addEvent(humanEvent); @@ -495,4 +504,4 @@ const CopyPasteControls = ({ return null; }; -export default CopyPasteControls; \ No newline at end of file +export default CopyPasteControls3D; \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/distanceFindingControls.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/distanceFindingControls.tsx similarity index 100% rename from app/src/modules/scene/controls/selectionControls/distanceFindingControls.tsx rename to app/src/modules/scene/controls/selectionControls/selection3D/distanceFindingControls.tsx diff --git a/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx similarity index 93% rename from app/src/modules/scene/controls/selectionControls/duplicationControls.tsx rename to app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 38c62d1..38816f4 100644 --- a/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -2,17 +2,17 @@ import * as THREE from "three"; import { useEffect, useMemo } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; -import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/builder/store"; -import * as Types from "../../../../types/world/worldTypes"; -import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys"; +import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; +import * as Types from "../../../../../types/world/worldTypes"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { useParams } from "react-router-dom"; -import { getUserData } from "../../../../functions/getUserData"; -import { useSceneContext } from "../../sceneContext"; -import { useVersionContext } from "../../../builder/version/versionContext"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; -// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; +// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; -const DuplicationControls = ({ +const DuplicationControls3D = ({ duplicatedObjects, setDuplicatedObjects, setpastedObjects, @@ -213,6 +213,11 @@ const DuplicationControls = ({ steeringAngle: 0, pickUpPoint: null, unLoadPoint: null, + paths: { + initPickup: [], + pickupDrop: [], + dropPickup: [], + }, triggers: [] } } @@ -326,13 +331,17 @@ const DuplicationControls = ({ uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "worker", - loadCapacity: 1, - triggers: [] - } + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + loadCount: 1, + processTime: 10, + triggers: [] + } + ] } } addEvent(humanEvent); @@ -464,4 +473,4 @@ const DuplicationControls = ({ return null; }; -export default DuplicationControls; \ No newline at end of file +export default DuplicationControls3D; \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/moveControls.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx similarity index 94% rename from app/src/modules/scene/controls/selectionControls/moveControls.tsx rename to app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 79cf195..6658f62 100644 --- a/app/src/modules/scene/controls/selectionControls/moveControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -1,20 +1,21 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../store/builder/store"; -// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; -import * as Types from "../../../../types/world/worldTypes"; -import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys"; -import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; -import { snapControls } from "../../../../utils/handleSnap"; +import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store"; +import * as Types from "../../../../../types/world/worldTypes"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; +import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; +import { snapControls } from "../../../../../utils/handleSnap"; import DistanceFindingControls from "./distanceFindingControls"; import { useParams } from "react-router-dom"; -import { useProductContext } from "../../../simulation/products/productContext"; -import { getUserData } from "../../../../functions/getUserData"; -import { useSceneContext } from "../../sceneContext"; -import { useVersionContext } from "../../../builder/version/versionContext"; +import { useProductContext } from "../../../../simulation/products/productContext"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; -function MoveControls({ +// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; + +function MoveControls3D({ movedObjects, setMovedObjects, pastedObjects, @@ -364,4 +365,4 @@ function MoveControls({ ); } -export default MoveControls; +export default MoveControls3D; diff --git a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx similarity index 94% rename from app/src/modules/scene/controls/selectionControls/rotateControls.tsx rename to app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index 216d4f7..fdde56a 100644 --- a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -1,17 +1,18 @@ import * as THREE from "three"; import { useEffect, useMemo, useRef } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/builder/store"; -// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; -import * as Types from "../../../../types/world/worldTypes"; -import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; +import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; +import * as Types from "../../../../../types/world/worldTypes"; +import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useParams } from "react-router-dom"; -import { useProductContext } from "../../../simulation/products/productContext"; -import { getUserData } from "../../../../functions/getUserData"; -import { useSceneContext } from "../../sceneContext"; -import { useVersionContext } from "../../../builder/version/versionContext"; +import { useProductContext } from "../../../../simulation/products/productContext"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; -function RotateControls({ +// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; + +function RotateControls3D({ rotatedObjects, setRotatedObjects, movedObjects, @@ -300,4 +301,4 @@ function RotateControls({ return null; } -export default RotateControls \ No newline at end of file +export default RotateControls3D \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selectionControls.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx similarity index 84% rename from app/src/modules/scene/controls/selectionControls/selectionControls.tsx rename to app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index 9a5ab33..080a5d8 100644 --- a/app/src/modules/scene/controls/selectionControls/selectionControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -1,26 +1,27 @@ -import * as THREE from "three"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; -import { SelectionHelper } from "./selectionHelper"; +import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; -import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../store/builder/store"; -import BoundingBox from "./boundingBoxHelper"; -// import { deleteFloorItem } from '../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi'; -import * as Types from "../../../../types/world/worldTypes"; +import { SelectionHelper } from "../selectionHelper"; +import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; +import * as Types from "../../../../../types/world/worldTypes"; -import DuplicationControls from "./duplicationControls"; -import CopyPasteControls from "./copyPasteControls"; -import MoveControls from "./moveControls"; -import RotateControls from "./rotateControls"; -import useModuleStore from "../../../../store/useModuleStore"; +import useModuleStore from "../../../../../store/useModuleStore"; import { useParams } from "react-router-dom"; -import { getUserData } from "../../../../functions/getUserData"; -import { useSceneContext } from "../../sceneContext"; -import { useVersionContext } from "../../../builder/version/versionContext"; -import { useProductContext } from "../../../simulation/products/productContext"; -import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useProductContext } from "../../../../simulation/products/productContext"; +import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; +import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; +import BoundingBox from "./boundingBoxHelper3D"; +import DuplicationControls3D from "./duplicationControls3D"; +import CopyPasteControls3D from "./copyPasteControls3D"; +import MoveControls3D from "./moveControls3D"; +import RotateControls3D from "./rotateControls3D"; -const SelectionControls: React.FC = () => { +// import { deleteFloorItem } from '../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi'; + +const SelectionControls3D: React.FC = () => { const { camera, controls, gl, scene, raycaster, pointer } = useThree(); const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); @@ -253,7 +254,7 @@ const SelectionControls: React.FC = () => { }); if (Objects.size === 0) { - clearSelection(); + // clearSelection(); return; } @@ -350,15 +351,15 @@ const SelectionControls: React.FC = () => { - + - + - + - + ); }; -export default SelectionControls; +export default SelectionControls3D; diff --git a/app/src/modules/scene/environment/shadow.tsx b/app/src/modules/scene/environment/shadow.tsx index d8c8002..3321673 100644 --- a/app/src/modules/scene/environment/shadow.tsx +++ b/app/src/modules/scene/environment/shadow.tsx @@ -6,7 +6,6 @@ import { useElevation, useShadows, useSunPosition, - useWallItems, useTileDistance, } from "../../../store/builder/store"; import * as CONSTANTS from "../../../types/world/worldConstants"; @@ -25,13 +24,12 @@ export default function Shadows() { const { controls, gl } = useThree(); const { elevation, setElevation } = useElevation(); const { azimuth, setAzimuth } = useAzimuth(); - const { wallItems } = useWallItems(); const { planeValue } = useTileDistance(); useEffect(() => { gl.shadowMap.enabled = true; gl.shadowMap.type = THREE.PCFShadowMap; - }, [gl, wallItems]); + }, [gl]); useEffect(() => { if (lightRef.current && targetRef.current) { diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 7c6c719..c70f733 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -6,11 +6,12 @@ import { useSelectedFloorItem, } from "../../../store/builder/store"; import * as CONSTANTS from "../../../types/world/worldConstants"; -import { useDeletableEventSphere, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; +import { useDeletableEventSphere, useSelectedEventSphere, useSelectedPoints } from "../../../store/simulation/useSimulationStore"; import { useEffect } from "react"; import { useBuilderStore } from "../../../store/builder/useBuilderStore"; export default function PostProcessing() { + const { selectedPoints } = useSelectedPoints(); const { deletableFloorItem } = useDeletableFloorItem(); const { selectedWallItem } = useSelectedWallItem(); const { selectedFloorItem } = useSelectedFloorItem(); @@ -61,6 +62,10 @@ export default function PostProcessing() { // console.log('deletableEventSphere: ', deletableEventSphere); }, [deletableEventSphere]) + useEffect(() => { + // console.log('selectedPoints: ', selectedPoints); + }, [selectedPoints]) + return ( { echo.info(`${materialUuid}, ${status}`); @@ -17,6 +17,7 @@ export function useDespawnHandler() { setEndTime(material.materialId, performance.now()); removeMaterial(material.materialId); + clearLocations(material.materialId); deSpawnLogStatus(material.materialName, `Despawned`); diff --git a/app/src/modules/simulation/actions/human/actionHandler/useAssemblyHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useAssemblyHandler.ts index a654b35..602de27 100644 --- a/app/src/modules/simulation/actions/human/actionHandler/useAssemblyHandler.ts +++ b/app/src/modules/simulation/actions/human/actionHandler/useAssemblyHandler.ts @@ -8,7 +8,7 @@ export function useAssemblyHandler() { const { getModelUuidByActionUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { incrementHumanLoad, addCurrentMaterial } = humanStore(); + const { incrementHumanLoad, addCurrentMaterial, addCurrentAction } = humanStore(); const assemblyLogStatus = (materialUuid: string, status: string) => { echo.info(`${materialUuid}, ${status}`); @@ -24,6 +24,7 @@ export function useAssemblyHandler() { if (!modelUuid) return; incrementHumanLoad(modelUuid, 1); + addCurrentAction(modelUuid, action.actionUuid); addCurrentMaterial(modelUuid, material.materialType, material.materialId); assemblyLogStatus(material.materialName, `performing assembly action`); diff --git a/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts index 9a1cf99..efefc39 100644 --- a/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts +++ b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts @@ -8,7 +8,7 @@ export function useWorkerHandler() { const { getModelUuidByActionUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { incrementHumanLoad, addCurrentMaterial } = humanStore(); + const { incrementHumanLoad, incrementLoadCount, addCurrentMaterial, addCurrentAction } = humanStore(); const workerLogStatus = (materialUuid: string, status: string) => { echo.info(`${materialUuid}, ${status}`); @@ -24,6 +24,8 @@ export function useWorkerHandler() { if (!modelUuid) return; incrementHumanLoad(modelUuid, 1); + incrementLoadCount(modelUuid, 1); + addCurrentAction(modelUuid, action.actionUuid); addCurrentMaterial(modelUuid, material.materialType, material.materialId); workerLogStatus(material.materialName, `performing worker action`); diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index c4b4245..79d6401 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -5,12 +5,14 @@ import { useSceneContext } from "../../../../scene/sceneContext"; import { useProductContext } from "../../../products/productContext"; export function useRetrieveHandler() { - const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore } = useSceneContext(); + const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { addMaterial } = materialStore(); const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore(); const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore(); + const { getHumanById, incrementHumanLoad, incrementLoadCount, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore(); + const { getAssetById, setCurrentAnimation } = assetStore(); const { selectedProduct } = selectedProductStore(); const { getArmBotById, addCurrentAction } = armBotStore(); const { isPlaying } = usePlayButtonStore(); @@ -269,7 +271,7 @@ export function useRetrieveHandler() { if (material) { removeLastMaterial(storageUnit.modelUuid); - updateCurrentLoad(storageUnit.modelUuid, -1) + updateCurrentLoad(storageUnit.modelUuid, -1); incrementVehicleLoad(vehicle.modelUuid, 1); addCurrentMaterial(vehicle.modelUuid, material.materialType, material.materialId); retrieveLogStatus(material.materialName, `is picked by ${vehicle.modelName}`); @@ -293,6 +295,37 @@ export function useRetrieveHandler() { retrievalTimeRef.current.delete(actionUuid); retrievalTimeRef.current.delete(`${actionUuid}_last`); } + } else if (triggeredModel.type === 'human') { + const human = getHumanById(triggeredModel.modelUuid); + const humanAsset = getAssetById(triggeredModel.modelUuid); + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + + if (human && !human.isScheduled && human.state === 'idle' && human.currentLoad < (action as HumanAction).loadCapacity) { + if (humanAsset && humanAsset.animationState?.current === 'idle') { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } else if (humanAsset && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { + const lastMaterial = getLastMaterial(storageUnit.modelUuid); + if (lastMaterial) { + if (action && human.currentLoad < (action as HumanAction).loadCapacity) { + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + incrementHumanLoad(human.modelUuid, 1); + incrementLoadCount(human.modelUuid, 1); + addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); + retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); + } + if (human.currentLoad + 1 < (action as HumanAction).loadCapacity) { + } + } + } + } + } } }); diff --git a/app/src/modules/simulation/conveyor/eventManager/useConveyorEventManager.ts b/app/src/modules/simulation/conveyor/eventManager/useConveyorEventManager.ts index 34407c9..76a1047 100644 --- a/app/src/modules/simulation/conveyor/eventManager/useConveyorEventManager.ts +++ b/app/src/modules/simulation/conveyor/eventManager/useConveyorEventManager.ts @@ -6,65 +6,92 @@ import { useSceneContext } from '../../../scene/sceneContext'; type ConveyorCallback = { conveyorId: string; callback: () => void; + except: string[]; }; export function useConveyorEventManager() { - const { conveyorStore } = useSceneContext(); - const { getConveyorById } = conveyorStore(); - const callbacksRef = useRef([]); + const { materialStore } = useSceneContext(); + const { getMaterialsByCurrentModelUuid } = materialStore(); + const callbacksRef = useRef>(new Map()); + const isCooldownRef = useRef>(new Map()); const isMonitoringRef = useRef(false); + const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); const { isReset } = useResetButtonStore(); useEffect(() => { if (isReset) { - callbacksRef.current = []; + callbacksRef.current.clear(); + isCooldownRef.current.clear(); + isMonitoringRef.current = false; } - }, [isReset]) + }, [isReset]); - // Add a new conveyor to monitor - const addConveyorToMonitor = (conveyorId: string, callback: () => void) => { - // Avoid duplicates - if (!callbacksRef.current.some((entry) => entry.conveyorId === conveyorId)) { - callbacksRef.current.push({ conveyorId, callback }); + const addConveyorToMonitor = (conveyorId: string, callback: () => void, except?: string[]) => { + if (!callbacksRef.current.has(conveyorId)) { + callbacksRef.current.set(conveyorId, []); } - // Start monitoring if not already running - if (!isMonitoringRef.current) { - isMonitoringRef.current = true; - } + callbacksRef.current.get(conveyorId)!.push({ conveyorId, callback, except: except || [] }); + isMonitoringRef.current = true; }; - // Remove a conveyor from monitoring const removeConveyorFromMonitor = (conveyorId: string) => { - callbacksRef.current = callbacksRef.current.filter( - (entry) => entry.conveyorId !== conveyorId - ); - - // Stop monitoring if no more conveyors to track - if (callbacksRef.current.length === 0) { + callbacksRef.current.delete(conveyorId); + isCooldownRef.current.delete(conveyorId); + if (callbacksRef.current.size === 0) { isMonitoringRef.current = false; } }; - // Check conveyor states every frame useFrame(() => { - if (!isMonitoringRef.current || callbacksRef.current.length === 0 || !isPlaying || isPaused) return; + if (!isMonitoringRef.current || !isPlaying || isPaused) return; - callbacksRef.current.forEach(({ conveyorId, callback }) => { - const conveyor = getConveyorById(conveyorId); - if (conveyor?.isPaused === false) { - callback(); - removeConveyorFromMonitor(conveyorId); // Remove after triggering + callbacksRef.current.forEach((queue, conveyorId) => { + if (!queue || queue.length === 0) return; + if (isCooldownRef.current.get(conveyorId)) return; + + const { callback, except } = queue[0]; + const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyorId); + + let conditionMet = false; + + if (!conveyorMaterials || conveyorMaterials.length === 0) { + conditionMet = true; + } else { + const pausedMaterials = conveyorMaterials.filter(m => m.isPaused); + + if (pausedMaterials.length === 0) { + conditionMet = true; + } else { + const allPausedInExcept = pausedMaterials.filter(m => !except.includes(m.materialId)); + if (allPausedInExcept.length === 0) { + conditionMet = true; + } + } + } + + if (conditionMet) { + queue.shift(); + if (callback) callback(); + + if (queue.length === 0) { + removeConveyorFromMonitor(conveyorId); + } else { + isCooldownRef.current.set(conveyorId, true); + setTimeout(() => { + isCooldownRef.current.set(conveyorId, false); + }, 1000); + } } }); }); - // Cleanup on unmount useEffect(() => { return () => { - callbacksRef.current = []; + callbacksRef.current.clear(); + isCooldownRef.current.clear(); isMonitoringRef.current = false; }; }, []); @@ -73,4 +100,4 @@ export function useConveyorEventManager() { addConveyorToMonitor, removeConveyorFromMonitor, }; -} \ No newline at end of file +} diff --git a/app/src/modules/simulation/events/arrows/arrows.tsx b/app/src/modules/simulation/events/arrows/arrows.tsx index 2053fe5..0b8e821 100644 --- a/app/src/modules/simulation/events/arrows/arrows.tsx +++ b/app/src/modules/simulation/events/arrows/arrows.tsx @@ -103,9 +103,21 @@ export function Arrows({ connections }: { readonly connections: ConnectionLine[] return ( setHoveredArrowTrigger(trigger.triggerUuid)} - onPointerOut={() => setHoveredArrowTrigger(null)} - onClick={() => { removeConnection(trigger) }} + onPointerOver={() => { + if (toolMode === '3D-Delete') { + setHoveredArrowTrigger(trigger.triggerUuid) + } + }} + onPointerOut={() => { + if (toolMode === '3D-Delete') { + setHoveredArrowTrigger(null) + } + }} + onClick={() => { + if (toolMode === '3D-Delete') { + removeConnection(trigger) + } + }} > { @@ -155,8 +160,8 @@ function TriggerConnector() { // Handle Human point else if (event.type === "human" && 'point' in event) { const point = event.point; - if (point.action?.triggers) { - point.action.triggers.forEach(trigger => { + point.actions?.forEach(action => { + action.triggers?.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, @@ -166,7 +171,7 @@ function TriggerConnector() { }); } }); - } + }); } }); diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index 4851c1c..2d2fc11 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -2,76 +2,163 @@ import { useEffect, useRef } from 'react'; import { useFrame } from '@react-three/fiber'; import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../scene/sceneContext'; - -type HumanCallback = { - humanId: string; - callback: () => void; -}; +import { useProductContext } from '../../products/productContext'; export function useHumanEventManager() { - const { humanStore } = useSceneContext(); - const { getHumanById } = humanStore(); - const callbacksRef = useRef([]); + const { humanStore, productStore, assetStore } = useSceneContext(); + const { getHumanById, clearLoadCount, setCurrentPhase } = humanStore(); + const { getAssetById } = assetStore(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + + const callbacksRef = useRef void)[]>>(new Map()); + const actionQueueRef = useRef>(new Map()); + const isCooldownRef = useRef>(new Map()); const isMonitoringRef = useRef(false); + const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); const { isReset } = useResetButtonStore(); useEffect(() => { if (isReset) { - callbacksRef.current = []; + callbacksRef.current.clear(); + actionQueueRef.current.clear(); + isCooldownRef.current.clear(); + isMonitoringRef.current = false; } - }, [isReset]) + }, [isReset]); - // Add a new human to monitor - const addHumanToMonitor = (humanId: string, callback: () => void) => { - // Avoid duplicates - if (!callbacksRef.current.some((entry) => entry.humanId === humanId)) { - callbacksRef.current.push({ humanId, callback }); + const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { + const action = getActionByUuid(selectedProduct.productUuid, actionUuid || '') as HumanAction | undefined; + + if (!action) return; + + const actionType = action.actionType; + if (actionType !== "worker" && actionType !== "assembly") return; + + if (!callbacksRef.current.has(humanId)) { + callbacksRef.current.set(humanId, []); + actionQueueRef.current.set(humanId, []); } - // Start monitoring if not already running - if (!isMonitoringRef.current) { - isMonitoringRef.current = true; - } + callbacksRef.current.get(humanId)!.push(callback); + actionQueueRef.current.get(humanId)!.push({ actionType, actionUuid }); + + isMonitoringRef.current = true; }; - // Remove a human from monitoring const removeHumanFromMonitor = (humanId: string) => { - callbacksRef.current = callbacksRef.current.filter( - (entry) => entry.humanId !== humanId - ); + callbacksRef.current.delete(humanId); + actionQueueRef.current.delete(humanId); + isCooldownRef.current.delete(humanId); - // Stop monitoring if no more humans to track - if (callbacksRef.current.length === 0) { + if (callbacksRef.current.size === 0) { isMonitoringRef.current = false; } }; - // Check human states every frame useFrame(() => { - if (!isMonitoringRef.current || callbacksRef.current.length === 0 || !isPlaying || isPaused) return; + if (!isMonitoringRef.current || !isPlaying || isPaused) return; - callbacksRef.current.forEach(({ humanId, callback }) => { + callbacksRef.current.forEach((queue, humanId) => { + if (queue.length === 0 || isCooldownRef.current.get(humanId)) return; + + const actionQueue = actionQueueRef.current.get(humanId); + if (!actionQueue || actionQueue.length === 0) return; + + const { actionType: expectedActionType, actionUuid } = actionQueue[0]; const human = getHumanById(humanId); - if (human?.point.action.actionType === 'worker') { - if (human && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { - callback(); - removeHumanFromMonitor(humanId); // Remove after triggering + const humanAsset = getAssetById(humanId); + const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as HumanAction | undefined; + + if (!humanAsset || !human || !action || action.actionType !== expectedActionType) return; + + let conditionMet = false; + + const currentAction = getActionByUuid(selectedProduct.productUuid, human.currentAction?.actionUuid || '') as HumanAction | undefined; + + if (expectedActionType === "worker") { + if (currentAction && currentAction.actionType === 'worker') { + conditionMet = ( + !human.isActive && + human.state === "idle" && + humanAsset.animationState?.current === 'idle' && + human.currentLoad < currentAction.loadCapacity + ); + + if (human.totalLoadCount >= currentAction.loadCount && actionUuid === human.currentAction?.actionUuid) { + queue.shift(); + actionQueue.shift(); + if (queue.length === 0) { + removeHumanFromMonitor(humanId); + } + return; + } + + if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { + setCurrentPhase(human.modelUuid, 'init'); + } + } else { + conditionMet = ( + !human.isActive && + human.state === "idle" && + humanAsset.animationState?.current === 'idle' && + human.currentLoad < action.loadCapacity + ); + if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { + setCurrentPhase(human.modelUuid, 'init'); + } } - } else if (human?.point.action.actionType === 'assembly') { - if (human && human.isActive === false && human.state === 'idle') { - callback(); - removeHumanFromMonitor(humanId); // Remove after triggering + + } else if (expectedActionType === "assembly") { + if (currentAction && currentAction.actionType === 'worker') { + conditionMet = ( + !human.isActive && + human.state === "idle" && + humanAsset.animationState?.current === 'idle' && + human.currentLoad < currentAction.loadCapacity + ); + if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { + setCurrentPhase(human.modelUuid, 'init'); + } + } else { + conditionMet = ( + !human.isActive && + human.state === "idle" && + humanAsset.animationState?.current === 'idle' && + human.currentLoad < action.loadCapacity + ) + } + if (conditionMet) { + clearLoadCount(human.modelUuid); + } + } + + if (conditionMet) { + const callback = queue.shift(); + actionQueue.shift(); + + if (callback) callback(); + + if (queue.length === 0) { + removeHumanFromMonitor(humanId); + } else { + isCooldownRef.current.set(humanId, true); + setTimeout(() => { + isCooldownRef.current.set(humanId, false); + }, 1000); } } }); - }); + }, 0); - // Cleanup on unmount useEffect(() => { return () => { - callbacksRef.current = []; + callbacksRef.current.clear(); + actionQueueRef.current.clear(); + isCooldownRef.current.clear(); isMonitoringRef.current = false; }; }, []); @@ -80,4 +167,4 @@ export function useHumanEventManager() { addHumanToMonitor, removeHumanFromMonitor, }; -} \ No newline at end of file +} diff --git a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx index 2b89bc2..5415d05 100644 --- a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx @@ -5,18 +5,21 @@ import * as THREE from 'three'; import { Line } from '@react-three/drei'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; interface HumanAnimatorProps { path: [number, number, number][]; handleCallBack: () => void; reset: () => void; startUnloadingProcess: () => void; - currentPhase: string; human: HumanStatus; } -function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, startUnloadingProcess }: Readonly) { - const { humanStore, assetStore } = useSceneContext(); +function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly) { + const { humanStore, assetStore, productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); const { getHumanById } = humanStore(); const { setCurrentAnimation } = assetStore(); const { isPaused } = usePauseButtonStore(); @@ -26,24 +29,30 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start const progressRef = useRef(0); const movingForward = useRef(true); const completedRef = useRef(false); - const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>(human.point?.action?.pickUpPoint?.rotation || [0, 0, 0]) + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.pickUpPoint?.rotation || [0, 0, 0]) const [restRotation, setRestingRotation] = useState(true); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); const rigidBodyRef = useRef(null); const { scene } = useThree(); useEffect(() => { - if (currentPhase === 'init-pickup' && path.length > 0) { + if (!human.currentAction?.actionUuid) return; + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + if (human.currentPhase === 'init-pickup' && path.length > 0) { setCurrentPath(path); - setObjectRotation(human.point.action.pickUpPoint?.rotation ?? null) - } else if (currentPhase === 'pickup-drop' && path.length > 0) { - setObjectRotation(human.point.action?.dropPoint?.rotation ?? null) + setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null) + } else if (human.currentPhase === 'init-assembly' && path.length > 0) { + setObjectRotation((action as HumanAction)?.assemblyPoint?.rotation ?? null) setCurrentPath(path); - } else if (currentPhase === 'drop-pickup' && path.length > 0) { - setObjectRotation(human.point.action?.pickUpPoint?.rotation ?? null) + } else if (human.currentPhase === 'pickup-drop' && path.length > 0) { + setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null) + setCurrentPath(path); + } else if (human.currentPhase === 'drop-pickup' && path.length > 0) { + setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null) setCurrentPath(path); } - }, [currentPhase, path, objectRotation]); + }, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); useEffect(() => { completedRef.current = false; @@ -76,7 +85,7 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start const object = scene.getObjectByProperty('uuid', human.modelUuid); if (!object || currentPath.length < 2) return; - if (isPaused) return; + if (isPaused || !isPlaying) return; let totalDistance = 0; const distances = []; @@ -127,13 +136,13 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); - if (human.currentMaterials.length > 0) { + if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); } else { setCurrentAnimation(human.modelUuid, 'walking', true, true, true); } } else { - if (human.currentMaterials.length > 0) { + if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); } else { setCurrentAnimation(human.modelUuid, 'idle', true, true, true); @@ -180,7 +189,7 @@ function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, start movingForward.current = !movingForward.current; setCurrentPath([]); handleCallBack(); - if (currentPhase === 'pickup-drop') { + if (human.currentPhase === 'pickup-drop') { requestAnimationFrame(startUnloadingProcess); } } diff --git a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx index 398bb2a..9cf0154 100644 --- a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx @@ -2,11 +2,18 @@ import { useEffect, useRef, useState } from 'react'; import { useThree } from '@react-three/fiber'; import * as THREE from 'three'; import { MaterialModel } from '../../../materials/instances/material/materialModel'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; -const MaterialAnimator = ({ human }: { human: HumanStatus }) => { +const MaterialAnimator = ({ human }: { human: HumanStatus; }) => { + const { productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); const meshRef = useRef(null!); const [hasLoad, setHasLoad] = useState(false); const [isAttached, setIsAttached] = useState(false); + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const { scene } = useThree(); useEffect(() => { @@ -39,11 +46,11 @@ const MaterialAnimator = ({ human }: { human: HumanStatus }) => { meshRef.current.visible = true; setIsAttached(true); } - }, [hasLoad, human.modelUuid, scene]); + }, [hasLoad, human.modelUuid, scene, human.currentPhase]); return ( <> - {hasLoad && human.point.action.actionType === 'worker' && human.currentMaterials.length > 0 && ( + {hasLoad && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && ( ('init'); const [path, setPath] = useState<[number, number, number][]>([]); const pauseTimeRef = useRef(null); const idleTimeRef = useRef(0); @@ -57,37 +56,38 @@ function HumanInstance({ human }: { human: HumanStatus }) { isSpeedRef.current = speed; }, [speed]); - const computePath = useCallback( - (start: any, end: any) => { - try { - const navMeshQuery = new NavMeshQuery(navMesh); - const { path: segmentPath } = navMeshQuery.computePath(start, end); - if ( - segmentPath.length > 0 && - Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(end.x) && - Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(end.z) - ) { - return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; - } else { - console.log("There is no path here...Choose valid path") - const { path: segmentPaths } = navMeshQuery.computePath(start, start); - return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; - } - } catch { - console.error("Failed to compute path"); - return []; + const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + let startPoint = new THREE.Vector3(start[0], start[1], start[2]); + let endPoint = new THREE.Vector3(end[0], end[1], end[2]); + const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.z) + ) { + return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } else { + console.log("There is no path here...Choose valid path") + const { path: segmentPaths } = navMeshQuery.computePath(startPoint, startPoint); + return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; } - }, [navMesh]); + } catch { + console.error("Failed to compute path"); + return []; + } + }, [navMesh]); function humanStatus(modelId: string, status: string) { // console.log(`${modelId} , ${status}`); } function reset() { - setCurrentPhase('init'); + setCurrentPhase(human.modelUuid, 'init'); setHumanActive(human.modelUuid, false); - setHumanPicking(human.modelUuid, false); setHumanState(human.modelUuid, 'idle'); + setHumanScheduled(human.modelUuid, false); setHumanLoad(human.modelUuid, 0); resetAnimation(human.modelUuid); setPath([]); @@ -120,25 +120,32 @@ function HumanInstance({ human }: { human: HumanStatus }) { useEffect(() => { if (isPlaying) { - if (!human.point.action.assemblyPoint || human.point.action.actionType === 'worker') return; + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - if (!human.isActive && human.state === 'idle' && currentPhase === 'init') { + if (!action || !(action as HumanAction).assemblyPoint || (action as HumanAction).actionType === 'worker') return; + + if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { + + const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); + if (!humanMesh) return; + + const toPickupPath = computePath(humanMesh.position.toArray(), (action as HumanAction)?.assemblyPoint?.position || [0, 0, 0]); + setPath(toPickupPath); setHumanState(human.modelUuid, 'idle'); - setCurrentPhase('waiting'); - setHumanPicking(human.modelUuid, false); + setCurrentPhase(human.modelUuid, 'init-assembly'); setHumanActive(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Human is waiting for material in assembly'); - } else if (!human.isActive && human.state === 'idle' && currentPhase === 'waiting') { + } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'waiting') { + if (human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current !== 'working_standing') { setCurrentAnimation(human.modelUuid, 'working_standing', true, true, false); setHumanState(human.modelUuid, 'running'); - setCurrentPhase('assembling'); - setHumanPicking(human.modelUuid, true); + setCurrentPhase(human.modelUuid, 'assembling'); setHumanActive(human.modelUuid, true); processStartTimeRef.current = performance.now(); - processTimeRef.current = human.point.action.processTime || 0; + processTimeRef.current = (action as HumanAction).processTime || 0; accumulatedPausedTimeRef.current = 0; lastPauseTimeRef.current = null; hasLoggedHalfway.current = false; @@ -149,18 +156,18 @@ function HumanInstance({ human }: { human: HumanStatus }) { } } } else if (human.isActive && human.state === 'running' && human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current === 'working_standing' && humanAsset.animationState?.isCompleted) { - if (human.point.action.assemblyPoint && currentPhase === 'assembling') { + if ((action as HumanAction).assemblyPoint && human.currentPhase === 'assembling') { setHumanState(human.modelUuid, 'idle'); - setCurrentPhase('waiting'); - setHumanPicking(human.modelUuid, false); + setCurrentPhase(human.modelUuid, 'waiting'); setHumanActive(human.modelUuid, false); + setHumanScheduled(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Human is waiting for material in assembly'); decrementHumanLoad(human.modelUuid, 1); const material = removeLastMaterial(human.modelUuid); if (material) { - triggerPointActions(human.point.action, material.materialId); + triggerPointActions((action as HumanAction), material.materialId); } } } @@ -169,12 +176,14 @@ function HumanInstance({ human }: { human: HumanStatus }) { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + }, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); const trackAssemblyProcess = useCallback(() => { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const now = performance.now(); - if (!processStartTimeRef.current || !human.point.action.processTime) { + if (!processStartTimeRef.current || !(action as HumanAction).processTime || !action) { return; } @@ -190,12 +199,12 @@ function HumanInstance({ human }: { human: HumanStatus }) { } const elapsed = (now - processStartTimeRef.current - accumulatedPausedTimeRef.current) * isSpeedRef.current; - const totalProcessTimeMs = human.point.action.processTime * 1000; + const totalProcessTimeMs = ((action as HumanAction).processTime || 1) * 1000; if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) { hasLoggedHalfway.current = true; if (human.currentMaterials.length > 0) { - setMaterial(human.currentMaterials[0].materialId, human.point.action.swapMaterial || 'Default Material'); + setMaterial(human.currentMaterials[0].materialId, (action as HumanAction).swapMaterial || 'Default Material'); } humanStatus(human.modelUuid, `🟡 Human ${human.modelUuid} reached halfway in assembly.`); } @@ -212,73 +221,48 @@ function HumanInstance({ human }: { human: HumanStatus }) { } processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess); - }, [human.modelUuid, human.point.action.processTime, human.currentMaterials]); + }, [human.modelUuid, human.currentMaterials]); useEffect(() => { if (isPlaying) { - if (!human.point.action.pickUpPoint || !human.point.action.dropPoint || human.point.action.actionType === 'assembly') return; + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + if (!action || action.actionType !== 'worker' || !action.pickUpPoint || !action.dropPoint) return; - if (!human.isActive && human.state === 'idle' && currentPhase === 'init') { - const toPickupPath = computePath( - new THREE.Vector3(human?.position[0], human?.position[1], human?.position[2]), - new THREE.Vector3( - human?.point?.action?.pickUpPoint?.position?.[0] ?? 0, - human?.point?.action?.pickUpPoint?.position?.[1] ?? 0, - human?.point?.action?.pickUpPoint?.position?.[2] ?? 0 - ) - ); + if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { + + const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); + if (!humanMesh) return; + + const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]); setPath(toPickupPath); - setCurrentPhase('init-pickup'); + setCurrentPhase(human.modelUuid, 'init-pickup'); setHumanState(human.modelUuid, 'running'); - setHumanPicking(human.modelUuid, false); setHumanActive(human.modelUuid, true); setCurrentAnimation(human.modelUuid, 'walking', true, true, true); humanStatus(human.modelUuid, 'Started from init, heading to pickup'); return; - } else if (!human.isActive && human.state === 'idle' && currentPhase === 'picking') { - if (humanAsset && human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { - if (human.point.action.pickUpPoint && human.point.action.dropPoint) { - const toDrop = computePath( - new THREE.Vector3( - human.point.action.pickUpPoint.position?.[0] ?? 0, - human.point.action.pickUpPoint.position?.[1] ?? 0, - human.point.action.pickUpPoint.position?.[2] ?? 0 - ), - new THREE.Vector3( - human.point.action.dropPoint.position?.[0] ?? 0, - human.point.action.dropPoint.position?.[1] ?? 0, - human.point.action.dropPoint.position?.[2] ?? 0 - ) - ); + } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'picking') { + if (humanAsset && human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { + if (action.pickUpPoint && action.dropPoint) { + const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]); setPath(toDrop); - setCurrentPhase('pickup-drop'); + setCurrentPhase(human.modelUuid, 'pickup-drop'); setHumanState(human.modelUuid, 'running'); - setHumanPicking(human.modelUuid, false); - setHumanPicking(human.modelUuid, true); setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point'); } - } else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') { + } else if (human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') { + if (human.currentMaterials[0]?.materialId) { + setIsVisible(human.currentMaterials[0]?.materialId, false); + } setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); } - } else if (!human.isActive && human.state === 'idle' && currentPhase === 'dropping' && human.currentLoad === 0) { - if (human.point.action.pickUpPoint && human.point.action.dropPoint) { - const dropToPickup = computePath( - new THREE.Vector3( - human.point.action.dropPoint.position?.[0] ?? 0, - human.point.action.dropPoint.position?.[1] ?? 0, - human.point.action.dropPoint.position?.[2] ?? 0 - ), - new THREE.Vector3( - human.point.action.pickUpPoint.position?.[0] ?? 0, - human.point.action.pickUpPoint.position?.[1] ?? 0, - human.point.action.pickUpPoint.position?.[2] ?? 0 - ) - ); + } else if (!human.isActive && human.state === 'idle' && human.currentPhase === 'dropping' && human.currentLoad === 0) { + if (action.pickUpPoint && action.dropPoint) { + const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); setPath(dropToPickup); - setCurrentPhase('drop-pickup'); + setCurrentPhase(human.modelUuid, 'drop-pickup'); setHumanState(human.modelUuid, 'running'); - setHumanPicking(human.modelUuid, false); setHumanActive(human.modelUuid, true); setCurrentAnimation(human.modelUuid, 'walking', true, true, true); humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); @@ -289,30 +273,35 @@ function HumanInstance({ human }: { human: HumanStatus }) { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + }, [human, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); function handleCallBack() { - if (currentPhase === 'init-pickup') { - setCurrentPhase('picking'); + if (human.currentPhase === 'init-pickup') { + setCurrentPhase(human.modelUuid, 'picking'); setHumanState(human.modelUuid, 'idle'); - setHumanPicking(human.modelUuid, true); setHumanActive(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Reached pickup point, waiting for material'); setPath([]); - } else if (currentPhase === 'pickup-drop') { - setCurrentPhase('dropping'); + } if (human.currentPhase === 'init-assembly') { + setCurrentPhase(human.modelUuid, 'waiting'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached assembly point, waiting for material'); + setPath([]); + } else if (human.currentPhase === 'pickup-drop') { + setCurrentPhase(human.modelUuid, 'dropping'); setHumanState(human.modelUuid, 'idle'); - setHumanPicking(human.modelUuid, false); setHumanActive(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); humanStatus(human.modelUuid, 'Reached drop point'); setPath([]); - } else if (currentPhase === 'drop-pickup') { - setCurrentPhase('picking'); + } else if (human.currentPhase === 'drop-pickup') { + setCurrentPhase(human.modelUuid, 'picking'); setHumanState(human.modelUuid, 'idle'); - setHumanPicking(human.modelUuid, true); setHumanActive(human.modelUuid, false); + setHumanScheduled(human.modelUuid, false); setPath([]); clearCurrentMaterials(human.modelUuid); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); @@ -365,38 +354,35 @@ function HumanInstance({ human }: { human: HumanStatus }) { }, [human, isPlaying]); function startUnloadingProcess() { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { - if (human.point.action.triggers.length > 0) { - const trigger = getTriggerByUuid(selectedProduct.productUuid, human.point.action.triggers[0]?.triggerUuid); + if ((action as HumanAction).triggers.length > 0) { + const trigger = getTriggerByUuid(selectedProduct.productUuid, (action as HumanAction).triggers[0]?.triggerUuid); const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); if (trigger && model) { if (model.type === 'transfer') { - const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); if (action) { handleMaterialDropToConveyor(model); } } else if (model.type === 'machine') { - const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); if (action) { handleMaterialDropToMachine(model); } } else if (model.type === 'roboticArm') { - const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); if (action) { handleMaterialDropToArmBot(model); } } else if (model.type === 'storageUnit') { - const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); if (action) { handleMaterialDropToStorageUnit(model); } } else if (model.type === 'vehicle') { - const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); if (action) { handleMaterialDropToVehicle(model); } @@ -415,6 +401,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToStorageUnit(model: StorageEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (model && humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); @@ -428,7 +415,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { human.currentLoad, model.modelUuid, model.point.action.storageCapacity, - human.point.action + (action as HumanAction) ); } } else { @@ -482,6 +469,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToConveyor(model: ConveyorEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); @@ -495,7 +483,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { human.modelUuid, human.currentLoad, conveyor.modelUuid, - human.point.action + (action as HumanAction) ); } } else { @@ -547,6 +535,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToArmBot(model: RoboticArmEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); @@ -560,7 +549,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { human.modelUuid, human.currentLoad, model.modelUuid, - human.point.action + (action as HumanAction) ); } } else { @@ -617,6 +606,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToVehicle(model: VehicleEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); @@ -630,7 +620,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { human.modelUuid, human.currentLoad, model.modelUuid, - human.point.action + (action as HumanAction) ); } } else { @@ -687,6 +677,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { } function handleMaterialDropToMachine(model: MachineEventSchema) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); @@ -700,7 +691,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { human.modelUuid, human.currentLoad, model.modelUuid, - human.point.action + (action as HumanAction) ); } } else { @@ -790,7 +781,6 @@ function HumanInstance({ human }: { human: HumanStatus }) { ([0, 1, 0]); const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]); + const [assemblyPosition, setAssemblyPosition] = useState<[number, number, number]>([0, 1, 0]); const [startRotation, setStartRotation] = useState<[number, number, number]>([0, Math.PI, 0]); const [assemblyRotation, setAssemblyRotation] = useState<[number, number, number]>([0, 0, 0]); const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]); @@ -48,9 +49,8 @@ function HumanUi() { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); - const selectedHuman = selectedEventSphere ? getHumanById(selectedEventSphere.userData.modelUuid) : null; - const actionType = selectedHuman?.point?.action?.actionType || null; - const isAssembly = actionType === 'assembly'; + const currentAction = getActionByUuid(selectedProduct.productUuid, selectedAction.actionId || ''); + const isAssembly = currentAction?.actionType === 'assembly'; const updateBackend = ( productName: string, @@ -68,10 +68,10 @@ function HumanUi() { }; useEffect(() => { - if (!selectedEventSphere) return; + if (!selectedEventSphere || !selectedAction?.actionId) return; const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); - if (!selectedHuman || !selectedHuman.point?.action) return; + if (!selectedHuman || !selectedHuman.point?.actions) return; setSelectedHumanData({ position: selectedHuman.position, @@ -86,42 +86,47 @@ function HumanUi() { ); } - const action = selectedHuman.point.action; + const action = selectedHuman.point.actions.find(a => a.actionUuid === selectedAction.actionId); + if (!action) return; - if (action.pickUpPoint?.position && outerGroup.current) { - const worldPos = new Vector3(...action.pickUpPoint.position); - const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); - setStartPosition([localPosition.x, 1, localPosition.z]); - setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]); + if (isAssembly) { + if (action.assemblyPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...action.assemblyPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setAssemblyPosition([localPosition.x, 1, localPosition.z]); + setAssemblyRotation(action.assemblyPoint.rotation || [0, 0, 0]); + } else { + setAssemblyPosition([0, 1, 0]); + setAssemblyRotation([0, 0, 0]); + } } else { - setStartPosition([0, 1, 0]); - setStartRotation([0, Math.PI, 0]); - } + if (action.pickUpPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...action.pickUpPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setStartPosition([localPosition.x, 1, localPosition.z]); + setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]); + } else { + setStartPosition([0, 1, 0]); + setStartRotation([0, Math.PI, 0]); + } - if (action.dropPoint?.position && outerGroup.current) { - const worldPos = new Vector3(...action.dropPoint.position); - const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); - setEndPosition([localPosition.x, 1, localPosition.z]); - setEndRotation(action.dropPoint.rotation || [0, Math.PI, 0]); - } else { - setEndPosition([0, 1, 0]); - setEndRotation([0, 0, 0]); + if (action.dropPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...action.dropPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setEndPosition([localPosition.x, 1, localPosition.z]); + setEndRotation(action.dropPoint.rotation || [0, Math.PI, 0]); + } else { + setEndPosition([0, 1, 0]); + setEndRotation([0, 0, 0]); + } } - - if (action.assemblyPoint?.rotation) { - setAssemblyRotation(action.assemblyPoint.rotation); - } else { - setAssemblyRotation([0, 0, 0]); - } - }, [selectedEventSphere, outerGroup.current, selectedAction, humans]); const handlePointerDown = ( e: any, - state: "start" | "end", - rotation: "start" | "end" + state: "start" | "end" | "assembly", + rotation: "start" | "end" | "assembly" ) => { - if (isAssembly) return; e.stopPropagation(); const intersection = new Vector3(); const pointer = new Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1); @@ -144,7 +149,10 @@ function HumanUi() { if (outerGroup.current) { localPoint = outerGroup.current.worldToLocal(intersection.clone()); } - const marker = state === "start" ? startMarker.current : endMarker.current; + const marker = + state === "start" ? startMarker.current : + state === "end" ? endMarker.current : + assemblyMarker.current; if (marker && localPoint) { const markerPos = new Vector3().copy(marker.position); dragOffset.current.copy(markerPos.sub(localPoint)); @@ -154,7 +162,6 @@ function HumanUi() { if (controls) (controls as any).enabled = false; }; - const handlePointerUp = () => { (controls as any).enabled = true; setIsDragging(null); @@ -163,40 +170,46 @@ function HumanUi() { if (!selectedEventSphere?.userData.modelUuid || !selectedAction?.actionId) return; const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); - if (!selectedHuman || !outerGroup.current) return; + if (!selectedHuman || !outerGroup.current || !currentAction) return; - const isAssembly = selectedHuman.point?.action?.actionType === 'assembly'; + const updatedActions = selectedHuman.point.actions.map(action => { + if (action.actionUuid !== currentAction.actionUuid) return action; - let updatedAction; + if (isAssembly) { + if (!assemblyMarker.current || !outerGroup.current) return action; - if (isAssembly) { - updatedAction = { - ...selectedHuman.point.action, - assemblyPoint: { - rotation: assemblyRotation - }, - }; - } else { - if (!startMarker.current || !endMarker.current) return; + const worldPosAssembly = new Vector3(...assemblyPosition); + const globalAssemblyPosition = outerGroup.current.localToWorld(worldPosAssembly.clone()); - const worldPosStart = new Vector3(...startPosition); - const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone()); + return { + ...action, + assemblyPoint: { + position: [globalAssemblyPosition.x, globalAssemblyPosition.y, globalAssemblyPosition.z] as [number, number, number], + rotation: assemblyRotation + }, + }; + } else { + if (!startMarker.current || !endMarker.current || !outerGroup.current) return action; - const worldPosEnd = new Vector3(...endPosition); - const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone()); + const worldPosStart = new Vector3(...startPosition); + const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone()); - updatedAction = { - ...selectedHuman.point.action, - pickUpPoint: { - position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number], - rotation: startRotation, - }, - dropPoint: { - position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number], - rotation: endRotation, - }, - }; - } + const worldPosEnd = new Vector3(...endPosition); + const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone()); + + return { + ...action, + pickUpPoint: { + position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number], + rotation: startRotation, + }, + dropPoint: { + position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number], + rotation: endRotation, + }, + }; + } + }); const event = updateEvent( selectedProduct.productUuid, @@ -205,7 +218,7 @@ function HumanUi() { ...selectedHuman, point: { ...selectedHuman.point, - action: updatedAction, + actions: updatedActions, }, } ); @@ -221,7 +234,7 @@ function HumanUi() { }; useFrame(() => { - if (isAssembly || !isDragging || !plane.current || !raycaster || !outerGroup.current) return; + if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return; const intersectPoint = new Vector3(); const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint); if (!intersects) return; @@ -232,6 +245,8 @@ function HumanUi() { setStartPosition([localPoint.x, 1, localPoint.z]); } else if (isDragging === "end") { setEndPosition([localPoint.x, 1, localPoint.z]); + } else if (isDragging === "assembly") { + setAssemblyPosition([localPoint.x, 1, localPoint.z]); } }); @@ -240,28 +255,34 @@ function HumanUi() { const currentPointerX = state.pointer.x; const deltaX = currentPointerX - prevMousePos.current.x; prevMousePos.current.x = currentPointerX; - const marker = isRotating === "start" ? isAssembly ? assemblyMarker.current : startMarker.current : isAssembly ? assemblyMarker.current : endMarker.current; + + const marker = + isRotating === "start" ? startMarker.current : + isRotating === "end" ? endMarker.current : + assemblyMarker.current; + if (marker) { const rotationSpeed = 10; marker.rotation.y += deltaX * rotationSpeed; - if (isAssembly && isRotating === "start") { - setAssemblyRotation([ - marker.rotation.x, - marker.rotation.y, - marker.rotation.z, - ]); - } else if (isRotating === "start") { + + if (isRotating === "start") { setStartRotation([ marker.rotation.x, marker.rotation.y, marker.rotation.z, ]); - } else { + } else if (isRotating === "end") { setEndRotation([ marker.rotation.x, marker.rotation.y, marker.rotation.z, ]); + } else { + setAssemblyRotation([ + marker.rotation.x, + marker.rotation.y, + marker.rotation.z, + ]); } } }); @@ -281,7 +302,7 @@ function HumanUi() { return () => { window.removeEventListener("pointerup", handleGlobalPointerUp); }; - }, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, assemblyRotation]); + }, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, assemblyPosition, assemblyRotation]); return ( <> @@ -295,16 +316,13 @@ function HumanUi() { { if (e.object.parent.name === "handle") { - e.stopPropagation(); - const normalizedX = (e.clientX / window.innerWidth) * 2 - 1; - prevMousePos.current.x = normalizedX; - setIsRotating("start"); - setIsDragging(null); - if (controls) (controls as any).enabled = false; + handlePointerDown(e, "assembly", "assembly"); + } else { + handlePointerDown(e, "assembly", "assembly"); } }} onPointerMissed={() => { @@ -322,8 +340,11 @@ function HumanUi() { position={startPosition} rotation={startRotation} onPointerDown={(e: any) => { - e.stopPropagation(); - handlePointerDown(e, "start", "start"); + if (e.object.parent.name === "handle") { + handlePointerDown(e, "start", "start"); + } else { + handlePointerDown(e, "start", "start"); + } }} onPointerMissed={() => { setIsDragging(null); @@ -339,8 +360,11 @@ function HumanUi() { position={endPosition} rotation={endRotation} onPointerDown={(e: any) => { - e.stopPropagation(); - handlePointerDown(e, "end", "end"); + if (e.object.parent.name === "handle") { + handlePointerDown(e, "end", "end"); + } else { + handlePointerDown(e, "end", "end"); + } }} onPointerMissed={() => { setIsDragging(null); @@ -356,4 +380,4 @@ function HumanUi() { ); } -export default HumanUi \ No newline at end of file +export default HumanUi; \ No newline at end of file diff --git a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx index 9dcfaab..72cd5fe 100644 --- a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx +++ b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx @@ -12,7 +12,8 @@ function MaterialInstance({ material }: { readonly material: MaterialSchema }) { const matRef: any = useRef(); const { scene } = useThree(); const { selectedProductStore } = useProductContext(); - const { productStore } = useSceneContext(); + const { productStore, materialStore } = useSceneContext(); + const { setIsPaused } = materialStore(); const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid, getActionByPointUuid } = productStore(); const { selectedProduct } = selectedProductStore(); const { speed } = useAnimationPlaySpeed(); @@ -80,16 +81,23 @@ function MaterialInstance({ material }: { readonly material: MaterialSchema }) { return 1; } + if (event.type === 'human') { + return event.speed; + } + } else { return 1; } } const callTrigger = () => { - if (!material.next) return; - const action = getActionByPointUuid(selectedProduct.productUuid, material.next.pointUuid); - if (action) { - triggerPointActions(action, material.materialId); + if (material.next) { + const action = getActionByPointUuid(selectedProduct.productUuid, material.next.pointUuid); + if (action) { + triggerPointActions(action, material.materialId); + } + } else { + setIsPaused(material.materialId, true); } } @@ -97,7 +105,7 @@ function MaterialInstance({ material }: { readonly material: MaterialSchema }) { <> {material.isRendered && - + } { if (events.type === 'human') { addHuman(selectedProduct.productUuid, events); + + if (events.point.actions.length > 0) { + addCurrentAction(events.modelUuid, events.point.actions[0].actionUuid); + } } }); } diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index 897c6d1..ab6041c 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -45,7 +45,8 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) { rotationMax: link.rotationMax ? new THREE.Vector3(...link.rotationMax) : undefined, limitation: link.limitation ? new THREE.Vector3(...link.limitation) : undefined, })), - ringRadius: ik.ringRadius, + minDistance: ik.minDistance, + maxDistance: ik.maxDistance, maxheight: ik.maxheight, minheight: ik.minheight, })); diff --git a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx index 5226369..1f4dee8 100644 --- a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx +++ b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx @@ -171,11 +171,22 @@ const ArmBotUI = () => { } } - const { handlePointerDown } = useDraggableGLTF(updatePointToState); + const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || ''); + + const { handlePointerDown } = useDraggableGLTF( + updatePointToState, + { + minDistance: targetMesh?.userData?.iks[0]?.minDistance || 1.2, + maxDistance: targetMesh?.userData?.iks[0]?.maxDistance || 2, + maxheight: targetMesh?.userData?.iks[0]?.maxheight || 0.6, + minheight: targetMesh?.userData?.iks[0]?.minheight || 1.9, + } + ); if (!selectedArmBotData || !Array.isArray(selectedArmBotData.point?.actions)) { - return null; // avoid rendering if no data yet + return null; } + return ( <> {selectedArmBotData.point.actions.map((action: any) => { diff --git a/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts b/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts index 7c7d0b4..2f63234 100644 --- a/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts +++ b/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts @@ -1,15 +1,26 @@ import { useRef, useState } from "react"; import * as THREE from "three"; import { ThreeEvent, useThree } from "@react-three/fiber"; -import { - useSelectedEventData, -} from "../../../../store/simulation/useSimulationStore"; +import { useSelectedEventData } from "../../../../store/simulation/useSimulationStore"; import { useProductContext } from "../../products/productContext"; import { useSceneContext } from "../../../scene/sceneContext"; type OnUpdateCallback = (object: THREE.Object3D) => void; -export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { +export default function useDraggableGLTF( + onUpdate: OnUpdateCallback, + constraints: { + minDistance?: number; + maxDistance?: number; + maxheight?: number; + minheight?: number; + } = { + minDistance: 1.2, + maxDistance: 2, + minheight: 0.6, + maxheight: 1.9 + } +) { const { productStore } = useSceneContext(); const { getEventByModelUuid } = productStore(); const { selectedEventData } = useSelectedEventData(); @@ -120,8 +131,10 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { // CONSTRAIN MOVEMENT HERE: const centerX = selectedArmBot.position[0]; const centerZ = selectedArmBot.position[2]; - const minDistance = 1.2; - const maxDistance = 2; + const minDistance = constraints.minDistance ?? 1.2; + const maxDistance = constraints.maxDistance ?? 2; + const minHeight = constraints.minheight ?? 0.6; + const maxHeight = constraints.maxheight ?? 1.9; const delta = new THREE.Vector3(targetPosition.x - centerX, 0, targetPosition.z - centerZ); @@ -169,9 +182,8 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { targetPosition.x = centerX + finalLocal.x; targetPosition.z = centerZ + finalLocal.z; - - // Clamp Y axis if needed - targetPosition.y = Math.min(Math.max(targetPosition.y, 0.6), 1.9); + // Clamp Y axis using variables + targetPosition.y = Math.min(Math.max(targetPosition.y, minHeight), maxHeight); // Convert to local if parent exists if (parent) { diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 815f0ca..6a0ef6b 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -22,7 +22,7 @@ export function useTriggerHandler() { const { addMachineToMonitor } = useMachineEventManager(); const { addHumanToMonitor } = useHumanEventManager(); const { getVehicleById } = vehicleStore(); - const { getHumanById } = humanStore(); + const { getHumanById, setHumanScheduled } = humanStore(); const { getMachineById } = machineStore(); const { getStorageUnitById } = storageUnitStore(); const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore(); @@ -330,37 +330,29 @@ export function useTriggerHandler() { if (vehicle) { if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { // Handle current action from vehicle - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId); } else { // Handle current action using Event Manager setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); - addVehicleToMonitor(vehicle.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId); - } - ) + addVehicleToMonitor(vehicle.modelUuid, () => { + handleAction(action, materialId); + }) } } } else { setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); addHumanToMonitor(human.modelUuid, () => { const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); if (vehicle) { if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { // Handle current action from vehicle - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } setIsPaused(materialId, true); handleAction(action, materialId); @@ -369,82 +361,32 @@ export function useTriggerHandler() { // Handle current action using Event Manager setIsPaused(materialId, true); - addVehicleToMonitor(vehicle.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId); - } - ) + addVehicleToMonitor(vehicle.modelUuid, () => { + handleAction(action, materialId); + }) } } - }) + }, action.actionUuid) } } } else if (model?.type === 'transfer') { const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); if (human) { - if (human.isActive === false && human.state === 'idle') { - - if (human && human.modelUuid === "cc62adae-7000-447b-b845-6d4910de503a") { - console.log(human); - } - const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); + addHumanToMonitor(human.modelUuid, () => { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); if (conveyor) { - if (!conveyor.isPaused) { - // Handle current action from vehicle - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - setIsPaused(materialId, true); - handleAction(action, materialId); + // Handle current action using Event Manager + setIsPaused(materialId, true); - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addConveyorToMonitor(conveyor.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId); - } - ) - } - } - } else { - setIsPaused(materialId, true); - addHumanToMonitor(human.modelUuid, () => { - const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (conveyor) { - if (!conveyor.isPaused) { - // Handle current action from vehicle - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - setIsPaused(materialId, true); + addConveyorToMonitor(conveyor.modelUuid, () => { + addHumanToMonitor(human.modelUuid, () => { handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addConveyorToMonitor(conveyor.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId); - } - ) - } - } - }) - } + }, action.actionUuid) + }, [materialId]) + } + }, action.actionUuid) } } else if (model?.type === 'machine') { const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); @@ -454,53 +396,39 @@ export function useTriggerHandler() { if (machine) { if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { setIsPaused(materialId, true); - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId); } else { // Handle current action using Event Manager setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); - addMachineToMonitor(machine.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId); - } - ) + addMachineToMonitor(machine.modelUuid, () => { + handleAction(action, materialId); + }) } } } else { setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); addHumanToMonitor(human.modelUuid, () => { const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); if (machine) { if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { setIsPaused(materialId, true); - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } handleAction(action, materialId); } else { // Handle current action using Event Manager setIsPaused(materialId, true); - addMachineToMonitor(machine.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId); - } - ) + addMachineToMonitor(machine.modelUuid, () => { + handleAction(action, materialId); + }) } } - } - ); + }, action.actionUuid); } } } else { @@ -508,23 +436,16 @@ export function useTriggerHandler() { // Handle current action from arm bot setIsPaused(materialId, true); - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } handleAction(action, materialId); } else { // Handle current action using Event Manager + setHumanScheduled(human.modelUuid, true); setIsPaused(materialId, true); - addHumanToMonitor(human.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId) - } - ); + addHumanToMonitor(human.modelUuid, () => { + handleAction(action, materialId) + }, action.actionUuid); } } @@ -533,23 +454,17 @@ export function useTriggerHandler() { // Handle current action from arm bot setIsPaused(materialId, true); - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId); } else { // Handle current action using Event Manager setIsPaused(materialId, true); - addHumanToMonitor(human.modelUuid, - () => { - if (action.actionType === 'worker') { - setIsVisible(materialId, false); - } - handleAction(action, materialId) - } - ); + setHumanScheduled(human.modelUuid, true); + addHumanToMonitor(human.modelUuid, () => { + handleAction(action, materialId) + }, action.actionUuid); } } @@ -711,26 +626,13 @@ export function useTriggerHandler() { setNextLocation(material.materialId, null); - if (action && human) { - - if (human.isActive === false && human.state === 'idle') { - - // Handle current action from arm bot + setHumanScheduled(human.modelUuid, true); + addHumanToMonitor(human.modelUuid, () => { setIsVisible(materialId, false); handleAction(action, materialId); - - } else { - - addHumanToMonitor(human.modelUuid, - () => { - setIsVisible(materialId, false); - - handleAction(action, materialId); - } - ) - } + }, action.actionUuid) } } } @@ -879,8 +781,10 @@ export function useTriggerHandler() { const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); if (previousModel) { if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } else { + setHumanScheduled(human.modelUuid, true); addConveyorToMonitor(conveyor.modelUuid, () => { handleAction(action, materialId) @@ -888,6 +792,7 @@ export function useTriggerHandler() { ) } } else { + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } // handleAction(action, materialId) @@ -898,12 +803,14 @@ export function useTriggerHandler() { if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { // Handle current action from vehicle setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId); } else { // Handle current action using Event Manager setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); addVehicleToMonitor(vehicle.modelUuid, () => { @@ -913,6 +820,7 @@ export function useTriggerHandler() { } } } else { + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } @@ -928,8 +836,11 @@ export function useTriggerHandler() { const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); if (previousModel) { if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } else { + setHumanScheduled(human.modelUuid, true); addConveyorToMonitor(conveyor.modelUuid, () => { handleAction(action, materialId) @@ -937,6 +848,7 @@ export function useTriggerHandler() { ) } } else { + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } } @@ -946,12 +858,14 @@ export function useTriggerHandler() { if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { // Handle current action from vehicle setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId); } else { // Handle current action using Event Manager setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); addVehicleToMonitor(vehicle.modelUuid, () => { @@ -961,10 +875,10 @@ export function useTriggerHandler() { } } } else { + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId) } - } - ); + }, action.actionUuid); } } } @@ -1255,11 +1169,11 @@ export function useTriggerHandler() { setNextLocation(material.materialId, null); - if (action) { + if (action && action.actionType === 'worker') { if (human) { - if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { + if (human.isActive === false && human.state === 'idle' && !human.isScheduled && human.currentLoad < action.loadCapacity) { setIsVisible(materialId, false); @@ -1276,6 +1190,7 @@ export function useTriggerHandler() { }); // Handle current action from human + setHumanScheduled(human.modelUuid, true); handleAction(action, materialId); } else { @@ -1460,14 +1375,60 @@ export function useTriggerHandler() { actionUuid: material.current.actionUuid, }); - setNextLocation(material.materialId, { - modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid, - pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid, - }) - - setIsPaused(material.materialId, false); + setIsPaused(material.materialId, true); setIsVisible(material.materialId, true); + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + + if (action && action.triggers.length > 0 && + action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid && + action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid && + action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + + if (model?.type === 'roboticArm') { + addArmBotToMonitor(model.modelUuid, () => { + setNextLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', + }) + + setIsPaused(material.materialId, false); + }) + } else if (model?.type === 'vehicle') { + addVehicleToMonitor(model.modelUuid, () => { + setNextLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', + }) + + setIsPaused(material.materialId, false); + }) + } else if (model?.type === 'transfer') { + setNextLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', + }) + + setIsPaused(material.materialId, false); + } else if (model?.type === 'human') { + addHumanToMonitor(model.modelUuid, () => { + setNextLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', + }) + + setIsPaused(material.materialId, false); + }, action.actionUuid) + } else { + setNextLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', + }) + + setIsPaused(material.materialId, false); + } + } } } @@ -1510,8 +1471,31 @@ export function useTriggerHandler() { } else { - // Event Manager Needed + setIsPaused(materialId, true); + addVehicleToMonitor(vehicle.modelUuid, + () => { + setIsPaused(materialId, false); + setIsVisible(materialId, false); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger?.triggeredAsset?.triggeredPoint?.pointUuid || '', + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid || '', + }); + + setNextLocation(material.materialId, null); + + // Handle current action from vehicle + handleAction(action, materialId); + } + ) } } } diff --git a/app/src/modules/simulation/vehicle/instances/animator/interactivePoint.tsx b/app/src/modules/simulation/vehicle/instances/animator/interactivePoint.tsx new file mode 100644 index 0000000..5705cc5 --- /dev/null +++ b/app/src/modules/simulation/vehicle/instances/animator/interactivePoint.tsx @@ -0,0 +1,328 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { Canvas, useThree, useFrame, ThreeEvent } from '@react-three/fiber'; +import { Line, OrbitControls } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useActiveTool, useSelectedPath } from '../../../../../store/builder/store'; + +interface InteractivePointsProps { + agvUuid: string; +} + + +export default function InteractivePoints({ agvUuid }: InteractivePointsProps) { + const { gl, scene, raycaster } = useThree(); + const [points, setPoints] = useState<[number, number, number][]>([]); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); // XZ plane + const progressRef = useRef(0); + const { selectedPath } = useSelectedPath(); + const lastTimeRef = useRef(performance.now()); + const [isAnyDragging, setIsAnyDragging] = useState(""); + const { activeTool } = useActiveTool(); + const hasClicked = useRef(false); + + useFrame(() => { + if (!isPlaying) return + const now = performance.now(); + const delta = (now - lastTimeRef.current) / 1000; + lastTimeRef.current = now; + + const object = scene.getObjectByProperty('uuid', agvUuid); + if (!object || points.length < 2) return; + if (isPaused) return; + + let totalDistance = 0; + const distances = []; + let accumulatedDistance = 0; + let index = 0; + const rotationSpeed = 1; + + for (let i = 0; i < points.length - 1; i++) { + const start = new THREE.Vector3(...points[i]); + const end = new THREE.Vector3(...points[i + 1]); + const segmentDistance = start.distanceTo(end); + distances.push(segmentDistance); + totalDistance += segmentDistance; + } + + while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) { + accumulatedDistance += distances[index]; + index++; + } + + if (index < distances.length) { + const start = new THREE.Vector3(...points[index]); + const end = new THREE.Vector3(...points[index + 1]); + const segmentDistance = distances[index]; + + const currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); + const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); + const currentAngle = object.rotation.y; + + let angleDifference = targetAngle - currentAngle; + if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; + if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; + + const maxRotationStep = (rotationSpeed * speed * 2) * delta; + object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); + const isAligned = Math.abs(angleDifference) < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * 2); + const t = (progressRef.current - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + } + } + }); + + const downPosition = useRef<{ x: number; y: number } | null>(null); + + const handleMouseDown = useCallback((e: MouseEvent) => { + hasClicked.current = false; + downPosition.current = { x: e.clientX, y: e.clientY }; + }, []); + + const handleClick = useCallback((e: MouseEvent) => { + if (hasClicked.current) return; + hasClicked.current = true; + + if ( + !downPosition.current || + Math.abs(downPosition.current.x - e.clientX) > 2 || + Math.abs(downPosition.current.y - e.clientY) > 2 + ) { + return; + } + + const intersection = new THREE.Vector3(); + if (raycaster.ray.intersectPlane(plane.current, intersection) && activeTool !== "pen") { + const pointArray = intersection.toArray() as [number, number, number]; + + // ✅ Check if this point already exists + const alreadyExists = points.some((p) => + Math.abs(p[0] - pointArray[0]) < 0.01 && + Math.abs(p[1] - pointArray[1]) < 0.01 && + Math.abs(p[2] - pointArray[2]) < 0.01 + ); + + if (!alreadyExists) { + console.log("pointArray: ", pointArray); + setPoints((prev) => [...prev, pointArray]); + console.log("points created"); + } + } + }, [activeTool, raycaster, points]); + + + useEffect(() => { + if (isPlaying) return; + const domElement = gl.domElement; + + domElement.addEventListener('mousedown', handleMouseDown); + domElement.addEventListener('mouseup', handleClick); + + + return () => { + domElement.removeEventListener('mousedown', handleMouseDown); + domElement.removeEventListener('mouseup', handleClick); + ; + }; + }, [isPlaying, handleClick, handleMouseDown]); + + + const updatePoint = (index: number, pos: THREE.Vector3) => { + const updated = [...points]; + updated[index] = pos.toArray() as [number, number, number]; + setPoints(updated); + }; + + + return ( + <> + {selectedPath === "manual" && + + {points.length > 0 && ( + + {points.map((pos, i) => + ( + + + ) + + + )} + + ) + } + {points && ( + points.map((pos, i) => { + if (i < points.length - 1) { + return ( + { + const updated = [...points]; + updated[i0] = p0.toArray() as [number, number, number]; + updated[i1] = p1.toArray() as [number, number, number]; + setPoints(updated); + }} + isAnyDragging={isAnyDragging} + setIsAnyDragging={setIsAnyDragging} + /> + ); + } + return null; + }) + )} + + } + + ); +} + + + +function DraggableSphere({ + index, + position, + onMove, + isAnyDragging, + setIsAnyDragging, +}: { + index: number; + position: THREE.Vector3; + onMove: (index: number, pos: THREE.Vector3) => void; + isAnyDragging: string; + setIsAnyDragging: (val: string) => void; +}) { + const meshRef = useRef(null); + const { gl, controls, raycaster } = useThree(); + const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + const { activeTool } = useActiveTool(); + + const onPointerDown = (e: ThreeEvent) => { + e.stopPropagation() + if (activeTool !== 'pen') return; + setIsAnyDragging("point"); + gl.domElement.style.cursor = 'grabbing'; + if (controls) (controls as any).enabled = false; + }; + + const onPointerMove = (e: ThreeEvent) => { + if (isAnyDragging !== "point" || activeTool !== 'pen') return; + + const intersect = new THREE.Vector3(); + if (raycaster.ray.intersectPlane(plane, intersect)) { + meshRef.current!.position.copy(intersect); + onMove(index, intersect); + } + }; + + const onPointerUp = () => { + if (activeTool !== 'pen') return; + setIsAnyDragging(""); + gl.domElement.style.cursor = 'default'; + if (controls) (controls as any).enabled = true; + }; + useEffect(() => { + gl.domElement.addEventListener("pointerup", onPointerUp); + return (() => { + gl.domElement.removeEventListener("pointerup", onPointerUp); + }) + }, [activeTool]) + + return ( + + + + + ); +} + +function DraggableLineSegment({ + index, + start, + end, + updatePoints, + isAnyDragging, + setIsAnyDragging, +}: { + index: number; + start: THREE.Vector3; + end: THREE.Vector3; + updatePoints: (i0: number, p0: THREE.Vector3, i1: number, p1: THREE.Vector3) => void; + isAnyDragging: string; + setIsAnyDragging: (val: string) => void; +}) { + const { gl, raycaster, controls } = useThree(); + const { activeTool } = useActiveTool(); + const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + const dragStart = useRef(null); + + const onPointerDown = () => { + if (activeTool !== 'pen' || isAnyDragging) return; + setIsAnyDragging("line"); + gl.domElement.style.cursor = 'grabbing'; + if (controls) (controls as any).enabled = false; + }; + + const onPointerMove = (e: ThreeEvent) => { + if (isAnyDragging !== "line" || activeTool !== 'pen') return; + + const intersect = new THREE.Vector3(); + if (raycaster.ray.intersectPlane(plane, intersect)) { + if (!dragStart.current) dragStart.current = intersect.clone(); + const offset = new THREE.Vector3().subVectors(intersect, dragStart.current); + const newStart = start.clone().add(offset); + const newEnd = end.clone().add(offset); + updatePoints(index, newStart, index + 1, newEnd); + } + }; + + const onPointerUp = () => { + if (activeTool !== 'pen') return; + setIsAnyDragging(""); + dragStart.current = null; + gl.domElement.style.cursor = 'default'; + if (controls) (controls as any).enabled = true; + }; + useEffect(() => { + gl.domElement.addEventListener("pointerup", onPointerUp); + return (() => { + gl.domElement.removeEventListener("pointerup", onPointerUp); + }) + }, [activeTool]) + + return ( + + ); +} \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index d76c0b3..72dbc49 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -1,10 +1,12 @@ -import { useEffect, useRef, useState } from 'react' -import { useFrame, useThree } from '@react-three/fiber'; + +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { useFrame, useThree, ThreeEvent } from '@react-three/fiber'; import * as THREE from 'three'; import { Line } from '@react-three/drei'; import { RapierRigidBody } from '@react-three/rapier'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; +import { useActiveTool, useSelectedPath } from '../../../../../store/builder/store'; interface VehicleAnimatorProps { path: [number, number, number][]; @@ -30,7 +32,9 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai const [restRotation, setRestingRotation] = useState(true); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); const rigidBodyRef = useRef(null); - const { scene } = useThree(); + const { scene, controls } = useThree(); + const { selectedPath } = useSelectedPath(); + const [isAnyDragging, setIsAnyDragging] = useState(""); const object = scene.getObjectByProperty('uuid', agvUuid); @@ -39,7 +43,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai } useEffect(() => { - if (currentPhase === 'stationed-pickup' && path.length > 0) { + if (currentPhase === 'stationed-pickup' && path.length > 0 && selectedPath === "auto") { setCurrentPath(path); setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation) } else if (currentPhase === 'pickup-drop' && path.length > 0) { @@ -49,7 +53,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation) setCurrentPath(path); } - }, [currentPhase, path, objectRotation]); + }, [currentPhase, path, objectRotation, selectedPath]); useEffect(() => { completedRef.current = false; @@ -82,6 +86,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai const lastTimeRef = useRef(performance.now()); useFrame(() => { + if (!isPlaying) return const now = performance.now(); const delta = (now - lastTimeRef.current) / 1000; lastTimeRef.current = now; @@ -217,26 +222,197 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai handleCallBack(); if (currentPhase === 'pickup-drop') { requestAnimationFrame(startUnloadingProcess); + } } }); + const updatePoint = (index: number, pos: THREE.Vector3) => { + const updated = [...currentPath]; + updated[index] = pos.toArray() as [number, number, number]; + setCurrentPath(updated); + }; + return ( <> - {currentPath.length > 0 && ( - // helper - - - {currentPath.map((point, index) => ( - - - - - ))} - - )} + {selectedPath === "auto" && + + {currentPath.map((pos, i) => { + if (i < currentPath.length - 1) { + return ( + { + const updated = [...currentPath]; + updated[i0] = p0.toArray() as [number, number, number]; + updated[i1] = p1.toArray() as [number, number, number]; + setCurrentPath(updated); + }} + isAnyDragging={isAnyDragging} + setIsAnyDragging={setIsAnyDragging} + /> + ); + } + return null; + })} + {currentPath.length > 0 && ( + { if (controls) (controls as any).enabled = true; }}> + {currentPath.map((pos, i) => + ( + ) + )} + + )} + + } ); } -export default VehicleAnimator; \ No newline at end of file + + +export default VehicleAnimator; + + + + +function DraggableSphere({ + index, + position, + onMove, + isAnyDragging, + setIsAnyDragging, +}: { + index: number; + position: THREE.Vector3; + onMove: (index: number, pos: THREE.Vector3) => void; + isAnyDragging: string; + setIsAnyDragging: (val: string) => void; +}) { + const meshRef = useRef(null); + const { gl, controls, raycaster } = useThree(); + const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + const { activeTool } = useActiveTool(); + + const onPointerDown = (e: ThreeEvent) => { + e.stopPropagation() + if (activeTool !== 'pen') return; + setIsAnyDragging("point"); + gl.domElement.style.cursor = 'grabbing'; + if (controls) (controls as any).enabled = false; + }; + + const onPointerMove = (e: ThreeEvent) => { + if (isAnyDragging !== "point" || activeTool !== 'pen') return; + + const intersect = new THREE.Vector3(); + if (raycaster.ray.intersectPlane(plane, intersect)) { + meshRef.current!.position.copy(intersect); + onMove(index, intersect); + } + }; + + const onPointerUp = () => { + if (activeTool !== 'pen') return; + setIsAnyDragging(""); + gl.domElement.style.cursor = 'default'; + if (controls) (controls as any).enabled = true; + }; + useEffect(() => { + gl.domElement.addEventListener("pointerup", onPointerUp); + return (() => { + gl.domElement.removeEventListener("pointerup", onPointerUp); + }) + }, [activeTool]) + + return ( + + + + + ); +} + +function DraggableLineSegment({ + index, + start, + end, + updatePoints, + isAnyDragging, + setIsAnyDragging, +}: { + index: number; + start: THREE.Vector3; + end: THREE.Vector3; + updatePoints: (i0: number, p0: THREE.Vector3, i1: number, p1: THREE.Vector3) => void; + isAnyDragging: string; + setIsAnyDragging: (val: string) => void; +}) { + const { gl, raycaster, controls } = useThree(); + const { activeTool } = useActiveTool(); + const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + const dragStart = useRef(null); + + const onPointerDown = () => { + if (activeTool !== 'pen' || isAnyDragging) return; + setIsAnyDragging("line"); + gl.domElement.style.cursor = 'grabbing'; + if (controls) (controls as any).enabled = false; + }; + + const onPointerMove = (e: ThreeEvent) => { + console.log('isAnyDragging: ', isAnyDragging); + if (isAnyDragging !== "line" || activeTool !== 'pen') return; + + const intersect = new THREE.Vector3(); + if (raycaster.ray.intersectPlane(plane, intersect)) { + if (!dragStart.current) dragStart.current = intersect.clone(); + const offset = new THREE.Vector3().subVectors(intersect, dragStart.current); + const newStart = start.clone().add(offset); + const newEnd = end.clone().add(offset); + updatePoints(index, newStart, index + 1, newEnd); + } + }; + + const onPointerUp = () => { + if (activeTool !== 'pen') return; + setIsAnyDragging(""); + dragStart.current = null; + gl.domElement.style.cursor = 'default'; + if (controls) (controls as any).enabled = true; + }; + useEffect(() => { + gl.domElement.addEventListener("pointerup", onPointerUp); + return (() => { + gl.domElement.removeEventListener("pointerup", onPointerUp); + }) + }, [activeTool]) + return ( + + ); +} diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 5c49901..3d6d274 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,30 +1,34 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; -import { useNavMesh } from '../../../../../store/builder/store'; +import { useNavMesh, useSelectedPath } from '../../../../../store/builder/store'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; +import InteractivePoints from '../animator/interactivePoint'; import MaterialAnimator from '../animator/materialAnimator'; import VehicleAnimator from '../animator/vehicleAnimator'; +import { useHumanEventManager } from '../../../human/eventManager/useHumanEventManager'; + function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore } = useSceneContext(); - const { removeMaterial, setEndTime } = materialStore(); + const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore, assetStore } = useSceneContext(); + const { removeMaterial, setEndTime, setIsVisible } = materialStore(); const { getStorageUnitById } = storageUnitStore(); - const { getHumanById } = humanStore(); + const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad , incrementLoadCount } = humanStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, getAssetById } = assetStore(); + const { addHumanToMonitor } = useHumanEventManager(); const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { vehicles, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = vehicleStore(); - const [currentPhase, setCurrentPhase] = useState('stationed'); const [path, setPath] = useState<[number, number, number][]>([]); const pauseTimeRef = useRef(null); @@ -38,6 +42,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) const { isPaused } = usePauseButtonStore(); const previousTimeRef = useRef(null); const animationFrameIdRef = useRef(null); + const { selectedPath, setSelectedPath } = useSelectedPath(); useEffect(() => { isPausedRef.current = isPaused; @@ -59,17 +64,15 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) ) { return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; } else { - console.log("There is no path here...Choose valid path") const { path: segmentPaths } = navMeshQuery.computePath(start, start); return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; } + } catch { console.error("Failed to compute path"); return []; } - }, - [navMesh] - ); + }, [navMesh]); function vehicleStatus(modelId: string, status: string) { // console.log(`${modelId} , ${status}`); @@ -97,7 +100,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } useEffect(() => { - if (isPlaying) { + if (isPlaying && selectedPath === "auto") { if (!agvDetail.point.action.unLoadPoint || !agvDetail.point.action.pickUpPoint) return; if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') { @@ -105,10 +108,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), agvDetail?.point?.action?.pickUpPoint?.position ); - // const toPickupPath = computePath( - // new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), - // new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]) - // ); + setPath(toPickupPath); setCurrentPhase('stationed-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); @@ -149,8 +149,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) else { reset() } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [vehicles, currentPhase, path, isPlaying]); + }, [vehicles, currentPhase, path, isPlaying, selectedPath]); function animate(currentTime: number) { if (previousTimeRef.current === null) { @@ -228,6 +227,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) if (agvDetail.point.action.triggers.length > 0) { const trigger = getTriggerByUuid(selectedProduct.productUuid, agvDetail.point.action.triggers[0]?.triggerUuid); const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); + const triggeredAction = getActionByUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredAction?.actionUuid || ''); if (trigger && model) { if (model.type === 'transfer') { @@ -249,8 +249,8 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } } else if (model.type === 'human') { const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid); - if (action) { - handleMaterialDropToHuman(model); + if (action && (triggeredAction?.actionType === 'assembly' || triggeredAction?.actionType === 'worker')) { + handleMaterialDropToHuman(model, triggeredAction); } } } else { @@ -265,76 +265,59 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } } - function handleMaterialDropToHuman(model: HumanEventSchema) { + function handleMaterialDropToHuman(model: HumanEventSchema, action: HumanAction) { + if (model) { - if (model.point.action.actionType === 'worker') { - loopMaterialDropToHuman( - agvDetail.modelUuid, - agvDetail.currentLoad, - agvDetail.point.action.unLoadDuration, - model.modelUuid, - model.point.action.loadCapacity, - agvDetail.point.action - ); + if (action.actionType === 'worker') { + addHumanToMonitor(model.modelUuid, () => { + loopMaterialDropToHuman( + agvDetail, + model.modelUuid, + action.actionUuid + ); + }, action.actionUuid || '') } } } function loopMaterialDropToHuman( - vehicleId: string, - vehicleCurrentLoad: number, - unLoadDuration: number, + vehicle: VehicleStatus, humanId: string, - storageMaxCapacity: number, - action: VehicleAction + humanActionId: string ) { - startTime = performance.now(); - const fixedInterval = ((unLoadDuration / vehicleCurrentLoad) * (1000 / isSpeedRef.current)); + let currentVehicleLoad = vehicle.currentLoad; const unloadLoop = () => { - if (isPausedRef.current) { - pauseTimeRef.current ??= performance.now(); - requestAnimationFrame(unloadLoop); + const human = getHumanById(humanId); + const humanAsset = getAssetById(humanId); + const humanAction = human?.point.actions.find((action) => action.actionUuid === humanActionId); + + if (!human || human.currentAction?.actionUuid !== humanAction?.actionUuid) return; + if (!human.isActive && human.currentLoad < (humanAction?.loadCapacity || 0) && humanAsset?.animationState?.current === 'idle' && humanAction?.actionType === 'worker') { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + decrementVehicleLoad(vehicle.modelUuid, 1); + currentVehicleLoad -= 1; + const material = removeLastMaterial(vehicle.modelUuid); + if (material) { + setIsVisible(material.materialId, false); + addCurrentMaterial(humanId, material.materialType, material.materialId); + incrementHumanLoad(humanId, 1); + incrementLoadCount(humanId, 1); + } return; } - - if (pauseTimeRef.current) { - const pauseDuration = performance.now() - pauseTimeRef.current; - startTime += pauseDuration; - pauseTimeRef.current = null; - } - - const elapsedTime = performance.now() - startTime; - const human = getHumanById(humanId); - - if (elapsedTime >= fixedInterval) { - if (human && agvDetail && - human.currentLoad < storageMaxCapacity && - vehicleCurrentLoad > 0) { - - decrementVehicleLoad(vehicleId, 1); - vehicleCurrentLoad -= 1; - - const material = removeLastMaterial(vehicleId); - if (material) { - - triggerPointActions(action, material.materialId); - - } - - if (vehicleCurrentLoad > 0 && human.currentLoad < storageMaxCapacity) { - startTime = performance.now(); - requestAnimationFrame(unloadLoop); - } - } - } else { + setTimeout(() => { requestAnimationFrame(unloadLoop); - } + }, 500) }; const human = getHumanById(humanId); - if (human && vehicleCurrentLoad > 0 && human?.currentLoad < storageMaxCapacity) { - unloadLoop(); + const humanAction = human?.point.actions.find((action) => action.actionUuid === humanActionId); + if (human && human.currentAction?.actionUuid !== humanActionId && human.currentLoad < (humanAction?.loadCapacity || 0)) { + addCurrentAction(humanId, humanActionId); + setTimeout(() => { + unloadLoop(); + }, 500) } } @@ -600,6 +583,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) reset={reset} startUnloadingProcess={startUnloadingProcess} /> + {selectedPath === "manual" && ()} ); diff --git a/app/src/modules/simulation/vehicle/navMesh/navMesh.tsx b/app/src/modules/simulation/vehicle/navMesh/navMesh.tsx index 3c14ea1..ff07ee9 100644 --- a/app/src/modules/simulation/vehicle/navMesh/navMesh.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/navMesh.tsx @@ -1,25 +1,32 @@ import { useRef } from "react"; -import { useNavMesh } from "../../../../store/builder/store"; -import PolygonGenerator from "./polygonGenerator"; -import NavMeshDetails from "./navMeshDetails"; import * as CONSTANTS from "../../../../types/world/worldConstants"; import * as Types from "../../../../types/world/worldTypes"; +import { useLoadingProgress, useNavMesh, useTileDistance } from "../../../../store/builder/store"; +import useModuleStore from "../../../../store/useModuleStore"; +import PolygonGenerator from "./polygonGenerator"; +import NavMeshDetails from "./navMeshDetails"; function NavMesh() { let groupRef = useRef() as Types.RefGroup; const { setNavMesh } = useNavMesh(); + const { loadingProgress } = useLoadingProgress(); + const { activeModule } = useModuleStore(); + const { planeValue, gridValue } = useTileDistance(); return ( <> - - + {activeModule === 'simulation' && loadingProgress === 0 && + <> + + - - - - - - + + + + + + + } ) } diff --git a/app/src/modules/simulation/vehicle/navMesh/navMeshDetails.tsx b/app/src/modules/simulation/vehicle/navMesh/navMeshDetails.tsx index 7290466..10da010 100644 --- a/app/src/modules/simulation/vehicle/navMesh/navMeshDetails.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/navMeshDetails.tsx @@ -1,53 +1,58 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useMemo } from "react"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { generateSoloNavMesh } from "@recast-navigation/generators"; import { init as initRecastNavigation } from "@recast-navigation/core"; import { DebugDrawer, getPositionsAndIndices } from "@recast-navigation/three"; -import { useSceneContext } from "../../../scene/sceneContext"; +import { useToggleView } from "../../../../store/builder/store"; interface NavMeshDetailsProps { setNavMesh: (navMesh: any) => void; groupRef: React.MutableRefObject; } +const NAVMESH_CONFIG = { + + // cellSize: 0.2, + // cellHeight: 0.7, + // walkableRadius: 0.5, + + cellSize: 0.3, + cellHeight: 0.7, + walkableRadius: 0.7, +}; + export default function NavMeshDetails({ setNavMesh, groupRef, }: NavMeshDetailsProps) { - const { aisleStore, wallStore } = useSceneContext(); - const { aisles } = aisleStore(); const { scene } = useThree(); - const { walls } = wallStore(); + const { toggleView } = useToggleView(); + + const meshes = useMemo(() => { + return groupRef.current?.children.filter((child): child is THREE.Mesh => + child instanceof THREE.Mesh + ) || []; + }, [groupRef.current?.children]); useEffect(() => { + if (toggleView || meshes.length === 0) return; + const initializeNavigation = async () => { try { await initRecastNavigation(); - if (!groupRef.current || groupRef.current.children.length === 0) { - return; - } - - const meshes = groupRef?.current?.children as THREE.Mesh[]; const [positions, indices] = getPositionsAndIndices(meshes); - // const cellSize = 0.2; - // const cellHeight = 0.7; - // const walkableRadius = 0.5; + const { cellSize, cellHeight, walkableRadius } = NAVMESH_CONFIG; - const cellSize = 0.3; - const cellHeight = 0.7; - const walkableRadius = 0.7; const { success, navMesh } = generateSoloNavMesh(positions, indices, { cs: cellSize, ch: cellHeight, walkableRadius: Math.round(walkableRadius / cellHeight), }); - if (!success || !navMesh) { - return; - } + if (!success || !navMesh) return; setNavMesh(navMesh); @@ -59,12 +64,12 @@ export default function NavMeshDetails({ debugDrawer.drawNavMesh(navMesh); // scene.add(debugDrawer); } catch (error) { - echo.error("Failed to initialize navigation") + console.error("Failed to initialize navigation:", error); } }; initializeNavigation(); - }, [scene, groupRef, aisles, walls]); + }, [meshes, setNavMesh, toggleView, scene]); return null; } diff --git a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx index f22cef4..19e9482 100644 --- a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx @@ -76,10 +76,16 @@ export default function PolygonGenerator({ turf.lineString(line.map((p: any) => p?.position)) ); - const validLineFeatures = lineFeatures.filter((line) => { + let validLineFeatures = lineFeatures.filter((line) => { const coords = line.geometry.coordinates; return coords.length >= 2; - }); + }).filter((line) => { + if (line.geometry.coordinates[0].toString() !== line.geometry.coordinates[1].toString()) { + return true; + } + }) + + if (validLineFeatures.length < 3) { validLineFeatures = [] } const polygons = turf.polygonize(turf.featureCollection(validLineFeatures)); diff --git a/app/src/pages/Dashboard.tsx b/app/src/pages/Dashboard.tsx index 15959c1..606765c 100644 --- a/app/src/pages/Dashboard.tsx +++ b/app/src/pages/Dashboard.tsx @@ -16,8 +16,9 @@ const Dashboard: React.FC = () => { useEffect(() => { const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken") if (token) { - useSocketStore.getState().initializeSocket(email, organization, token); + useSocketStore.getState().initializeSocket(email, organization, token, refreshToken); } else { } diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 4ff57df..6da93b6 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -1,17 +1,12 @@ import React, { useEffect } from "react"; import useModuleStore from "../store/useModuleStore"; import { - useSocketStore, - useOrganization, - useUserName, - useWallItems, - useSaveVersion, - useViewSceneStore, - useProjectName, - useRenameModeStore, - useSelectedFloorItem, - useZones, - useSelectedComment, + useSocketStore, + useOrganization, + useUserName, + useSaveVersion, + useProjectName, + useActiveTool, } from "../store/builder/store"; import { useNavigate, useParams } from "react-router-dom"; import { useSelectedUserStore } from "../store/collaboration/useCollabStore"; @@ -31,127 +26,134 @@ import { useVersionHistoryStore } from "../store/builder/useVersionHistoryStore" import { VersionProvider } from "../modules/builder/version/versionContext"; import { sharedWithMeProjects } from "../services/dashboard/sharedWithMeProject"; import SecondaryCanvas from "../modules/secondaryCamera/secondaryCanvas"; +import { handleCanvasCursors } from "../utils/mouseUtils/handleCanvasCursors"; const Project: React.FC = () => { - let navigate = useNavigate(); - const echo = useLogger(); - const { setToggleUI } = useToggleStore(); - const { setActiveModule } = useModuleStore(); - const { setUserName } = useUserName(); - const { setOrganization } = useOrganization(); - const { setWallItems } = useWallItems(); - const { setZones } = useZones(); - const { isVersionSaved } = useSaveVersion(); - const { projectId } = useParams(); - const { setProjectName } = useProjectName(); - const { userId, email, organization, userName } = getUserData(); - const { selectedUser } = useSelectedUserStore(); - const { isLogListVisible } = useLogger(); - const { setVersions } = useVersionHistoryStore(); + let navigate = useNavigate(); + const echo = useLogger(); + const { setToggleUI } = useToggleStore(); + const { setActiveModule } = useModuleStore(); + const { setUserName } = useUserName(); + const { setOrganization } = useOrganization(); + const { isVersionSaved } = useSaveVersion(); + const { projectId } = useParams(); + const { setProjectName } = useProjectName(); + const { userId, email, organization, userName } = getUserData(); + const { selectedUser } = useSelectedUserStore(); + const { isLogListVisible } = useLogger(); + const { setVersions } = useVersionHistoryStore(); + const { activeTool } = useActiveTool(); + useEffect(() => { + if (!email || !userId) { + console.error("User data not found in localStorage"); + return; + } - useEffect(() => { - if (!email || !userId) { - console.error("User data not found in localStorage"); - return; - } + const fetchProjects = async () => { + try { + const projects = await getAllProjects(userId, organization); + const shared = await sharedWithMeProjects(); - const fetchProjects = async () => { - try { - const projects = await getAllProjects(userId, organization); - const shared = await sharedWithMeProjects(); + const allProjects = [...(projects?.Projects || []), ...(shared || [])]; - const allProjects = [...(projects?.Projects || []), ...(shared || [])]; + const matchedProject = allProjects.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); - const matchedProject = allProjects.find( - (val: any) => val.projectUuid === projectId || val._id === projectId - ); + if (matchedProject) { + setProjectName(matchedProject.projectName); + await viewProject(organization, matchedProject._id, userId); + } else { + console.warn("Project not found with given ID:", projectId); + } + } catch (error) { + console.error("Error fetching projects:", error); + } + }; - if (matchedProject) { - setProjectName(matchedProject.projectName); - await viewProject(organization, matchedProject._id, userId); - } else { - console.warn("Project not found with given ID:", projectId); - } - } catch (error) { - console.error("Error fetching projects:", error); - } - }; + fetchProjects(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - fetchProjects(); - }, []); + useEffect(() => { + if (!projectId) return; + getVersionHistoryApi(projectId) + .then((data) => { + const versions: VersionHistory = []; + data.versions.forEach((version: any) => { + versions.push({ + version: version.version, + versionId: version.versionId, + versionName: version.versionName, + versionDescription: version.description, + timeStamp: version.createdAt, + createdBy: version.createdBy.userName, + }); + }); + setVersions(versions); + }) + .catch(() => { + console.error("Error fetching version history"); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projectId]); + useEffect(() => { + if (!isVersionSaved) { + setToggleUI(true, true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isVersionSaved]); - useEffect(() => { - if (!projectId) return; - getVersionHistoryApi(projectId).then((data) => { - const versions: VersionHistory = []; - data.versions.forEach((version: any) => { - versions.push({ - version: version.version, - versionId: version.versionId, - versionName: version.versionName, - versionDescription: version.description, - timeStamp: version.createdAt, - createdBy: version.createdBy.userName - }) - }) - setVersions(versions); - }).catch(() => { - console.error("Error fetching version history") - }) - }, [projectId]) + useEffect(() => { + setActiveModule("builder"); + if (email) { + const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken"); + if (token) { + useSocketStore + .getState() + .initializeSocket(email, organization, token, refreshToken); + } + if (organization && userName) { + setOrganization(organization); + setUserName(userName); + } + echo.success("Log in successful"); + } else { + navigate("/"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - useEffect(() => { - if (!isVersionSaved) { - setToggleUI(true, true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isVersionSaved]); + useEffect(() => { + handleCanvasCursors(activeTool); + }, [activeTool]); - useEffect(() => { - setWallItems([]); - setZones([]); - setActiveModule("builder"); - if (email) { - const token = localStorage.getItem("token"); - if (token) { - useSocketStore.getState().initializeSocket(email, organization, token); - } - if (organization && userName) { - setOrganization(organization); - setUserName(userName); - } - echo.success("Log in successful"); - } else { - navigate("/"); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + return ( +
+ + + + + + + + + + - return ( -
- - - - - - - - - - + - - - {selectedUser && } - {isLogListVisible && ( - - - - )} -
- ); + {selectedUser && } + {isLogListVisible && ( + + + + )} +
+ ); }; export default Project; diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index ba4a007..d4e3170 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -1,11 +1,15 @@ -import * as THREE from "three"; import { create } from "zustand"; import { io } from "socket.io-client"; import * as CONSTANTS from "../../types/world/worldConstants"; export const useSocketStore = create((set: any, get: any) => ({ socket: null, - initializeSocket: (email?: string, organization?: string, token?: string) => { + initializeSocket: ( + email?: string, + organization?: string, + token?: string, + refreshToken?: string + ) => { const existingSocket = get().socket; if (existingSocket) { return; @@ -15,7 +19,7 @@ export const useSocketStore = create((set: any, get: any) => ({ `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, { reconnection: true, - auth: { token }, + auth: { token, refreshToken }, } ); @@ -23,7 +27,7 @@ export const useSocketStore = create((set: any, get: any) => ({ `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, { reconnection: true, - auth: { token }, + auth: { token, refreshToken }, } ); @@ -31,21 +35,21 @@ export const useSocketStore = create((set: any, get: any) => ({ `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/dashboard`, { reconnection: true, - auth: { token }, + auth: { token, refreshToken }, } ); const projectSocket = io( `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, { reconnection: true, - auth: { token }, + auth: { token, refreshToken }, } ); const threadSocket = io( `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, { reconnection: true, - auth: { token }, + auth: { token, refreshToken }, } ); @@ -141,39 +145,11 @@ export const useToggleView = create((set: any) => ({ setToggleView: (x: any) => set(() => ({ toggleView: x })), })); -export const useUpdateScene = create((set: any) => ({ - updateScene: false, - setUpdateScene: (x: any) => set(() => ({ updateScene: x })), -})); - -export const useWalls = create((set: any) => ({ - walls: [], - setWalls: (x: any) => set(() => ({ walls: x })), -})); - export const useRoomsState = create((set: any) => ({ roomsState: [], setRoomsState: (x: any) => set(() => ({ roomsState: x })), })); -export const useZones = create((set: any) => ({ - zones: [], - setZones: (callback: any) => - set((state: any) => ({ - zones: typeof callback === "function" ? callback(state.zones) : callback, - })), -})); - -interface ZonePointsState { - zonePoints: THREE.Vector3[]; - setZonePoints: (points: THREE.Vector3[]) => void; -} - -export const useZonePoints = create((set) => ({ - zonePoints: [], - setZonePoints: (points) => set({ zonePoints: points }), -})); - export const useSelectedItem = create((set: any) => ({ selectedItem: { name: "", @@ -210,45 +186,11 @@ export const useMenuVisible = create((set: any) => ({ setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), })); -// export const useDeleteTool = create((set: any) => ({ -// deleteTool: false, -// setDeleteTool: (x: any) => set(() => ({ deleteTool: x })), -// })); - export const useToolMode = create((set: any) => ({ toolMode: null, setToolMode: (x: any) => set(() => ({ toolMode: x })), })); -export const useNewLines = create((set: any) => ({ - newLines: [], - setNewLines: (x: any) => set(() => ({ newLines: x })), -})); - -export const useDeletedLines = create((set: any) => ({ - deletedLines: [], - setDeletedLines: (x: any) => set(() => ({ deletedLines: x })), -})); - -export const useMovePoint = create((set: any) => ({ - movePoint: false, - setMovePoint: (x: any) => set(() => ({ movePoint: x })), -})); - -// export const useDeletePointOrLine = create((set: any) => ({ -// deletePointOrLine: false, -// setDeletePointOrLine: (x: any) => set(() => ({ deletePointOrLine: x })), -// })); - -export const useWallItems = create((set: any) => ({ - wallItems: [], - setWallItems: (callback: any) => - set((state: any) => ({ - wallItems: - typeof callback === "function" ? callback(state.wallItems) : callback, - })), -})); - export const useSelectedWallItem = create((set: any) => ({ selectedWallItem: null, setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), @@ -310,24 +252,6 @@ export const useActiveLayer = create((set: any) => ({ setActiveLayer: (x: any) => set({ activeLayer: x }), })); -interface RefTextUpdateState { - refTextupdate: number; - setRefTextUpdate: ( - callback: (currentValue: number) => number | number - ) => void; -} - -export const useRefTextUpdate = create((set) => ({ - refTextupdate: -1000, - setRefTextUpdate: (callback) => - set((state) => ({ - refTextupdate: - typeof callback === "function" - ? callback(state.refTextupdate) - : callback, - })), -})); - export const useResetCamera = create((set: any) => ({ resetCamera: false, setResetCamera: (x: any) => set({ resetCamera: x }), @@ -348,11 +272,6 @@ export const useActiveSubTool = create((set: any) => ({ setActiveSubTool: (x: any) => set({ activeSubTool: x }), })); -export const use2DUndoRedo = create((set: any) => ({ - is2DUndoRedo: null, - set2DUndoRedo: (x: any) => set({ is2DUndoRedo: x }), -})); - export const useElevation = create((set: any) => ({ elevation: 45, setElevation: (x: any) => set({ elevation: x }), @@ -429,21 +348,6 @@ export const useDrieUIValue = create((set: any) => ({ })), })); -export const useStartSimulation = create((set: any) => ({ - startSimulation: false, - setStartSimulation: (x: any) => set({ startSimulation: x }), -})); - -export const useEyeDropMode = create((set: any) => ({ - eyeDropMode: false, - setEyeDropMode: (x: any) => set({ eyeDropMode: x }), -})); - -export const useEditingPoint = create((set: any) => ({ - editingPoint: false, - setEditingPoint: (x: any) => set({ editingPoint: x }), -})); - export const usezoneTarget = create((set: any) => ({ zoneTarget: [], setZoneTarget: (x: any) => set({ zoneTarget: x }), @@ -769,4 +673,8 @@ export const useSecondaryCameraState = create((set) => ({ export const useSecondaryCameraEdit = create((set) => ({ secondaryCameraEdit: false, setSecondaryCameraEdit: (x: boolean) => set({ secondaryCameraEdit: x }), -})); \ No newline at end of file +})); +export const useSelectedPath = create((set: any) => ({ + selectedPath: "auto", + setSelectedPath: (x: any) => set({ selectedPath: x }), +})); diff --git a/app/src/store/simulation/useHumanStore.ts b/app/src/store/simulation/useHumanStore.ts index 496228f..bcaf303 100644 --- a/app/src/store/simulation/useHumanStore.ts +++ b/app/src/store/simulation/useHumanStore.ts @@ -12,8 +12,13 @@ interface HumansStore { ) => void; clearHumans: () => void; + setCurrentPhase: (modelUuid: string, phase: string) => void; + + addCurrentAction: (modelUuid: string, actionUuid: string) => void; + removeCurrentAction: (modelUuid: string) => void; + setHumanActive: (modelUuid: string, isActive: boolean) => void; - setHumanPicking: (modelUuid: string, isPicking: boolean) => void; + setHumanScheduled: (modelUuid: string, isPicking: boolean) => void; setHumanLoad: (modelUuid: string, load: number) => void; setHumanState: ( modelUuid: string, @@ -22,6 +27,11 @@ interface HumansStore { incrementHumanLoad: (modelUuid: string, incrementBy: number) => void; decrementHumanLoad: (modelUuid: string, decrementBy: number) => void; + incrementLoadCount: (modelUuid: string, incrementBy: number) => void; + decrementLoadCount: (modelUuid: string, decrementBy: number) => void; + + clearLoadCount: (modelUuid: string) => void; + addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void; setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void; removeLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; @@ -50,10 +60,12 @@ export const createHumanStore = () => { state.humans.push({ ...event, productUuid, + currentPhase: 'init', isActive: false, - isPicking: false, + isScheduled: false, idleTime: 0, activeTime: 0, + totalLoadCount: 0, currentLoad: 0, currentMaterials: [], distanceTraveled: 0 @@ -83,6 +95,39 @@ export const createHumanStore = () => { }); }, + setCurrentPhase: (modelUuid, phase) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentPhase = phase; + } + }); + }, + + addCurrentAction: (modelUuid, actionUuid) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + const action = human.point.actions.find(h => h.actionUuid === actionUuid); + if (action) { + human.currentAction = { + actionUuid: action.actionUuid, + actionName: action.actionName + }; + } + } + }); + }, + + removeCurrentAction: (modelUuid) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentAction = undefined; + } + }); + }, + setHumanActive: (modelUuid, isActive) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); @@ -92,11 +137,11 @@ export const createHumanStore = () => { }); }, - setHumanPicking: (modelUuid, isPicking) => { + setHumanScheduled: (modelUuid, isScheduled) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); if (human) { - human.isPicking = isPicking; + human.isScheduled = isScheduled; } }); }, @@ -137,6 +182,33 @@ export const createHumanStore = () => { }); }, + incrementLoadCount: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.totalLoadCount += incrementBy; + } + }); + }, + + decrementLoadCount: (modelUuid, decrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.totalLoadCount -= decrementBy; + } + }); + }, + + clearLoadCount: (modelUuid) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.totalLoadCount = 0; + } + }); + }, + addCurrentMaterial: (modelUuid, materialType, materialId) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts index a500d9a..98a0928 100644 --- a/app/src/store/simulation/useMaterialStore.ts +++ b/app/src/store/simulation/useMaterialStore.ts @@ -36,6 +36,8 @@ type MaterialsStore = { } | null ) => MaterialSchema | undefined; + clearLocations: (materialId: string) => void; + setMaterial: (materialId: string, materialType: string) => MaterialSchema | undefined; setStartTime: (materialId: string, startTime: number) => MaterialSchema | undefined; setEndTime: (materialId: string, endTime: number) => MaterialSchema | undefined; @@ -140,6 +142,18 @@ export const createMaterialStore = () => { return updatedMaterial; }, + clearLocations: (materialId) => { + let updatedMaterial: MaterialSchema | undefined; + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) { + delete material.previous; + delete material.next; + updatedMaterial = JSON.parse(JSON.stringify(material)); + } + }); + return updatedMaterial; + }, setMaterial: (materialId, materialType) => { let updatedMaterial: MaterialSchema | undefined; set((state) => { diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index aa75d68..258a8cb 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -32,13 +32,13 @@ type ProductsStore = { productUuid: string, modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action'] + action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss @@ -66,8 +66,8 @@ type ProductsStore = { getEventByTriggerUuid: (productUuid: string, triggerUuid: string) => EventsSchema | undefined; getEventByPointUuid: (productUuid: string, pointUuid: string) => EventsSchema | undefined; getPointByUuid: (productUuid: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema | undefined; - getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action']) | undefined; - getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action']) | undefined; + getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]) | undefined; + getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]) | undefined; getModelUuidByPointUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getModelUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getPointUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts index 9742795..2220afd 100644 --- a/app/src/store/simulation/useSimulationStore.ts +++ b/app/src/store/simulation/useSimulationStore.ts @@ -203,8 +203,8 @@ export const useSelectedAnimation = create()( ); interface IsDraggingState { - isDragging: "start" | "end" | null; - setIsDragging: (state: "start" | "end" | null) => void; + isDragging: "start" | "end" | "assembly" | null; + setIsDragging: (state: "start" | "end" | "assembly" | null) => void; } export const useIsDragging = create()( @@ -219,8 +219,8 @@ export const useIsDragging = create()( ); interface IsRotatingState { - isRotating: "start" | "end" | null; - setIsRotating: (state: "start" | "end" | null) => void; + isRotating: "start" | "end" | "assembly" | null; + setIsRotating: (state: "start" | "end" | "assembly" | null) => void; } export const useIsRotating = create()( @@ -276,4 +276,40 @@ export const useComparisonProduct = create()( }); }, })) +); + +interface SelectedPointsState { + selectedPoints: THREE.Object3D[]; + setSelectedPoints: (points: THREE.Object3D[]) => void; + addSelectedPoint: (point: THREE.Object3D) => void; + removeSelectedPoint: (uuid: string) => void; + clearSelectedPoints: () => void; +} + +export const useSelectedPoints = create()( + immer((set) => ({ + selectedPoints: [], + setSelectedPoints: (points) => { + set((state) => { + state.selectedPoints = points; + }); + }, + addSelectedPoint: (point) => { + set((state) => { + if (!state.selectedPoints.find(p => p.uuid === point.uuid)) { + state.selectedPoints.push(point); + } + }); + }, + removeSelectedPoint: (uuid) => { + set((state) => { + state.selectedPoints = state.selectedPoints.filter(p => p.uuid !== uuid); + }); + }, + clearSelectedPoints: () => { + set((state) => { + state.selectedPoints = []; + }); + }, + })) ); \ No newline at end of file diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index 34264da..1e558f0 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -10,6 +10,9 @@ interface VehiclesStore { modelUuid: string, updates: Partial> ) => void; + addPathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], point: VehicleAction["paths"]["initPickup"][number]) => VehicleAction["paths"]; + updatePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string, updates: Partial) => VehicleAction["paths"]; + deletePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string) => VehicleAction["paths"]; clearVehicles: () => void; setVehicleActive: (modelUuid: string, isActive: boolean) => void; @@ -79,6 +82,48 @@ export const createVehicleStore = () => { }); }, + addPathPoint: (modelUuid, pathKey, point) => { + let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] }; + set((state) => { + const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + if (vehicle) { + const path = vehicle.point.action.paths[pathKey]; + path.push(point); + updatedPaths = vehicle.point.action.paths; + } + }); + return updatedPaths; + }, + + updatePathPoint: (modelUuid, pathKey, pointId, updates) => { + let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] }; + set((state) => { + const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + if (vehicle) { + const path = vehicle.point.action.paths[pathKey]; + const index = path.findIndex(p => p.pointId === pointId); + if (index !== -1) { + Object.assign(path[index], updates); + updatedPaths = vehicle.point.action.paths; + } + } + }); + return updatedPaths; + }, + + deletePathPoint: (modelUuid, pathKey, pointId) => { + let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] }; + set((state) => { + const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + if (vehicle) { + const path = vehicle.point.action.paths[pathKey]; + vehicle.point.action.paths[pathKey] = path.filter(p => p.pointId !== pointId); + updatedPaths = vehicle.point.action.paths; + } + }); + return updatedPaths; + }, + clearVehicles: () => { set((state) => { state.vehicles = []; diff --git a/app/src/store/useUIToggleStore.ts b/app/src/store/useUIToggleStore.ts index 1afef0e..cbee72f 100644 --- a/app/src/store/useUIToggleStore.ts +++ b/app/src/store/useUIToggleStore.ts @@ -24,3 +24,30 @@ export const usePlayerStore = create((set) => ({ hidePlayer: false, // initial state setHidePlayer: (hide) => set({ hidePlayer: hide }), // state updater })); + +interface MouseNoteState { + Leftnote: string; + Middlenote: string; + Rightnote: string; + setNotes: (notes: { + Leftnote: string; + Middlenote: string; + Rightnote: string; + }) => void; + setLeftnote: (note: string) => void; + setMiddlenote: (note: string) => void; + setRightnote: (note: string) => void; + resetNotes: () => void; +} + +export const useMouseNoteStore = create((set) => ({ + Leftnote: '', + Middlenote: '', + Rightnote: '', + setNotes: (notes) => set(notes), + setLeftnote: (note) => set({ Leftnote: note }), + setMiddlenote: (note) => set({ Middlenote: note }), + setRightnote: (note) => set({ Rightnote: note }), + resetNotes: () => + set({ Leftnote: '', Middlenote: '', Rightnote: '' }), +})); diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 3de3896..df75860 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -45,4 +45,5 @@ // @use "./scene/scene"; -@use "./scene/comments"; \ No newline at end of file +@use "./scene/comments"; +@use "./scene/cursors.scss"; diff --git a/app/src/styles/pages/dashboard.scss b/app/src/styles/pages/dashboard.scss index 474c060..5a628a4 100644 --- a/app/src/styles/pages/dashboard.scss +++ b/app/src/styles/pages/dashboard.scss @@ -84,12 +84,6 @@ font-weight: var(--font-weight-medium); background: var(--background-color-button); - svg { - path { - stroke: var(--background-color-selected); - } - } - &:hover { background: var(--background-color-button); } diff --git a/app/src/styles/scene/cursors.scss b/app/src/styles/scene/cursors.scss new file mode 100644 index 0000000..43fa058 --- /dev/null +++ b/app/src/styles/scene/cursors.scss @@ -0,0 +1,60 @@ +$cursor-default: url("../../assets/cursors/default.svg") 0 0, default; +$cursor-pen: url("../../assets/cursors/pen.svg") 0 0, default; +$cursor-delete: url("../../assets/cursors/pointing.svg") 4 0, default; +$cursor-comment: url("../../assets/cursors/comment.svg") 0 16, default; +$cursor-draw: url("../../assets/cursors/cell.svg") 8 8, default; +$cursor-cross: url("../../assets/cursors/cross.svg") 8 8, default; +$cursor-grab: url("../../assets/cursors/open.svg") 8 8, default; +$cursor-grabing: url("../../assets/cursors/close.svg") 8 8, default; + +.scene-container { + canvas { + cursor: $cursor-default !important; + } +} + +.scene-container.draw { + canvas { + cursor: $cursor-draw !important; + } +} + +.scene-container.pointer { + canvas { + cursor: $cursor-delete !important; + } +} + +.scene-container.pen { + canvas { + cursor: $cursor-pen !important; + } +} + +.scene-container.measure { + canvas { + cursor: $cursor-cross !important; + } +} + +.scene-container.hand { + canvas { + cursor: $cursor-grab !important; + &:active{ + cursor: $cursor-grabing !important; + } + } +} + +.scene-container.hand-closed { + canvas { + cursor: $cursor-grabing !important; + } +} + +.scene-container.comment { + canvas { + cursor: $cursor-comment !important; + } +} + diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index a39a3ec..5d6b21f 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -40,6 +40,29 @@ interface VehicleAction { steeringAngle: number; pickUpPoint: { position: { x: number; y: number, z: number }, rotation: { x: number; y: number, z: number } } | null; unLoadPoint: { position: { x: number; y: number, z: number }, rotation: { x: number; y: number, z: number } } | null; + paths: { + initPickup: { + pointId: string; + position: [number, number, number]; + isCurved: boolean; + handleA: [number, number, number] | null; + handleB: [number, number, number] | null; + }[], + pickupDrop: { + pointId: string; + position: [number, number, number]; + isCurved: boolean; + handleA: [number, number, number] | null; + handleB: [number, number, number] | null; + }[], + dropPickup: { + pointId: string; + position: [number, number, number]; + isCurved: boolean; + handleA: [number, number, number] | null; + handleB: [number, number, number] | null; + }[], + } triggers: TriggerSchema[]; } @@ -73,11 +96,12 @@ interface HumanAction { actionUuid: string; actionName: string; actionType: "worker" | "assembly"; - processTime?: number; + processTime: number; swapMaterial?: string; - assemblyPoint?: { rotation: [number, number, number] | null; } + assemblyPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } + loadCount: number; loadCapacity: number; triggers: TriggerSchema[]; } @@ -124,7 +148,7 @@ interface HumanPointSchema { uuid: string; position: [number, number, number]; rotation: [number, number, number]; - action: HumanAction; + actions: HumanAction[]; } type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; @@ -223,19 +247,18 @@ interface StorageUnitStatus extends StorageEventSchema { interface HumanStatus extends HumanEventSchema { productUuid: string; + currentPhase: string; isActive: boolean; - isPicking: boolean; + isScheduled: boolean; idleTime: number; activeTime: number; + totalLoadCount: number; currentLoad: number; currentMaterials: { materialType: string; materialId: string; }[]; distanceTraveled: number; currentAction?: { actionUuid: string; actionName: string; - animationUuid: string; - materialType?: string | null; - materialId?: string | null; }; } @@ -302,7 +325,8 @@ type IK = { target: number; effector: number; links: Link[]; - ringRadius?: number; + minDistance?: number; + maxDistance?: number; maxheight?: number; minheight?: number; -} ; +}; diff --git a/app/src/utils/mouseUtils/handleCanvasCursors.ts b/app/src/utils/mouseUtils/handleCanvasCursors.ts new file mode 100644 index 0000000..9a06e93 --- /dev/null +++ b/app/src/utils/mouseUtils/handleCanvasCursors.ts @@ -0,0 +1,37 @@ +export const handleCanvasCursors = (name: string) => { + const canvas = document.getElementById('work-space-three-d-canvas'); + if (!canvas) return; + + const cursorMap: Record = { + default: '', + 'draw-wall': 'draw', + 'draw-aisle': 'draw', + 'draw-zone': 'draw', + 'draw-floor': 'draw', + measure: 'measure', + delete: 'pointer', + pointer: 'pointer', + grab: 'hand', + grabbing: 'hand-closed', + pen: 'pen', + 'free-hand': 'hand', + move: 'move', + comment: 'comment', + // Add more mappings as needed + }; + + const validCursorClasses = new Set(Object.values(cursorMap)); + + // Remove previously applied cursor-related classes + canvas.classList.forEach((cls) => { + if (validCursorClasses.has(cls)) { + canvas.classList.remove(cls); + } + }); + + // Add the new cursor class + const newCursorClass = cursorMap[name]; + if (newCursorClass) { + canvas.classList.add(newCursorClass); + } +}; diff --git a/app/src/utils/mouseUtils/mouseHelper.ts b/app/src/utils/mouseUtils/mouseHelper.ts new file mode 100644 index 0000000..fa309cc --- /dev/null +++ b/app/src/utils/mouseUtils/mouseHelper.ts @@ -0,0 +1,45 @@ +import { useMouseNoteStore } from "../../store/useUIToggleStore"; + +const actionNotes: Record = { + 'left+CONTROL': 'Box Select', + 'left+SHIFT': 'Multi Select', + 'middle+CONTROL': 'Zoom In', +}; + + +export function mouseActionHelper() { + const setNotes = useMouseNoteStore.getState().setNotes; + + const activeKeys = new Set(); + + function updateNotesFromKeys() { + const sortedKeys = Array.from(activeKeys).sort(); + const leftKey = ['left', ...sortedKeys].join('+'); + const middleKey = ['middle', ...sortedKeys].join('+'); + const rightKey = ['right', ...sortedKeys].join('+'); + + setNotes({ + Leftnote: actionNotes[leftKey] || '', + Middlenote: actionNotes[middleKey] || '', + Rightnote: actionNotes[rightKey] || '', + }); + } + + function handleKeyDown(event: KeyboardEvent) { + activeKeys.add(event.key.toUpperCase()); + updateNotesFromKeys(); + } + + function handleKeyUp(event: KeyboardEvent) { + activeKeys.delete(event.key.toUpperCase()); + updateNotesFromKeys(); + } + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + }; +} diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 1abe459..9cc81e4 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -53,7 +53,7 @@ const KeyPressListener: React.FC = () => { const { setCreateNewVersion } = useVersionHistoryStore(); const { setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { setSelectedComment } = useSelectedComment(); - const { setDfxUploaded, setDxfWallGenerate } = useDfxUpload(); + const { setDfxUploaded } = useDfxUpload(); const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || @@ -128,6 +128,9 @@ const KeyPressListener: React.FC = () => { setActiveTool("measure"); setToolMode("MeasurementScale"); } + if (key === "C") { + setActiveTool("comment"); + } }; const handleSidebarShortcuts = (key: string) => { diff --git a/compose.yaml b/compose.yaml index 60ae66f..3697f47 100644 --- a/compose.yaml +++ b/compose.yaml @@ -4,13 +4,13 @@ services: context: ./app dockerfile: Dockerfile args: - - REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:7999 - - REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:4999 + - REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:9902 + - REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:9901 - REACT_APP_SERVER_MARKETPLACE_URL=185.100.212.76:50011 - container_name: aalai-beta-Demo + container_name: aalaiDemoTwo stdin_open: true tty: true ports: - - "8300:80" + - "8400:80" volumes: - ./app:/app