From f7a0f3b3d67fe9c52d6241a242c98ddef7078401 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 9 Apr 2025 15:38:29 +0530 Subject: [PATCH] feat: Enhance ArmBotMechanics with connected models and process management; update ConveyorMechanics for radio input behavior; adjust NavMeshDetails cell size; add depthWrite to ZoneGroup material; include triggers in copy and duplication controls; refine path connector actions filtering; improve socket store zone management --- .../mechanics/ArmBotMechanics.tsx | 401 ++++++++++++++---- .../mechanics/ConveyorMechanics.tsx | 4 +- .../modules/builder/agv/navMeshDetails.tsx | 2 +- app/src/modules/builder/groups/zoneGroup.tsx | 1 + .../controls/selection/copyPasteControls.tsx | 1 + .../selection/duplicationControls.tsx | 1 + .../modules/simulation/path/pathConnector.tsx | 13 +- app/src/store/store.ts | 6 +- 8 files changed, 333 insertions(+), 96 deletions(-) diff --git a/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx index 4dffdb2..cfa7822 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ArmBotMechanics.tsx @@ -1,32 +1,128 @@ import React, { useRef, useMemo, useCallback, useState } from "react"; -import { InfoIcon } from "../../../icons/ExportCommonIcons"; +import { InfoIcon, AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; -import { - useSelectedActionSphere, - useSimulationStates, - useSocketStore -} from "../../../../store/store"; +import { useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store"; import * as Types from '../../../../types/world/worldTypes'; -import LabeledButton from "../../../ui/inputs/LabledButton"; import LabledDropdown from "../../../ui/inputs/LabledDropdown"; +import { handleResize } from "../../../../functions/handleResizePannel"; + +interface ConnectedModel { + modelUUID: string; + modelName: string; + points: { + uuid: string; + position: [number, number, number]; + index?: number; + }[]; + triggers?: { + uuid: string; + name: string; + type: string; + isUsed: boolean; + }[]; +} const ArmBotMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); const { simulationStates, setSimulationStates } = useSimulationStates(); const { socket } = useSocketStore(); - const [selectedTrigger, setSelectedTrigger] = useState(null); + const [selectedProcessIndex, setSelectedProcessIndex] = useState(null); + const actionsContainerRef = useRef(null); - const propertiesContainerRef = useRef(null); - - // Get connected models for dropdowns - const connectedModels = useMemo(() => { + // Get connected models and their triggers + const connectedModels = useMemo(() => { if (!selectedActionSphere?.points?.uuid) return []; + const armBotPaths = simulationStates.filter( + (path): path is Types.ArmBotEventsSchema => path.type === "ArmBot" + ); + + const currentPoint = armBotPaths.find( + (path) => path.points.uuid === selectedActionSphere.points.uuid + )?.points; + + if (!currentPoint?.connections?.targets) return []; + + return currentPoint.connections.targets.reduce((acc, target) => { + const connectedModel = simulationStates.find( + (model) => model.modeluuid === target.modelUUID + ); + + if (!connectedModel) return acc; + + let triggers: { uuid: string; name: string; type: string; isUsed: boolean }[] = []; + let points: { uuid: string; position: [number, number, number] }[] = []; + + if (connectedModel.type === "Conveyor") { + const conveyor = connectedModel as Types.ConveyorEventsSchema; + + const connectedPointUUIDs = currentPoint?.connections?.targets + .filter(t => t.modelUUID === connectedModel.modeluuid) + .map(t => t.pointUUID) || []; + + points = conveyor.points + .map((point, idx) => ({ + uuid: point.uuid, + position: point.position, + index: idx + })) + .filter(point => connectedPointUUIDs.includes(point.uuid)); + + + triggers = conveyor.points.flatMap(p => p.triggers?.filter(t => t.isUsed) || []); + } + else if (connectedModel.type === "StaticMachine") { + const staticMachine = connectedModel as Types.StaticMachineEventsSchema; + + points = [{ + uuid: staticMachine.points.uuid, + position: staticMachine.points.position + }]; + + triggers = staticMachine.points.triggers ? + [{ + uuid: staticMachine.points.triggers.uuid, + name: staticMachine.points.triggers.name, + type: staticMachine.points.triggers.type, + isUsed: true // StaticMachine triggers are always considered used + }] : []; + } + + if (!acc.some(m => m.modelUUID === connectedModel.modeluuid)) { + acc.push({ + modelUUID: connectedModel.modeluuid, + modelName: connectedModel.modelName, + points, + triggers + }); + } + + return acc; + }, []); }, [selectedActionSphere, simulationStates]); - // Get triggers only from connected models + // Get triggers from connected models const connectedTriggers = useMemo(() => { - }, [connectedModels, simulationStates]); + return connectedModels.flatMap(model => + (model.triggers || []).map(trigger => ({ + ...trigger, + displayName: `${model.modelName} - ${trigger.name}`, + modelUUID: model.modelUUID + })) + ); + }, [connectedModels]); + + // Get all points from connected models + const connectedPoints = useMemo(() => { + return connectedModels.flatMap(model => + model.points.map(point => ({ + ...point, + displayName: `${model.modelName} - Point${typeof point.index === 'number' ? ` ${point.index}` : ''}`, + modelUUID: model.modelUUID + })) + ); + }, [connectedModels]); + const { selectedPoint } = useMemo(() => { if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null }; @@ -45,77 +141,148 @@ const ArmBotMechanics: React.FC = () => { }, [selectedActionSphere, simulationStates]); const updateBackend = async (updatedPath: Types.ArmBotEventsSchema | undefined) => { - // if (!updatedPath) return; - // const email = localStorage.getItem("email"); - // const organization = email ? email.split("@")[1].split(".")[0] : ""; + if (!updatedPath) return; + const email = localStorage.getItem("email"); + const organization = email ? email.split("@")[1].split(".")[0] : ""; - // const data = { - // organization: organization, - // modeluuid: updatedPath.modeluuid, - // eventData: { type: "ArmBot", points: updatedPath.points } - // } + const data = { + organization: organization, + modeluuid: updatedPath.modeluuid, + eventData: { type: "ArmBot", points: updatedPath.points } + } + console.log('data: ', data); - // socket.emit('v2:model-asset:updateEventData', data); + socket.emit('v2:model-asset:updateEventData', data); } - // const handleActionUpdate = useCallback((updatedAction: Partial) => { - // if (!selectedActionSphere?.points?.uuid) return; + const handleActionUpdate = useCallback((updatedAction: Partial) => { + if (!selectedActionSphere?.points?.uuid || !selectedPoint) return; - // const updatedPaths = simulationStates.map((path) => { - // return path; - // }); + const updatedPaths = simulationStates.map((path) => { + if (path.type === "ArmBot" && path.points.uuid === selectedActionSphere.points.uuid) { + return { + ...path, + points: { + ...path.points, + actions: { + ...path.points.actions, + ...updatedAction + } + } + }; + } + return path; + }); - // const updatedPath = updatedPaths.find( - // (path): path is Types.ArmBotEventsSchema => - // path.type === "ArmBot" && - // path.points.uuid === selectedActionSphere.points.uuid - // ); - // updateBackend(updatedPath); + const updatedPath = updatedPaths.find( + (path): path is Types.ArmBotEventsSchema => + path.type === "ArmBot" && + path.points.uuid === selectedActionSphere.points.uuid + ); + updateBackend(updatedPath); - // setSimulationStates(updatedPaths); - // }, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]); + setSimulationStates(updatedPaths); + }, [selectedActionSphere?.points?.uuid, selectedPoint, simulationStates, setSimulationStates]); - // const handleSpeedChange = useCallback((speed: number) => { - // handleActionUpdate({ speed }); - // }, [handleActionUpdate]); + const handleSpeedChange = useCallback((speed: number) => { + handleActionUpdate({ speed }); + }, [handleActionUpdate]); - // const handleProcessChange = useCallback((processes: Types.ArmBotEventsSchema['points']['actions']['processes']) => { - // handleActionUpdate({ processes }); - // }, [handleActionUpdate]); + const handleProcessChange = useCallback((processes: Types.ArmBotEventsSchema['points']['actions']['processes']) => { + handleActionUpdate({ processes }); + }, [handleActionUpdate]); - // const handleTriggerSelect = useCallback((displayName: string) => { - // const selected = connectedTriggers.find(t => t.displayName === displayName); - // setSelectedTrigger(selected?.uuid || null); - // }, [connectedTriggers]); + const handleAddProcess = useCallback(() => { + if (!selectedPoint) return; - // const handleStartPointSelect = useCallback((pointUUID: string) => { - // if (!selectedTrigger || !selectedPoint) return; + const newProcess: any = { + triggerId: "", + startPoint: "", + endPoint: "" + }; - // const updatedProcesses = selectedPoint.actions.processes?.map(process => - // process.triggerId === selectedTrigger - // ? { ...process, startPoint: pointUUID } - // : process - // ) || []; + const updatedProcesses = selectedPoint.actions.processes ? [...selectedPoint.actions.processes, newProcess] : [newProcess]; - // handleProcessChange(updatedProcesses); - // }, [selectedTrigger, selectedPoint, handleProcessChange]); + handleProcessChange(updatedProcesses); + setSelectedProcessIndex(updatedProcesses.length - 1); + }, [selectedPoint, handleProcessChange]); - // const handleEndPointSelect = useCallback((pointUUID: string) => { - // if (!selectedTrigger || !selectedPoint) return; + const handleDeleteProcess = useCallback((index: number) => { + if (!selectedPoint?.actions.processes) return; - // const updatedProcesses = selectedPoint.actions.processes?.map(process => - // process.triggerId === selectedTrigger - // ? { ...process, endPoint: pointUUID } - // : process - // ) || []; + const updatedProcesses = [...selectedPoint.actions.processes]; + updatedProcesses.splice(index, 1); - // handleProcessChange(updatedProcesses); - // }, [selectedTrigger, selectedPoint, handleProcessChange]); + handleProcessChange(updatedProcesses); - // const getCurrentProcess = useCallback(() => { - // if (!selectedTrigger || !selectedPoint) return null; - // return selectedPoint.actions.processes?.find(p => p.triggerId === selectedTrigger); - // }, [selectedTrigger, selectedPoint]); + // Reset selection if deleting the currently selected process + if (selectedProcessIndex === index) { + setSelectedProcessIndex(null); + } else if (selectedProcessIndex !== null && selectedProcessIndex > index) { + // Adjust selection index if needed + setSelectedProcessIndex(selectedProcessIndex - 1); + } + }, [selectedPoint, selectedProcessIndex, handleProcessChange]); + + const handleTriggerSelect = useCallback((displayName: string, index: number) => { + const selected = connectedTriggers.find(t => t.displayName === displayName); + if (!selected || !selectedPoint?.actions.processes) return; + + const oldProcess = selectedPoint.actions.processes[index]; + + const updatedProcesses = [...selectedPoint.actions.processes]; + + // Only reset start/end if new trigger invalidates them (your logic can expand this) + updatedProcesses[index] = { + ...oldProcess, + triggerId: selected.uuid, + startPoint: oldProcess.startPoint || "", // preserve if exists + endPoint: oldProcess.endPoint || "" // preserve if exists + }; + + handleProcessChange(updatedProcesses); + }, [connectedTriggers, selectedPoint, handleProcessChange]); + + const handleStartPointSelect = useCallback((displayName: string, index: number) => { + if (!selectedPoint?.actions.processes) return; + + const point = connectedPoints.find(p => p.displayName === displayName); + if (!point) return; + + const updatedProcesses = [...selectedPoint.actions.processes]; + updatedProcesses[index] = { + ...updatedProcesses[index], + startPoint: point.uuid + }; + + handleProcessChange(updatedProcesses); + }, [selectedPoint, connectedPoints, handleProcessChange]); + + const handleEndPointSelect = useCallback((displayName: string, index: number) => { + if (!selectedPoint?.actions.processes) return; + + const point = connectedPoints.find(p => p.displayName === displayName); + if (!point) return; + + const updatedProcesses = [...selectedPoint.actions.processes]; + updatedProcesses[index] = { + ...updatedProcesses[index], + endPoint: point.uuid + }; + + handleProcessChange(updatedProcesses); + }, [selectedPoint, connectedPoints, handleProcessChange]); + + const getProcessByIndex = useCallback((index: number) => { + if (!selectedPoint?.actions.processes || index >= selectedPoint.actions.processes.length) return null; + return selectedPoint.actions.processes[index]; + }, [selectedPoint]); + + const getFilteredTriggerOptions = (currentIndex: number) => { + const usedTriggerUUIDs = selectedPoint?.actions.processes?.filter((_, i) => i !== currentIndex).map(p => p.triggerId).filter(Boolean) ?? []; + + return connectedTriggers.filter(trigger => !usedTriggerUUIDs.includes(trigger.uuid)).map(trigger => trigger.displayName); + }; return (
@@ -124,10 +291,10 @@ const ArmBotMechanics: React.FC = () => {
-
+
ArmBot Properties
- {/* {selectedPoint && ( + {selectedPoint && ( <> { onChange={(value) => handleSpeedChange(parseInt(value))} /> - t.uuid === selectedTrigger)?.displayName || ''} - onSelect={handleTriggerSelect} - options={connectedTriggers.map(trigger => trigger.displayName)} - /> +
+
+
Processes
+
+ Add +
+
+
+
+ {selectedPoint.actions.processes?.map((process, index) => ( +
+
setSelectedProcessIndex(index)} + > + Process {index + 1} +
+
handleDeleteProcess(index)} + > + +
+
+ ))} +
+
handleResize(e, actionsContainerRef)} + > + +
+
+
- {selectedTrigger && ( - <> + {selectedProcessIndex !== null && ( +
+ t.uuid === getProcessByIndex(selectedProcessIndex)?.triggerId + )?.displayName || 'Select a trigger' + } + onSelect={(value) => handleTriggerSelect(value, selectedProcessIndex)} + options={getFilteredTriggerOptions(selectedProcessIndex)} + /> + + `${model.modelName} [${index}]`)} + defaultOption={ + connectedPoints.find(p => + p.uuid === getProcessByIndex(selectedProcessIndex)?.startPoint + )?.displayName || 'Select start point' + } + onSelect={(value) => handleStartPointSelect(value, selectedProcessIndex)} + options={connectedPoints.map(point => point.displayName)} /> `${model.modelName} [${index}]`)} + defaultOption={ + connectedPoints.find(p => + p.uuid === getProcessByIndex(selectedProcessIndex)?.endPoint + )?.displayName || 'Select end point' + } + onSelect={(value) => handleEndPointSelect(value, selectedProcessIndex)} + options={connectedPoints.map(point => point.displayName)} /> - - +
)} - )} */} + )}
diff --git a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx index 6c7487e..71ee9ce 100644 --- a/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/ConveyorMechanics.tsx @@ -647,7 +647,7 @@ const ConveyorMechanics: React.FC = () => { setSelectedItem({ type: "action", item: action }) } > - +
{ setSelectedItem({ type: "trigger", item: trigger }) } > - +
{ uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) }, }, transparent: true, + depthWrite: false }), []); useEffect(() => { diff --git a/app/src/modules/scene/controls/selection/copyPasteControls.tsx b/app/src/modules/scene/controls/selection/copyPasteControls.tsx index d315c10..8a0f8ca 100644 --- a/app/src/modules/scene/controls/selection/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selection/copyPasteControls.tsx @@ -345,6 +345,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas uuid: THREE.MathUtils.generateUUID() } : defaultAction, + triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' }, connections: { source: { modelUUID: obj.uuid, pointUUID }, targets: [] diff --git a/app/src/modules/scene/controls/selection/duplicationControls.tsx b/app/src/modules/scene/controls/selection/duplicationControls.tsx index 9c987d0..dfadaea 100644 --- a/app/src/modules/scene/controls/selection/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selection/duplicationControls.tsx @@ -327,6 +327,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb uuid: THREE.MathUtils.generateUUID() } : defaultAction, + triggers: {uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete'}, connections: { source: { modelUUID: obj.uuid, pointUUID }, targets: [] diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index bb4c039..c561cbb 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -952,8 +952,17 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec ); }) }, - // Ensure all required ArmBot point properties are included - actions: state.points.actions, + actions: { + ...state.points.actions, + processes: state.points.actions.processes?.filter(process => { + return !( + process.startPoint === connection1.point || + process.endPoint === connection1.point || + process.startPoint === connection2.point || + process.endPoint === connection2.point + ); + }) || [] + }, triggers: state.points.triggers } }; diff --git a/app/src/store/store.ts b/app/src/store/store.ts index d39e8e1..dad47b3 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -68,7 +68,11 @@ export const useWalls = create((set: any) => ({ export const useZones = create((set: any) => ({ zones: [], - setZones: (x: any) => set(() => ({ zones: x })), + setZones: (callback: any) => + set((state: any) => ({ + zones: + typeof callback === 'function' ? callback(state.zones) : callback + })) })); interface ZonePointsState {