From a7e2e08d912ffdd45568ac16aa53dd5a777ea15e Mon Sep 17 00:00:00 2001 From: Vishnu Date: Fri, 1 Aug 2025 12:14:04 +0530 Subject: [PATCH 01/38] dof commented --- app/src/modules/scene/postProcessing/postProcessing.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 3b96455..fc9ce0b 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -79,11 +79,11 @@ export default function PostProcessing() { denoiseRadius={6} denoiseSamples={16} /> - + /> */} Date: Fri, 1 Aug 2025 12:16:17 +0530 Subject: [PATCH 02/38] bloom comented --- app/src/modules/scene/postProcessing/postProcessing.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index fc9ce0b..8aacd96 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -84,12 +84,12 @@ export default function PostProcessing() { focalLength={0.15} bokehScale={2} /> */} - + /> */} {selectedWallAsset && ( Date: Fri, 1 Aug 2025 13:28:42 +0530 Subject: [PATCH 03/38] refactor: stats moved to sepetate component --- app/src/modules/scene/helpers/StatsHelper.tsx | 22 +++++++++++++++++++ app/src/modules/scene/scene.tsx | 5 +++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 app/src/modules/scene/helpers/StatsHelper.tsx diff --git a/app/src/modules/scene/helpers/StatsHelper.tsx b/app/src/modules/scene/helpers/StatsHelper.tsx new file mode 100644 index 0000000..486488b --- /dev/null +++ b/app/src/modules/scene/helpers/StatsHelper.tsx @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; +import { Stats } from "@react-three/drei"; +import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys"; + +export default function StatsHelper() { + const [visible, setVisible] = useState(false); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + if (keyCombination === "F1") { + event.preventDefault(); + setVisible(prev => !prev); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, []); + + return visible ? : null; +} diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 0f08a2d..0e7a277 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo } from "react"; import { Canvas } from "@react-three/fiber"; -import { KeyboardControls, Stats } from "@react-three/drei"; +import { KeyboardControls } from "@react-three/drei"; import { useSceneContext } from "./sceneContext"; import Builder from "../builder/builder"; @@ -14,6 +14,7 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects"; import { getUserData } from "../../functions/getUserData"; import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; import { Color, SRGBColorSpace } from "three"; +import StatsHelper from "./helpers/StatsHelper"; export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) { const map = useMemo(() => [ @@ -76,7 +77,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co - + ); From 0da9e8997ca55fbac8583f3f885a03ad8e18e4dd Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 1 Aug 2025 18:10:54 +0530 Subject: [PATCH 04/38] feat: Clean up commented code and improve asset handling in various components --- app/src/modules/builder/builder.tsx | 8 +-- .../selection3D/copyPasteControls3D.tsx | 2 +- .../selection3D/moveControls3D.tsx | 2 +- app/src/modules/scene/scene.tsx | 5 +- .../instances/animator/roboticArmAnimator.tsx | 58 +++++++++++++++++-- .../armInstance/roboticArmInstance.tsx | 4 -- .../simulation/spatialUI/arm/armBotUI.tsx | 13 +++-- .../spatialUI/arm/useDraggableGLTF.ts | 6 +- .../instances/animator/vehicleAnimator.tsx | 4 +- 9 files changed, 77 insertions(+), 25 deletions(-) diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index d340a35..ab9434b 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -95,7 +95,7 @@ export default function Builder() { - {/* + @@ -103,7 +103,7 @@ export default function Builder() { - */} + @@ -113,9 +113,9 @@ export default function Builder() { - {/* */} + - {/* */} + diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index a147750..1466b3f 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -31,7 +31,7 @@ const CopyPasteControls3D = ({ const { assetStore, eventStore } = useSceneContext(); const { addEvent } = eventStore(); const { projectId } = useParams(); - const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore(); + const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 18f4abf..6f65300 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -339,7 +339,7 @@ function MoveControls3D({ } updateAsset(movedAsset.userData.modelUuid, { - position: asset.position, + position: [position.x, position.y, position.z], rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 0f08a2d..e10fc4a 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo } from "react"; import { Canvas } from "@react-three/fiber"; -import { KeyboardControls, Stats } from "@react-three/drei"; +import { KeyboardControls } from "@react-three/drei"; import { useSceneContext } from "./sceneContext"; import Builder from "../builder/builder"; @@ -52,7 +52,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co }).catch((err) => { console.error(err); }); - // eslint-disable-next-line + // eslint-disable-next-line }, [activeModule, assets, loadingProgress]) return ( @@ -76,7 +76,6 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co - ); diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index c382230..1f81c2d 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -1,15 +1,27 @@ import { useEffect, useRef, useState } from 'react'; -import { useFrame } from '@react-three/fiber'; +import { useFrame, useThree } from '@react-three/fiber'; import * as THREE from 'three'; import { Line, Text } from '@react-three/drei'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; type PointWithDegree = { position: [number, number, number]; degree: number; }; -function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone, armBot, path }: any) { +interface RoboticArmAnimatorProps { + HandleCallback: () => void; + restPosition: THREE.Vector3; + ikSolver: any; + targetBone: string; + armBot: ArmBotStatus; + path: [number, number, number][]; + currentPhase: string; +} + +function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone, armBot, path, currentPhase }: RoboticArmAnimatorProps) { const progressRef = useRef(0); const curveRef = useRef(null); const totalDistanceRef = useRef(0); @@ -19,6 +31,14 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]); const [circlePointsWithDegrees, setCirclePointsWithDegrees] = useState([]); const [customCurvePoints, setCustomCurvePoints] = useState(null); + const { armBotStore, productStore, materialStore } = useSceneContext(); + const { getArmBotById } = armBotStore(); + const { getMaterialById } = materialStore(); + const { getEventByModelUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { scene } = useThree(); + let curveHeight = 1.75 const CIRCLE_RADIUS = 1.6 @@ -145,8 +165,38 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone useEffect(() => { if (circlePoints.length > 0 && currentPath.length > 0) { - const start = currentPath[0]; - const end = currentPath[currentPath.length - 1]; + let start = currentPath[0]; + let end = currentPath[currentPath.length - 1]; + + const armbotStatus = getArmBotById(armBot.modelUuid); + const currentMaterial = armbotStatus?.currentAction?.materialId; + if (armbotStatus && currentMaterial && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end')) { + const materialData = getMaterialById(currentMaterial); + if (materialData) { + const prevModel = getEventByModelUuid(selectedProduct.productUuid, materialData.current.modelUuid); + + if (prevModel && prevModel.type === 'transfer') { + const material = scene.getObjectByProperty("uuid", currentMaterial); + const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid); + if (material && armbotModel) { + const materialWorldPos = new THREE.Vector3(); + material.getWorldPosition(materialWorldPos); + + const armbotWorldPos = new THREE.Vector3(); + armbotModel.getWorldPosition(armbotWorldPos); + + const materialLocalPos = materialWorldPos.clone(); + armbotModel.worldToLocal(materialLocalPos); + + if (currentPhase === 'rest-to-start') { + end = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; + } else if (currentPhase === 'start-to-end') { + start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; + } + } + } + } + } const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number]; const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number]; diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index b41bef8..e1595b6 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -334,7 +334,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { }, [currentPhase, armBot, isPlaying, isReset, ikSolver]) - function createCurveBetweenTwoPoints(p1: any, p2: any) { const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5); const points = [p1, mid, p2]; @@ -342,7 +341,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { } const HandleCallback = () => { - if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") { logStatus(armBot.modelUuid, "Callback triggered: rest"); setArmBotActive(armBot.modelUuid, false) @@ -370,7 +368,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("rest"); setPath([]) - } } @@ -389,7 +386,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { ikSolver={ikSolver} targetBone={targetBone} armBot={armBot} - logStatus={logStatus} path={path} currentPhase={currentPhase} /> diff --git a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx index 1afc206..1f4dee8 100644 --- a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx +++ b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx @@ -55,6 +55,7 @@ const ArmBotUI = () => { }) } + // Fetch and setup selected ArmBot data useEffect(() => { if (selectedEventSphere) { const selectedArmBot = getEventByModelUuid(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid); @@ -85,11 +86,13 @@ const ArmBotUI = () => { const modelData = getEventByModelUuid(selectedProduct.productUuid, modelUuid); if (modelData?.type === "roboticArm") { - const baseY = modelData.point.position?.[1] || 0; + const baseX = modelData.point.position?.[0] || 0; + const baseY = modelData.point.position?.[1] || 0;; + const baseZ = modelData.point.position?.[2] || 0; return { - pick: [0, baseY, 0 + 0.5], - drop: [0, baseY, 0 - 0.5], - default: [0, baseY, 0], + pick: [baseX, baseY, baseZ + 0.5], + drop: [baseX, baseY, baseZ - 0.5], + default: [baseX, baseY, baseZ], }; } @@ -222,7 +225,7 @@ const ArmBotUI = () => { ); } else { - return null; + return null; // important! must return something } })} diff --git a/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts b/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts index 2f63234..ce2090f 100644 --- a/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts +++ b/app/src/modules/simulation/spatialUI/arm/useDraggableGLTF.ts @@ -183,7 +183,11 @@ export default function useDraggableGLTF( targetPosition.z = centerZ + finalLocal.z; // Clamp Y axis using variables - targetPosition.y = Math.min(Math.max(targetPosition.y, minHeight), maxHeight); + + targetPosition.y = Math.min( + Math.max(targetPosition.y, Math.min(minHeight, maxHeight)), + Math.max(minHeight, maxHeight) + ); // Convert to local if parent exists if (parent) { diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 5ea19f9..2edfc58 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useFrame, useThree, ThreeEvent } from '@react-three/fiber'; import * as THREE from 'three'; -import { Line, TransformControls } from '@react-three/drei'; +import { Line } from '@react-three/drei'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useActiveTool, useSelectedPath } from '../../../../../store/builder/store'; From 35153c8c79ae42caf0326de2346969202bb3931a Mon Sep 17 00:00:00 2001 From: Poovizhi Date: Sat, 2 Aug 2025 18:23:42 +0530 Subject: [PATCH 05/38] updated dashboard duplication --- .../components/Dashboard/DashboardCard.tsx | 541 ++-- .../components/Dashboard/DashboardHome.tsx | 18 +- .../Dashboard/DashboardProjects.tsx | 73 +- app/src/components/ui/FileMenu.tsx | 169 +- app/src/modules/scene/scene.tsx | 146 +- app/src/pages/Project.tsx | 5 +- .../services/dashboard/duplicateProject.ts | 10 +- app/src/services/dashboard/updateProject.ts | 2 + app/src/store/simulation/useProductStore.ts | 2193 ++++++++++------- 9 files changed, 1782 insertions(+), 1375 deletions(-) diff --git a/app/src/components/Dashboard/DashboardCard.tsx b/app/src/components/Dashboard/DashboardCard.tsx index 68d91e7..0f75d19 100644 --- a/app/src/components/Dashboard/DashboardCard.tsx +++ b/app/src/components/Dashboard/DashboardCard.tsx @@ -2,289 +2,314 @@ import React, { useState, useRef, useEffect, act } from "react"; import img from "../../assets/image/image.png"; import { useNavigate } from "react-router-dom"; import { getUserData } from "../../functions/getUserData"; -import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store"; +import { + useLoadingProgress, + useProjectName, + useSocketStore, +} from "../../store/builder/store"; import { viewProject } from "../../services/dashboard/viewProject"; import OuterClick from "../../utils/outerClick"; import { KebabIcon } from "../icons/ExportCommonIcons"; import { getAllProjects } from "../../services/dashboard/getAllProjects"; +import { updateProject } from "../../services/dashboard/updateProject"; interface DashBoardCardProps { - projectName: string; - thumbnail: any; - projectId: string; - createdAt?: string; - isViewed?: string; - createdBy?: { _id: string, userName: string }; - handleDeleteProject?: (projectId: string) => Promise; - handleTrashDeleteProject?: (projectId: string) => Promise; - handleRestoreProject?: (projectId: string) => Promise; - handleDuplicateWorkspaceProject?: ( - projectId: string, - projectName: string, - thumbnail: string, - userId?: string - ) => Promise; - handleDuplicateRecentProject?: ( - projectId: string, - projectName: string, - thumbnail: string - ) => Promise; - active?: string; - setIsSearchActive?: React.Dispatch>; - setRecentDuplicateData?: React.Dispatch>; - setProjectDuplicateData?: React.Dispatch>; - setActiveFolder?: React.Dispatch>; + projectName: string; + thumbnail: any; + projectId: string; + createdAt?: string; + isViewed?: string; + createdBy?: { _id: string; userName: string }; + handleDeleteProject?: (projectId: string) => Promise; + handleTrashDeleteProject?: (projectId: string) => Promise; + handleRestoreProject?: (projectId: string) => Promise; + handleDuplicateWorkspaceProject?: ( + projectId: string, + projectName: string, + thumbnail: string, + userId?: string + ) => Promise; + handleDuplicateRecentProject?: ( + projectId: string, + projectName: string, + thumbnail: string + ) => Promise; + active?: string; + setIsSearchActive?: React.Dispatch>; + setRecentDuplicateData?: React.Dispatch>; + setProjectDuplicateData?: React.Dispatch>; + setActiveFolder?: React.Dispatch>; } type RelativeTimeFormatUnit = any; const DashboardCard: React.FC = ({ - projectName, - thumbnail, - projectId, - active, - handleDeleteProject, - handleRestoreProject, - handleTrashDeleteProject, - handleDuplicateWorkspaceProject, - handleDuplicateRecentProject, - createdAt, - createdBy, - setRecentDuplicateData, - setProjectDuplicateData, - setActiveFolder + projectName, + thumbnail, + projectId, + active, + handleDeleteProject, + handleRestoreProject, + handleTrashDeleteProject, + handleDuplicateWorkspaceProject, + handleDuplicateRecentProject, + createdAt, + createdBy, + setRecentDuplicateData, + setProjectDuplicateData, + setActiveFolder, }) => { - const navigate = useNavigate(); - const { setProjectName } = useProjectName(); - const { userId, organization, userName } = getUserData(); - const [isKebabOpen, setIsKebabOpen] = useState(false); - const [renameValue, setRenameValue] = useState(projectName); - const [isRenaming, setIsRenaming] = useState(false); - const { projectSocket } = useSocketStore(); - const { setLoadingProgress } = useLoadingProgress(); - const kebabRef = useRef(null); + const navigate = useNavigate(); + const { setProjectName } = useProjectName(); + const { userId, organization, userName } = getUserData(); + const [isKebabOpen, setIsKebabOpen] = useState(false); + const [renameValue, setRenameValue] = useState(projectName); + const [isRenaming, setIsRenaming] = useState(false); + const { projectSocket } = useSocketStore(); + const { setLoadingProgress } = useLoadingProgress(); + const kebabRef = useRef(null); - const navigateToProject = async (e: any) => { - console.log('active: ', active); - if (active && active == "trash") return; - try { - const viewProjects = await viewProject(organization, projectId, userId) - console.log('viewProjects: ', viewProjects); - console.log('projectName: ', projectName); - setLoadingProgress(1) - setProjectName(projectName); - navigate(`/${projectId}`); - } catch { + const navigateToProject = async (e: any) => { + if (active && active == "trash") return; + try { + const viewProjects = await viewProject(organization, projectId, userId); + setLoadingProgress(1); + setProjectName(projectName); + navigate(`/${projectId}`); + } catch {} + }; + + const handleOptionClick = async (option: string) => { + switch (option) { + case "delete": + if (handleDeleteProject) { + handleDeleteProject(projectId); + } else if (handleTrashDeleteProject) { + handleTrashDeleteProject(projectId); } - }; - - const handleOptionClick = async (option: string) => { - switch (option) { - case "delete": - if (handleDeleteProject) { - handleDeleteProject(projectId); - } else if (handleTrashDeleteProject) { - handleTrashDeleteProject(projectId); - } - break; - case "restore": - if (handleRestoreProject) { - await handleRestoreProject(projectId); - } - break; - case "open in new tab": - try { - if (active === "shared" && createdBy) { - console.log("ihreq"); - const newTab = await viewProject(organization, projectId, createdBy?._id); - console.log('newTab: ', newTab); - } else { - const newTab = await viewProject(organization, projectId, userId); - console.log('newTab: ', newTab); - setProjectName(projectName); - setIsKebabOpen(false); - } - } catch (error) { - - } - window.open(`/${projectId}`, "_blank"); - break; - case "rename": - setIsRenaming(true); - break; - case "duplicate": - if (handleDuplicateWorkspaceProject) { - setProjectDuplicateData && - setProjectDuplicateData({ - projectId, - projectName, - thumbnail, - }); - await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId); - if (active === "shared" && setActiveFolder) { - setActiveFolder("myProjects") - } - } else if (handleDuplicateRecentProject) { - setRecentDuplicateData && - setRecentDuplicateData({ - projectId, - projectName, - thumbnail, - userId - }); - await handleDuplicateRecentProject(projectId, projectName, thumbnail); - } - break; - default: - break; + break; + case "restore": + if (handleRestoreProject) { + await handleRestoreProject(projectId); } - setIsKebabOpen(false); - }; - - OuterClick({ - contextClassName: ["kebab-wrapper", "kebab-options-wrapper"], - setMenuVisible: () => setIsKebabOpen(false), - }); - - const handleProjectName = async (projectName: string) => { - setRenameValue(projectName); - if (!projectId) return; + break; + case "open in new tab": try { - const projects = await getAllProjects(userId, organization); - if (!projects || !projects.Projects) return; - let projectUuid = projects.Projects.find( - (val: any) => val.projectUuid === projectId || val._id === projectId + if (active === "shared" && createdBy) { + const newTab = await viewProject( + organization, + projectId, + createdBy?._id ); - const updateProjects = { - projectId: projectUuid, - organization, - userId, - projectName, - thumbnail: undefined, - }; + } else { + const newTab = await viewProject(organization, projectId, userId); - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); - } - } catch (error) { } - }; - - function getRelativeTime(dateString: string): string { - const date = new Date(dateString); - const now = new Date(); - const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); - - const intervals: Record = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60, - second: 1, - }; - - const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); - - for (const key in intervals) { - const unit = key as RelativeTimeFormatUnit; - const diff = Math.floor(diffInSeconds / intervals[unit]); - if (diff >= 1) { - return rtf.format(-diff, unit); - } + setProjectName(projectName); + setIsKebabOpen(false); + } + } catch (error) {} + window.open(`/${projectId}`, "_blank"); + break; + case "rename": + setIsRenaming(true); + break; + case "duplicate": + if (handleDuplicateWorkspaceProject) { + setProjectDuplicateData && + setProjectDuplicateData({ + projectId, + projectName, + thumbnail, + }); + await handleDuplicateWorkspaceProject( + projectId, + projectName, + thumbnail, + userId + ); + if (active === "shared" && setActiveFolder) { + setActiveFolder("myProjects"); + } + } else if (handleDuplicateRecentProject) { + setRecentDuplicateData && + setRecentDuplicateData({ + projectId, + projectName, + thumbnail, + userId, + }); + await handleDuplicateRecentProject(projectId, projectName, thumbnail); } - return "just now"; + break; + default: + break; } + setIsKebabOpen(false); + }; - const kebabOptionsMap: Record = { - default: ["rename", "delete", "duplicate", "open in new tab"], - trash: ["restore", "delete"], - shared: ["duplicate", "open in new tab"], + OuterClick({ + contextClassName: ["kebab-wrapper", "kebab-options-wrapper"], + setMenuVisible: () => setIsKebabOpen(false), + }); + + const handleProjectName = async (projectName: string) => { + setRenameValue(projectName); + if (!projectId) return; + try { + const projects = await getAllProjects(userId, organization); + if (!projects || !projects.Projects) return; + let projectUuid = projects.Projects.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); + const updateProjects = { + projectId: projectUuid?._id, + organization, + userId, + projectName, + thumbnail: undefined, + }; + // const updatedProjectName = await updateProject( + // projectUuid._id, + // userId, + // organization, + // undefined, + // projectName + // ); + if (projectSocket) { + projectSocket.emit("v1:project:update", updateProjects); + } + } catch (error) {} + }; + + function getRelativeTime(dateString: string): string { + const date = new Date(dateString); + const now = new Date(); + const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + const intervals: Record = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1, }; - const getOptions = () => { - if (active === "trash") return kebabOptionsMap.trash; - if (active === "shared") return kebabOptionsMap.shared; - if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared; - return kebabOptionsMap.default; - }; + const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); - return ( - - - - - {isKebabOpen && ( -
- {getOptions().map((option) => ( - - ))} -
+
+ {isRenaming ? ( + { + e.stopPropagation(); + handleProjectName(e.target.value); + }} + onBlur={() => { + setIsRenaming(false); + setProjectName(renameValue); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + setProjectName(renameValue); + setIsRenaming(false); + } + }} + autoFocus + /> + ) : ( + {renameValue} )} - - ); + {createdAt && ( +
+ {active && active == "trash" ? `Trashed by you` : `Edited `}{" "} + {getRelativeTime(createdAt)} +
+ )} +
+
+
+ {!createdBy + ? userName + ? userName?.charAt(0).toUpperCase() + : "A" + : createdBy?.userName?.charAt(0).toUpperCase()} +
+ +
+ + + {isKebabOpen && ( +
+ {getOptions().map((option) => ( + + ))} +
+ )} + + ); }; -export default DashboardCard; \ No newline at end of file +export default DashboardCard; diff --git a/app/src/components/Dashboard/DashboardHome.tsx b/app/src/components/Dashboard/DashboardHome.tsx index 6e9dce7..9e05994 100644 --- a/app/src/components/Dashboard/DashboardHome.tsx +++ b/app/src/components/Dashboard/DashboardHome.tsx @@ -13,10 +13,10 @@ interface Project { _id: string; projectName: string; thumbnail: string; - createdBy: { _id: string, userName: string }; + createdBy: { _id: string; userName: string }; projectUuid?: string; createdAt: string; - isViewed?: string + isViewed?: string; } interface RecentProjectsData { @@ -25,12 +25,12 @@ interface RecentProjectsData { const DashboardHome: React.FC = () => { const [recentProjects, setRecentProjects] = useState({}); + const [isSearchActive, setIsSearchActive] = useState(false); const { userId, organization } = getUserData(); const { projectSocket } = useSocketStore(); const [recentDuplicateData, setRecentDuplicateData] = useState({}); - const fetchRecentProjects = async () => { try { const projects = await recentlyViewed(organization, userId); @@ -38,9 +38,7 @@ const DashboardHome: React.FC = () => { if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) { setRecentProjects(projects); } - } catch (error) { - console.error("Error fetching recent projects:", error); - } + } catch (error) {} }; const handleRecentProjectSearch = async (inputValue: string) => { @@ -65,7 +63,7 @@ const DashboardHome: React.FC = () => { // userId, // organization // ); - // console.log('deletedProject: ', deletedProject); + // //socket for delete Project const deleteProject = { @@ -91,9 +89,7 @@ const DashboardHome: React.FC = () => { }; }); setIsSearchActive(false); - } catch (error) { - console.error("Error deleting project:", error); - } + } catch (error) {} }; const handleDuplicateRecentProject = async ( @@ -163,4 +159,4 @@ const DashboardHome: React.FC = () => { ); }; -export default DashboardHome; \ No newline at end of file +export default DashboardHome; diff --git a/app/src/components/Dashboard/DashboardProjects.tsx b/app/src/components/Dashboard/DashboardProjects.tsx index ccb56b2..51888d0 100644 --- a/app/src/components/Dashboard/DashboardProjects.tsx +++ b/app/src/components/Dashboard/DashboardProjects.tsx @@ -9,6 +9,7 @@ import { deleteProject } from "../../services/dashboard/deleteProject"; import ProjectSocketRes from "./socket/projectSocketRes.dev"; import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject"; import { duplicateProject } from "../../services/dashboard/duplicateProject"; +import { generateUniqueId } from "../../functions/generateUniqueId"; interface Project { _id: string; @@ -27,7 +28,7 @@ const DashboardProjects: React.FC = () => { const [workspaceProjects, setWorkspaceProjects] = useState( {} ); - const [sharedwithMeProject, setSharedWithMeProjects] = useState([]) + const [sharedwithMeProject, setSharedWithMeProjects] = useState([]); const [projectDuplicateData, setProjectDuplicateData] = useState({}); const [isSearchActive, setIsSearchActive] = useState(false); const [activeFolder, setActiveFolder] = useState("myProjects"); @@ -41,7 +42,11 @@ const DashboardProjects: React.FC = () => { } if (!setWorkspaceProjects || !setIsSearchActive) return; - const searchedProject = await searchProject(organization, userId, inputValue); + const searchedProject = await searchProject( + organization, + userId, + inputValue + ); setIsSearchActive(true); setWorkspaceProjects(searchedProject.message ? {} : searchedProject); }; @@ -49,14 +54,13 @@ const DashboardProjects: React.FC = () => { const fetchAllProjects = async () => { try { const projects = await getAllProjects(userId, organization); + if (!projects || !projects.Projects) return; if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) { setWorkspaceProjects(projects); } - } catch (error) { - console.error("Error fetching projects:", error); - } + } catch (error) {} }; const handleDeleteProject = async (projectId: any) => { @@ -66,7 +70,7 @@ const DashboardProjects: React.FC = () => { // userId, // organization // ); - // console.log('deletedProject: ', deletedProject); + // const deleteProjects = { projectId, organization, @@ -77,7 +81,6 @@ const DashboardProjects: React.FC = () => { if (projectSocket) { projectSocket.emit("v1:project:delete", deleteProjects); } else { - console.error("Socket is not connected."); } setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => { if (!Array.isArray(prevDiscardedProjects?.Projects)) { @@ -92,16 +95,21 @@ const DashboardProjects: React.FC = () => { }; }); setIsSearchActive(false); - } catch (error) { - console.error("Error deleting project:", error); - } + } catch (error) {} }; const handleDuplicateWorkspaceProject = async ( projectId: string, projectName: string, - thumbnail: string, + thumbnail: string ) => { + const duplicatedProject = await duplicateProject( + projectId, + generateUniqueId(), + thumbnail, + projectName + ); + console.log("duplicatedProject: ", duplicatedProject); const duplicateProjectData = { userId, @@ -110,7 +118,7 @@ const DashboardProjects: React.FC = () => { projectUuid: projectId, projectName, }; - projectSocket.emit("v1:project:Duplicate", duplicateProjectData); + // projectSocket.emit("v1:project:Duplicate", duplicateProjectData); }; const renderProjects = () => { @@ -153,16 +161,12 @@ const DashboardProjects: React.FC = () => { )); }; - const sharedProject = async () => { try { const sharedWithMe = await sharedWithMeProjects(); - setSharedWithMeProjects(sharedWithMe) - } catch { - - } - } - + setSharedWithMeProjects(sharedWithMe); + } catch {} + }; useEffect(() => { if (!isSearchActive) { @@ -172,10 +176,9 @@ const DashboardProjects: React.FC = () => { useEffect(() => { if (activeFolder === "shared") { - sharedProject() + sharedProject(); } - }, [activeFolder]) - + }, [activeFolder]); return (
@@ -184,7 +187,10 @@ const DashboardProjects: React.FC = () => { handleProjectsSearch={handleProjectsSearch} /> -
+
-
{activeFolder == "myProjects" ? renderProjects() : renderSharedProjects()}
+
+ {activeFolder == "myProjects" + ? renderProjects() + : renderSharedProjects()} +
- {projectDuplicateData && Object.keys(projectDuplicateData).length > 0 && ( - - )} + {projectDuplicateData && + Object.keys(projectDuplicateData).length > 0 && ( + + )}
); }; export default DashboardProjects; - - diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 86146ba..0f0aabe 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -10,100 +10,101 @@ import { updateProject } from "../../services/dashboard/updateProject"; import { getUserData } from "../../functions/getUserData"; const FileMenu: React.FC = () => { - const [openMenu, setOpenMenu] = useState(false); - const containerRef = useRef(null); - let clickTimeout: NodeJS.Timeout | null = null; - const { projectName, setProjectName } = useProjectName(); - const { dashBoardSocket } = useSocketStore(); - const { projectId } = useParams(); - const { userId, organization, email } = getUserData(); + const [openMenu, setOpenMenu] = useState(false); + const containerRef = useRef(null); + let clickTimeout: NodeJS.Timeout | null = null; + const { projectName, setProjectName } = useProjectName(); + const { dashBoardSocket } = useSocketStore(); + const { projectId } = useParams(); + const { userId, organization, email } = getUserData(); - const handleClick = () => { - if (clickTimeout) return; - setOpenMenu((prev) => !prev); - clickTimeout = setTimeout(() => { - clickTimeout = null; - }, 800); + const handleClick = () => { + if (clickTimeout) return; + setOpenMenu((prev) => !prev); + clickTimeout = setTimeout(() => { + clickTimeout = null; + }, 800); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(event.target as Node) + ) { + setOpenMenu(false); + } }; - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - containerRef.current && - !containerRef.current.contains(event.target as Node) - ) { - setOpenMenu(false); - } - }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); + const handleProjectRename = async (projectName: string) => { + setProjectName(projectName); + if (!projectId) return; - const handleProjectRename = async (projectName: string) => { - setProjectName(projectName); - if (!projectId) return + // localStorage.setItem("projectName", newName); - // localStorage.setItem("projectName", newName); + try { + if (!email || !userId) return; - try { + const projects = await getAllProjects(userId, organization); + if (!projects || !projects.Projects) return; + // console.log('projects: ', projects); + let projectUuid = projects.Projects.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); - if (!email || !userId) return; + const updateProjects = { + projectId: projectUuid?._id, + organization, + userId, + projectName, + thumbnail: undefined, + }; - const projects = await getAllProjects(userId, organization); - if (!projects || !projects.Projects) return; - // console.log('projects: ', projects); - let projectUuid = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId) + // if (dashBoardSocket) { + // const handleResponse = (data: any) => { + // console.log('Project update response:', data); + // dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up + // }; + // dashBoardSocket.on("v1-project:response:update", handleResponse); + // dashBoardSocket.emit("v1:project:update", updateProjects); + // } - const updateProjects = { - projectId: projectUuid, - organization, - userId, - projectName, - thumbnail: undefined - } - - // if (dashBoardSocket) { - // const handleResponse = (data: any) => { - // console.log('Project update response:', data); - // dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up - // }; - // dashBoardSocket.on("v1-project:response:update", handleResponse); - // dashBoardSocket.emit("v1:project:update", updateProjects); - // } - - //API for projects rename - const updatedProjectName = await updateProject( - projectId, - userId, - organization, - undefined, - projectName - ); - // - } catch (error) { - console.error("Error updating project name:", error); - } - }; - return ( - - ); + //API for projects rename + const updatedProjectName = await updateProject( + projectId, + userId, + organization, + undefined, + projectName + ); + console.log("updatedProjectName: ", updatedProjectName, projectId); + } catch (error) { + console.error("Error updating project name:", error); + } + }; + return ( + + ); }; export default FileMenu; diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 22e18ac..67f2ef2 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -16,69 +16,89 @@ import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; import { Color, SRGBColorSpace } from "three"; import StatsHelper from "./helpers/StatsHelper"; -export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) { - const map = useMemo(() => [ - { name: "forward", keys: ["ArrowUp", "w", "W"] }, - { name: "backward", keys: ["ArrowDown", "s", "S"] }, - { name: "left", keys: ["ArrowLeft", "a", "A"] }, - { name: "right", keys: ["ArrowRight", "d", "D"] }, - ], []); - const { assetStore } = useSceneContext(); - const { assets } = assetStore(); - const { userId, organization } = getUserData(); - const { projectId } = useParams(); - const { projectSocket } = useSocketStore(); - const { activeModule } = useModuleStore(); - const { loadingProgress } = useLoadingProgress(); +export default function Scene({ + layout, +}: { + readonly layout: "Main Layout" | "Comparison Layout"; +}) { + const map = useMemo( + () => [ + { name: "forward", keys: ["ArrowUp", "w", "W"] }, + { name: "backward", keys: ["ArrowDown", "s", "S"] }, + { name: "left", keys: ["ArrowLeft", "a", "A"] }, + { name: "right", keys: ["ArrowRight", "d", "D"] }, + ], + [] + ); + const { assetStore } = useSceneContext(); + const { assets } = assetStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { projectSocket } = useSocketStore(); + const { activeModule } = useModuleStore(); + const { loadingProgress } = useLoadingProgress(); - useEffect(() => { - if (!projectId && loadingProgress > 1) return; - getAllProjects(userId, organization) - .then((projects) => { - if (!projects || !projects.Projects) return; - let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId); - const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName('canvas')[0]; - if (!canvas) return; - const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png"); - const updateProjects = { - projectId: project?.projectUuid, - organization, - userId, - projectName: project?.projectName, - thumbnail: screenshotDataUrl, - }; - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); - } - }).catch((err) => { - console.error(err); - }); - // eslint-disable-next-line - }, [activeModule, assets, loadingProgress]) + useEffect(() => { + if (!projectId && loadingProgress > 1) return; + getAllProjects(userId, organization) + .then((projects) => { + if (!projects || !projects.Projects) return; + let project = projects.Projects.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); + const canvas = document + .getElementById("sceneCanvas") + ?.getElementsByTagName("canvas")[0]; + if (!canvas) return; + const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL( + "image/png" + ); + const updateProjects = { + projectId: project?._id, + organization, + userId, + projectName: project?.projectName, + thumbnail: screenshotDataUrl, + }; + if (projectSocket) { + projectSocket.emit("v1:project:update", updateProjects); + } + }) + .catch((err) => { + console.error(err); + }); + // eslint-disable-next-line + }, [activeModule, assets, loadingProgress]); - return ( - - { - e.preventDefault(); - }} - performance={{ min: 0.9, max: 1.0 }} - onCreated={(e) => { - e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d); - }} - gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} - > - - - - - - - - - ); + return ( + + { + e.preventDefault(); + }} + performance={{ min: 0.9, max: 1.0 }} + onCreated={(e) => { + e.scene.background = + layout === "Main Layout" ? null : new Color(0x19191d); + }} + gl={{ + outputColorSpace: SRGBColorSpace, + powerPreference: "high-performance", + antialias: true, + preserveDrawingBuffer: true, + }} + > + + + + + + + + + ); } diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 96c529f..2ee6b29 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -33,7 +33,8 @@ const Project: React.FC = () => { const { setUserName } = useUserName(); const { setOrganization } = useOrganization(); const { projectId } = useParams(); - const { setProjectName } = useProjectName(); + const { projectName, setProjectName } = useProjectName(); + console.log("projectName: ", projectName); const { userId, email, organization, userName } = getUserData(); const { selectedUser } = useSelectedUserStore(); const { isLogListVisible } = useLogger(); @@ -49,6 +50,7 @@ const Project: React.FC = () => { const fetchProjects = async () => { try { const projects = await getAllProjects(userId, organization); + console.log("projects: ", projects); const shared = await sharedWithMeProjects(); const allProjects = [...(projects?.Projects || []), ...(shared || [])]; @@ -56,7 +58,6 @@ const Project: React.FC = () => { const matchedProject = allProjects.find( (val: any) => val.projectUuid === projectId || val._id === projectId ); - if (matchedProject) { setProjectName(matchedProject.projectName); await viewProject(organization, matchedProject._id, userId); diff --git a/app/src/services/dashboard/duplicateProject.ts b/app/src/services/dashboard/duplicateProject.ts index 837bb0e..68fe5c6 100644 --- a/app/src/services/dashboard/duplicateProject.ts +++ b/app/src/services/dashboard/duplicateProject.ts @@ -1,6 +1,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; export const duplicateProject = async ( + refProjectID: string, projectUuid: string, thumbnail: string, projectName: string @@ -16,7 +17,12 @@ export const duplicateProject = async ( token: localStorage.getItem("token") || "", // Coerce null to empty string refresh_token: localStorage.getItem("refreshToken") || "", }, - body: JSON.stringify({ projectUuid, thumbnail, projectName }), + body: JSON.stringify({ + refProjectID, + projectUuid, + thumbnail, + projectName, + }), } ); const newAccessToken = response.headers.get("x-access-token"); @@ -24,7 +30,7 @@ export const duplicateProject = async ( //console.log("New token received:", newAccessToken); localStorage.setItem("token", newAccessToken); } - // console.log("response: ", response); + console.log("response: ", response); if (!response.ok) { console.error("Failed to add project"); } diff --git a/app/src/services/dashboard/updateProject.ts b/app/src/services/dashboard/updateProject.ts index 42e0d44..ab9ac0f 100644 --- a/app/src/services/dashboard/updateProject.ts +++ b/app/src/services/dashboard/updateProject.ts @@ -30,6 +30,7 @@ export const updateProject = async ( body: JSON.stringify(body), } ); + console.log('body: ', body); const newAccessToken = response.headers.get("x-access-token"); if (newAccessToken) { @@ -41,6 +42,7 @@ export const updateProject = async ( } const result = await response.json(); + console.log('result: ', result); return result; } catch (error) { if (error instanceof Error) { diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 258a8cb..3c338b1 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -1,943 +1,1290 @@ -import { create } from 'zustand'; -import { immer } from 'zustand/middleware/immer'; +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; type ProductsStore = { - products: productsSchema; + products: productsSchema; - // Product-level actions - addProduct: (productName: string, productUuid: string) => void; - setProducts: (products: productsSchema) => void; - clearProducts: () => void; - removeProduct: (productUuid: string) => void; - updateProduct: (productUuid: string, updates: Partial<{ productName: string; eventDatas: EventsSchema[] }>) => void; + // Product-level actions + addProduct: (productName: string, productUuid: string) => void; + setProducts: (products: productsSchema) => void; + clearProducts: () => void; + removeProduct: (productUuid: string) => void; + updateProduct: ( + productUuid: string, + updates: Partial<{ productName: string; eventDatas: EventsSchema[] }> + ) => void; - // Event-level actions - addEvent: (productUuid: string, event: EventsSchema) => void; - removeEvent: (productUuid: string, modelUuid: string) => void; - deleteEvent: (modelUuid: string) => EventsSchema[]; - updateEvent: (productUuid: string, modelUuid: string, updates: Partial) => EventsSchema | undefined; + // Event-level actions + addEvent: (productUuid: string, event: EventsSchema) => void; + removeEvent: (productUuid: string, modelUuid: string) => void; + deleteEvent: (modelUuid: string) => EventsSchema[]; + updateEvent: ( + productUuid: string, + modelUuid: string, + updates: Partial + ) => EventsSchema | undefined; - // Point-level actions - addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema) => EventsSchema | undefined; - removePoint: (productUuid: string, modelUuid: string, pointUuid: string) => EventsSchema | undefined; - updatePoint: ( - productUuid: string, - modelUuid: string, - pointUuid: string, - updates: Partial - ) => EventsSchema | undefined; + // Point-level actions + addPoint: ( + productUuid: string, + modelUuid: string, + point: + | ConveyorPointSchema + | VehiclePointSchema + | RoboticArmPointSchema + | MachinePointSchema + | StoragePointSchema + | HumanPointSchema + ) => EventsSchema | undefined; + removePoint: ( + productUuid: string, + modelUuid: string, + pointUuid: string + ) => EventsSchema | undefined; + updatePoint: ( + productUuid: string, + modelUuid: string, + pointUuid: string, + updates: Partial< + | ConveyorPointSchema + | VehiclePointSchema + | RoboticArmPointSchema + | MachinePointSchema + | StoragePointSchema + | HumanPointSchema + > + ) => EventsSchema | undefined; - // Action-level actions - addAction: ( - productUuid: string, - modelUuid: string, - pointUuid: string, - 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 - ) => EventsSchema | undefined; + // Action-level actions + addAction: ( + productUuid: string, + modelUuid: string, + pointUuid: string, + 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< + | ConveyorPointSchema["action"] + | VehiclePointSchema["action"] + | RoboticArmPointSchema["actions"][0] + | MachinePointSchema["action"] + | StoragePointSchema["action"] + | HumanPointSchema["actions"][0] + > + ) => EventsSchema | undefined; - // Trigger-level actionss - addTrigger: ( - productUuid: string, - actionUuid: string, - trigger: TriggerSchema - ) => EventsSchema | undefined; - removeTrigger: (productUuid: string, triggerUuid: string) => EventsSchema | undefined; - updateTrigger: ( - productUuid: string, - triggerUuid: string, - updates: Partial - ) => EventsSchema | undefined; + // Trigger-level actionss + addTrigger: ( + productUuid: string, + actionUuid: string, + trigger: TriggerSchema + ) => EventsSchema | undefined; + removeTrigger: ( + productUuid: string, + triggerUuid: string + ) => EventsSchema | undefined; + updateTrigger: ( + productUuid: string, + triggerUuid: string, + updates: Partial + ) => EventsSchema | undefined; - // Renaming functions - renameProduct: (productUuid: string, newName: string) => void; - renameAction: (productUuid: string, actionUuid: string, newName: string) => EventsSchema | undefined; - renameTrigger: (productUuid: string, triggerUuid: string, newName: string) => EventsSchema | undefined; + // Renaming functions + renameProduct: (productUuid: string, newName: string) => void; + renameAction: ( + productUuid: string, + actionUuid: string, + newName: string + ) => EventsSchema | undefined; + renameTrigger: ( + productUuid: string, + triggerUuid: string, + newName: string + ) => EventsSchema | undefined; - // Helper functions - getProductById: (productUuid: string) => { productName: string; productUuid: string; eventDatas: EventsSchema[] } | undefined; - getEventByModelUuid: (productUuid: string, modelUuid: string) => EventsSchema | undefined; - getEventByActionUuid: (productUuid: string, actionUuid: string) => EventsSchema | undefined; - 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['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; - getTriggerByUuid: (productUuid: string, triggerUuid: string) => TriggerSchema | undefined; - getTriggersByTriggeredPointUuid: (productUuid: string, triggeredPointUuid: string) => TriggerSchema[]; - getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean; + // Helper functions + getProductById: ( + productUuid: string + ) => + | { productName: string; productUuid: string; eventDatas: EventsSchema[] } + | undefined; + getEventByModelUuid: ( + productUuid: string, + modelUuid: string + ) => EventsSchema | undefined; + getEventByActionUuid: ( + productUuid: string, + actionUuid: string + ) => EventsSchema | undefined; + 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["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; + getTriggerByUuid: ( + productUuid: string, + triggerUuid: string + ) => TriggerSchema | undefined; + getTriggersByTriggeredPointUuid: ( + productUuid: string, + triggeredPointUuid: string + ) => TriggerSchema[]; + getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean; }; export const createProductStore = () => { - return create()( - immer((set, get) => ({ - products: [], + return create()( + immer((set, get) => ({ + products: [], - // Product-level actions - addProduct: (productName, productUuid) => { - set((state) => { - const existingProduct = state.products.find(p => p.productUuid === productUuid); - if (!existingProduct) { - const newProduct = { - productName, - productUuid: productUuid, - eventDatas: [] - }; - state.products.push(newProduct); - } - }); - }, + // Product-level actions + addProduct: (productName, productUuid) => { + set((state) => { + const existingProduct = state.products.find( + (p) => p.productUuid === productUuid + ); + if (!existingProduct) { + const newProduct = { + productName, + productUuid: productUuid, + eventDatas: [], + }; + state.products.push(newProduct); + } + }); + }, - setProducts: (products) => { - set((state) => { - state.products = products; - }); - }, + setProducts: (products) => { + set((state) => { + state.products = products; + }); + }, - clearProducts: () => { - set((state) => { - state.products = []; - }); - }, + clearProducts: () => { + set((state) => { + state.products = []; + }); + }, - removeProduct: (productUuid) => { - set((state) => { - state.products = state.products.filter(p => p.productUuid !== productUuid); - }); - }, + removeProduct: (productUuid) => { + set((state) => { + state.products = state.products.filter( + (p) => p.productUuid !== productUuid + ); + }); + }, - updateProduct: (productUuid, updates) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - Object.assign(product, updates); - } - }); - }, + updateProduct: (productUuid, updates) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + Object.assign(product, updates); + } + }); + }, - // Event-level actions - addEvent: (productUuid, event) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const existingEvent = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === event.modelUuid); - if (!existingEvent) { - product.eventDatas.push(event); - } - } - }); - }, - - removeEvent: (productUuid, modelUuid) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); - } - }); - }, - - deleteEvent: (modelUuid) => { - let updatedEvents: EventsSchema[] = []; - set((state) => { - const actionsToDelete = new Set(); - - for (const product of state.products) { - const eventIndex = product.eventDatas.findIndex(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (eventIndex !== -1) { - const event = product.eventDatas[eventIndex]; - - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action) { - actionsToDelete.add(point.action.actionUuid); - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action) { - actionsToDelete.add(point.action.actionUuid); - } else if ('actions' in point) { - for (const action of point.actions) { - actionsToDelete.add(action.actionUuid); - } - } - } - - product.eventDatas.splice(eventIndex, 1); - } - } - - for (const product of state.products) { - for (const event of product.eventDatas) { - let eventModified = false; - - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.triggers) { - const originalLength = point.action.triggers.length; - point.action.triggers = point.action.triggers.filter(trigger => { - return !( - (trigger.triggeredAsset?.triggeredModel?.modelUuid === modelUuid) || - (actionsToDelete.has(trigger.triggeredAsset?.triggeredAction?.actionUuid || '')) - ); - }); - if (point.action.triggers.length !== originalLength) { - eventModified = true; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.triggers) { - const originalLength = point.action.triggers.length; - point.action.triggers = point.action.triggers.filter((trigger: TriggerSchema) => { - return !( - (trigger.triggeredAsset?.triggeredModel?.modelUuid === modelUuid) || - (actionsToDelete.has(trigger.triggeredAsset?.triggeredAction?.actionUuid || '')) - ); - }); - if (point.action.triggers.length !== originalLength) { - eventModified = true; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if (action.triggers) { - const originalLength = action.triggers.length; - action.triggers = action.triggers.filter((trigger: TriggerSchema) => { - return !( - (trigger.triggeredAsset?.triggeredModel?.modelUuid === modelUuid) || - (actionsToDelete.has(trigger.triggeredAsset?.triggeredAction?.actionUuid || '')) - ); - }); - if (action.triggers.length !== originalLength) { - eventModified = true; - } - } - } - } - } - - if (eventModified) { - updatedEvents.push(JSON.parse(JSON.stringify(event))); - } - } - } - }); - return updatedEvents; - }, - - updateEvent: (productUuid, modelUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event) { - Object.assign(event, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - }); - return updatedEvent; - }, - - // Point-level actions - addPoint: (productUuid, modelUuid, point) => { - let updatedEvent: EventsSchema | undefined = undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid); - if (!existingPoint) { - (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if (event && 'point' in event) { - const existingPoint = (event as any).point?.uuid === point.uuid; - if (!existingPoint) { - (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any; - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - }); - return updatedEvent; - }, - - removePoint: (productUuid, modelUuid, pointUuid) => { - let updatedEvent: EventsSchema | undefined = undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - (event as ConveyorEventSchema).points = (event as ConveyorEventSchema).points.filter(p => p.uuid !== pointUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { - // For events with single point, we can't remove it, only reset to empty - } - } - }); - return updatedEvent; - }, - - updatePoint: (productUuid, modelUuid, pointUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); - if (point) { - Object.assign(point, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { - Object.assign((event as any).point, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - }); - return updatedEvent; - }, - - // Action-level actions - addAction: (productUuid, modelUuid, pointUuid, action) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - if (event && 'points' in event) { - const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); - if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) { - point.action = action as any; - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { - if ('action' in (event as any).point) { - if (!(event as any).point.action || (event as any).point.action.actionUuid !== action.actionUuid) { - (event as any).point.action = action; - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if ('actions' in (event as any).point) { - const existingAction = (event as any).point.actions.find((a: any) => a.actionUuid === action.actionUuid); - if (!existingAction) { - (event as any).point.actions.push(action); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - } - }); - return updatedEvent; - }, - - removeAction: (productUuid, actionUuid) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - // Handle ConveyorEventSchema - for (const point of (event as ConveyorEventSchema).points) { - } - } else if ('point' in event) { - const point = (event as any).point; - if (event.type === "roboticArm") { - if ('actions' in point) { - const index = point.actions.findIndex((a: any) => a.actionUuid === actionUuid); - if (index !== -1) { - point.actions.splice(index, 1); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if (event.type === "human") { - if ('actions' in point) { - const index = point.actions.findIndex((a: any) => a.actionUuid === actionUuid); - if (index !== -1) { - point.actions.splice(index, 1); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if ('action' in point && point.action?.actionUuid === actionUuid) { - point.action = undefined; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - }); - return updatedEvent; - }, - - updateAction: (productUuid, actionUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && point.action.actionUuid === actionUuid) { - Object.assign(point.action, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action.actionUuid === actionUuid) { - Object.assign(point.action, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - Object.assign(action, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - }); - return updatedEvent; - }, - - // Trigger-level actions - addTrigger: (productUuid, actionUuid, trigger) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && point.action.actionUuid === actionUuid) { - const existingTrigger = point.action.triggers.find(t => t.triggerUuid === trigger.triggerUuid); - if (!existingTrigger) { - point.action.triggers.push(trigger); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - return; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action.actionUuid === actionUuid) { - const existingTrigger = point.action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid); - if (!existingTrigger) { - point.action.triggers.push(trigger); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - return; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - const existingTrigger = action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid); - if (!existingTrigger) { - action.triggers.push(trigger); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - return; - } - } - } - } - } - }); - return updatedEvent; - }, - - removeTrigger: (productUuid, triggerUuid) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && 'triggers' in point.action) { - const Trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); - if (Trigger) { - point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && 'triggers' in point.action) { - const Trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (Trigger) { - point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } else if ('actions' in point) { - for (const action of point.actions) { - if ('triggers' in action) { - const Trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (Trigger) { - action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); - updatedEvent = JSON.parse(JSON.stringify(event)); - } - } - } - } - } - } - } - }); - return updatedEvent; - }, - - updateTrigger: (productUuid, triggerUuid, updates) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && 'triggers' in point.action) { - const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); - if (trigger) { - Object.assign(trigger, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && 'triggers' in point.action) { - const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - Object.assign(trigger, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if ('triggers' in action) { - const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - Object.assign(trigger, updates); - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - } - } - }); - return updatedEvent; - }, - - // Renaming functions - renameProduct: (productUuid, newName) => { - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - product.productName = newName; - } - }); - }, - - renameAction: (productUuid, actionUuid, newName) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && point.action.actionUuid === actionUuid) { - point.action.actionName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action.actionUuid === actionUuid) { - point.action.actionName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - action.actionName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - }); - return updatedEvent; - }, - - renameTrigger: (productUuid, triggerUuid, newName) => { - let updatedEvent: EventsSchema | undefined; - set((state) => { - const product = state.products.find(p => p.productUuid === productUuid); - if (product) { - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action && 'triggers' in point.action) { - const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); - if (trigger) { - trigger.triggerName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && 'triggers' in point.action) { - const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - trigger.triggerName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if ('triggers' in action) { - const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); - if (trigger) { - trigger.triggerName = newName; - updatedEvent = JSON.parse(JSON.stringify(event)); - return; - } - } - } - } - } - } - } - }); - return updatedEvent; - }, - - // Helper functions - getProductById: (productUuid) => { - return get().products.find(p => p.productUuid === productUuid); - }, - - getEventByModelUuid: (productUuid, modelUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); - }, - - getEventByActionUuid: (productUuid, actionUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return event; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return event; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { - return event; - } - } - } - } - return undefined; - }, - - getEventByTriggerUuid: (productUuid, triggerUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.triggers?.some(t => t.triggerUuid === triggerUuid)) { - return event; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point) { - if (point.action?.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) { - return event; - } - } else if ('actions' in point) { - for (const action of point.actions) { - if (action.triggers?.some((t: any) => t.triggerUuid === triggerUuid)) { - return event; - } - } - } - } - } - return undefined; - }, - - getEventByPointUuid: (productUuid, pointUuid) => { - const product = get().getProductById(productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - if ((event as ConveyorEventSchema).points.some(p => p.uuid === pointUuid)) { - return event; - } - } else if ('point' in event) { - if ((event as any).point?.uuid === pointUuid) { - return event; - } - } - } - return undefined; - }, - - getPointByUuid: (productUuid, modelUuid, pointUuid) => { - const event = get().getEventByModelUuid(productUuid, modelUuid); - if (!event) return undefined; - - if ('points' in event) { - return (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); - } else if ('point' in event && (event as any).point.uuid === pointUuid) { - return (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point; - } - return undefined; - }, - - getActionByUuid: (productUuid, actionUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return point.action; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return point.action; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) return action; - } - } - } - return undefined; - }, - - getActionByPointUuid: (productUuid, pointUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.uuid === pointUuid) { - return point.action; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if (point.uuid === pointUuid) { - return point.action; - } - } - } - return undefined; - }, - - getModelUuidByPointUuid: (productUuid, pointUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.uuid === pointUuid) { - return event.modelUuid; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if (point.uuid === pointUuid) { - return event.modelUuid; - } - } - } - return undefined; - }, - - getModelUuidByActionUuid: (productUuid, actionUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return event.modelUuid; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return event.modelUuid; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) return event.modelUuid; - } - } - } - return undefined; - }, - - getPointUuidByActionUuid: (productUuid, actionUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.actionUuid === actionUuid) { - return point.uuid; - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.actionUuid === actionUuid) { - return point.uuid; - } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) return point.uuid; - } - } - } - return undefined; - }, - - getTriggerByUuid: (productUuid, triggerUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return undefined; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - for (const trigger of point.action?.triggers || []) { - if (trigger.triggerUuid === triggerUuid) { - return trigger; - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point) { - for (const trigger of point.action?.triggers || []) { - if (trigger.triggerUuid === triggerUuid) { - return trigger; - } - } - } else if ('actions' in point) { - for (const action of point.actions) { - for (const trigger of action.triggers || []) { - if (trigger.triggerUuid === triggerUuid) { - return trigger; - } - } - } - } - } - } - return undefined; - }, - - getTriggersByTriggeredPointUuid: (productUuid, triggeredPointUuid) => { - const product = get().products.find(p => p.productUuid === productUuid); - if (!product) return []; - - const triggers: TriggerSchema[] = []; - - for (const event of product.eventDatas) { - if ('points' in event) { - for (const point of (event as ConveyorEventSchema).points) { - if (point.action?.triggers) { - for (const trigger of point.action.triggers) { - if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { - triggers.push(trigger); - } - } - } - } - } else if ('point' in event) { - const point = (event as any).point; - if ('action' in point && point.action?.triggers) { - for (const trigger of point.action.triggers) { - if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { - triggers.push(trigger); - } - } - } else if ('actions' in point) { - for (const action of point.actions) { - if (action.triggers) { - for (const trigger of action.triggers) { - if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { - triggers.push(trigger); - } - } - } - } - } - } - } - - return triggers; - }, - - getIsEventInProduct: (productUuid, modelUuid) => { - const product = get().getProductById(productUuid); - if (!product) return false; - return product.eventDatas.some(e => 'modelUuid' in e && e.modelUuid === modelUuid); + // Event-level actions + addEvent: (productUuid, event) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const existingEvent = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === event.modelUuid + ); + if (!existingEvent) { + product.eventDatas.push(event); } - })) - ) -} + } + }); + }, -export type ProductStoreType = ReturnType; \ No newline at end of file + removeEvent: (productUuid, modelUuid) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + product.eventDatas = product.eventDatas.filter( + (e) => "modelUuid" in e && e.modelUuid !== modelUuid + ); + } + }); + }, + + deleteEvent: (modelUuid) => { + let updatedEvents: EventsSchema[] = []; + set((state) => { + const actionsToDelete = new Set(); + + for (const product of state.products) { + const eventIndex = product.eventDatas.findIndex( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (eventIndex !== -1) { + const event = product.eventDatas[eventIndex]; + + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action) { + actionsToDelete.add(point.action.actionUuid); + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action) { + actionsToDelete.add(point.action.actionUuid); + } else if ("actions" in point) { + for (const action of point.actions) { + actionsToDelete.add(action.actionUuid); + } + } + } + + product.eventDatas.splice(eventIndex, 1); + } + } + + for (const product of state.products) { + for (const event of product.eventDatas) { + let eventModified = false; + + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.triggers) { + const originalLength = point.action.triggers.length; + point.action.triggers = point.action.triggers.filter( + (trigger) => { + return !( + trigger.triggeredAsset?.triggeredModel?.modelUuid === + modelUuid || + actionsToDelete.has( + trigger.triggeredAsset?.triggeredAction + ?.actionUuid || "" + ) + ); + } + ); + if (point.action.triggers.length !== originalLength) { + eventModified = true; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.triggers) { + const originalLength = point.action.triggers.length; + point.action.triggers = point.action.triggers.filter( + (trigger: TriggerSchema) => { + return !( + trigger.triggeredAsset?.triggeredModel?.modelUuid === + modelUuid || + actionsToDelete.has( + trigger.triggeredAsset?.triggeredAction?.actionUuid || + "" + ) + ); + } + ); + if (point.action.triggers.length !== originalLength) { + eventModified = true; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if (action.triggers) { + const originalLength = action.triggers.length; + action.triggers = action.triggers.filter( + (trigger: TriggerSchema) => { + return !( + trigger.triggeredAsset?.triggeredModel + ?.modelUuid === modelUuid || + actionsToDelete.has( + trigger.triggeredAsset?.triggeredAction + ?.actionUuid || "" + ) + ); + } + ); + if (action.triggers.length !== originalLength) { + eventModified = true; + } + } + } + } + } + + if (eventModified) { + updatedEvents.push(JSON.parse(JSON.stringify(event))); + } + } + } + }); + return updatedEvents; + }, + + updateEvent: (productUuid, modelUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event) { + Object.assign(event, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + }); + return updatedEvent; + }, + + // Point-level actions + addPoint: (productUuid, modelUuid, point) => { + let updatedEvent: EventsSchema | undefined = undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + const existingPoint = (event as ConveyorEventSchema).points.find( + (p) => p.uuid === point.uuid + ); + if (!existingPoint) { + (event as ConveyorEventSchema).points.push( + point as ConveyorPointSchema + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if (event && "point" in event) { + const existingPoint = (event as any).point?.uuid === point.uuid; + if (!existingPoint) { + ( + event as + | VehicleEventSchema + | RoboticArmEventSchema + | MachineEventSchema + | StorageEventSchema + ).point = point as any; + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + }); + return updatedEvent; + }, + + removePoint: (productUuid, modelUuid, pointUuid) => { + let updatedEvent: EventsSchema | undefined = undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + (event as ConveyorEventSchema).points = ( + event as ConveyorEventSchema + ).points.filter((p) => p.uuid !== pointUuid); + updatedEvent = JSON.parse(JSON.stringify(event)); + } else if ( + event && + "point" in event && + (event as any).point.uuid === pointUuid + ) { + // For events with single point, we can't remove it, only reset to empty + } + } + }); + return updatedEvent; + }, + + updatePoint: (productUuid, modelUuid, pointUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + const point = (event as ConveyorEventSchema).points.find( + (p) => p.uuid === pointUuid + ); + if (point) { + Object.assign(point, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ( + event && + "point" in event && + (event as any).point.uuid === pointUuid + ) { + Object.assign((event as any).point, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + }); + return updatedEvent; + }, + + // Action-level actions + addAction: (productUuid, modelUuid, pointUuid, action) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + const event = product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + if (event && "points" in event) { + const point = (event as ConveyorEventSchema).points.find( + (p) => p.uuid === pointUuid + ); + if ( + point && + (!point.action || point.action.actionUuid !== action.actionUuid) + ) { + point.action = action as any; + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ( + event && + "point" in event && + (event as any).point.uuid === pointUuid + ) { + if ("action" in (event as any).point) { + if ( + !(event as any).point.action || + (event as any).point.action.actionUuid !== action.actionUuid + ) { + (event as any).point.action = action; + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ("actions" in (event as any).point) { + const existingAction = (event as any).point.actions.find( + (a: any) => a.actionUuid === action.actionUuid + ); + if (!existingAction) { + (event as any).point.actions.push(action); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + } + }); + return updatedEvent; + }, + + removeAction: (productUuid, actionUuid) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + // Handle ConveyorEventSchema + for (const point of (event as ConveyorEventSchema).points) { + } + } else if ("point" in event) { + const point = (event as any).point; + if (event.type === "roboticArm") { + if ("actions" in point) { + const index = point.actions.findIndex( + (a: any) => a.actionUuid === actionUuid + ); + if (index !== -1) { + point.actions.splice(index, 1); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if (event.type === "human") { + if ("actions" in point) { + const index = point.actions.findIndex( + (a: any) => a.actionUuid === actionUuid + ); + if (index !== -1) { + point.actions.splice(index, 1); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if ( + "action" in point && + point.action?.actionUuid === actionUuid + ) { + point.action = undefined; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + }); + return updatedEvent; + }, + + updateAction: (productUuid, actionUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + Object.assign(point.action, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ( + "action" in point && + point.action.actionUuid === actionUuid + ) { + Object.assign(point.action, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + Object.assign(action, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + }); + return updatedEvent; + }, + + // Trigger-level actions + addTrigger: (productUuid, actionUuid, trigger) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + const existingTrigger = point.action.triggers.find( + (t) => t.triggerUuid === trigger.triggerUuid + ); + if (!existingTrigger) { + point.action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + return; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ( + "action" in point && + point.action.actionUuid === actionUuid + ) { + const existingTrigger = point.action.triggers.find( + (t: any) => t.triggerUuid === trigger.triggerUuid + ); + if (!existingTrigger) { + point.action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + return; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + const existingTrigger = action.triggers.find( + (t: any) => t.triggerUuid === trigger.triggerUuid + ); + if (!existingTrigger) { + action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + return; + } + } + } + } + } + }); + return updatedEvent; + }, + + removeTrigger: (productUuid, triggerUuid) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && "triggers" in point.action) { + const Trigger = point.action.triggers.find( + (t) => t.triggerUuid === triggerUuid + ); + if (Trigger) { + point.action.triggers = point.action.triggers.filter( + (t) => t.triggerUuid !== triggerUuid + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && "triggers" in point.action) { + const Trigger = point.action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (Trigger) { + point.action.triggers = point.action.triggers.filter( + (t: any) => t.triggerUuid !== triggerUuid + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ("triggers" in action) { + const Trigger = action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (Trigger) { + action.triggers = action.triggers.filter( + (t: any) => t.triggerUuid !== triggerUuid + ); + updatedEvent = JSON.parse(JSON.stringify(event)); + } + } + } + } + } + } + } + }); + return updatedEvent; + }, + + updateTrigger: (productUuid, triggerUuid, updates) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t) => t.triggerUuid === triggerUuid + ); + if (trigger) { + Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ("triggers" in action) { + const trigger = action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + } + } + }); + return updatedEvent; + }, + + // Renaming functions + renameProduct: (productUuid, newName) => { + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + product.productName = newName; + } + }); + }, + + renameAction: (productUuid, actionUuid, newName) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + point.action.actionName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ( + "action" in point && + point.action.actionUuid === actionUuid + ) { + point.action.actionName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + action.actionName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + }); + return updatedEvent; + }, + + renameTrigger: (productUuid, triggerUuid, newName) => { + let updatedEvent: EventsSchema | undefined; + set((state) => { + const product = state.products.find( + (p) => p.productUuid === productUuid + ); + if (product) { + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t) => t.triggerUuid === triggerUuid + ); + if (trigger) { + trigger.triggerName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && "triggers" in point.action) { + const trigger = point.action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + trigger.triggerName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ("triggers" in action) { + const trigger = action.triggers.find( + (t: any) => t.triggerUuid === triggerUuid + ); + if (trigger) { + trigger.triggerName = newName; + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } + } + } + } + } + } + }); + return updatedEvent; + }, + + // Helper functions + getProductById: (productUuid) => { + // console.log( + // "j", + // get().products.map((p) => p.productUuid) + // ); + + // console.log("productUuid: ", productUuid); + return get().products.find((p) => p.productUuid === productUuid); + }, + + getEventByModelUuid: (productUuid, modelUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + return product.eventDatas.find( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + }, + + getEventByActionUuid: (productUuid, actionUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return event; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return event; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) { + return event; + } + } + } + } + return undefined; + }, + + getEventByTriggerUuid: (productUuid, triggerUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if ( + point.action?.triggers?.some( + (t) => t.triggerUuid === triggerUuid + ) + ) { + return event; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point) { + if ( + point.action?.triggers?.some( + (t: any) => t.triggerUuid === triggerUuid + ) + ) { + return event; + } + } else if ("actions" in point) { + for (const action of point.actions) { + if ( + action.triggers?.some( + (t: any) => t.triggerUuid === triggerUuid + ) + ) { + return event; + } + } + } + } + } + return undefined; + }, + + getEventByPointUuid: (productUuid, pointUuid) => { + const product = get().getProductById(productUuid); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + if ( + (event as ConveyorEventSchema).points.some( + (p) => p.uuid === pointUuid + ) + ) { + return event; + } + } else if ("point" in event) { + if ((event as any).point?.uuid === pointUuid) { + return event; + } + } + } + return undefined; + }, + + getPointByUuid: (productUuid, modelUuid, pointUuid) => { + const event = get().getEventByModelUuid(productUuid, modelUuid); + if (!event) return undefined; + + if ("points" in event) { + return (event as ConveyorEventSchema).points.find( + (p) => p.uuid === pointUuid + ); + } else if ( + "point" in event && + (event as any).point.uuid === pointUuid + ) { + return ( + event as + | VehicleEventSchema + | RoboticArmEventSchema + | MachineEventSchema + | StorageEventSchema + ).point; + } + return undefined; + }, + + getActionByUuid: (productUuid, actionUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return point.action; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return point.action; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) return action; + } + } + } + return undefined; + }, + + getActionByPointUuid: (productUuid, pointUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.uuid === pointUuid) { + return point.action; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if (point.uuid === pointUuid) { + return point.action; + } + } + } + return undefined; + }, + + getModelUuidByPointUuid: (productUuid, pointUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.uuid === pointUuid) { + return event.modelUuid; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if (point.uuid === pointUuid) { + return event.modelUuid; + } + } + } + return undefined; + }, + + getModelUuidByActionUuid: (productUuid, actionUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return event.modelUuid; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return event.modelUuid; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) return event.modelUuid; + } + } + } + return undefined; + }, + + getPointUuidByActionUuid: (productUuid, actionUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return point.uuid; + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.actionUuid === actionUuid) { + return point.uuid; + } else if ("actions" in point) { + const action = point.actions.find( + (a: any) => a.actionUuid === actionUuid + ); + if (action) return point.uuid; + } + } + } + return undefined; + }, + + getTriggerByUuid: (productUuid, triggerUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } else if ("actions" in point) { + for (const action of point.actions) { + for (const trigger of action.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } + } + } + return undefined; + }, + + getTriggersByTriggeredPointUuid: (productUuid, triggeredPointUuid) => { + const product = get().products.find( + (p) => p.productUuid === productUuid + ); + if (!product) return []; + + const triggers: TriggerSchema[] = []; + + for (const event of product.eventDatas) { + if ("points" in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.triggers) { + for (const trigger of point.action.triggers) { + if ( + trigger.triggeredAsset?.triggeredPoint?.pointUuid === + triggeredPointUuid + ) { + triggers.push(trigger); + } + } + } + } + } else if ("point" in event) { + const point = (event as any).point; + if ("action" in point && point.action?.triggers) { + for (const trigger of point.action.triggers) { + if ( + trigger.triggeredAsset?.triggeredPoint?.pointUuid === + triggeredPointUuid + ) { + triggers.push(trigger); + } + } + } else if ("actions" in point) { + for (const action of point.actions) { + if (action.triggers) { + for (const trigger of action.triggers) { + if ( + trigger.triggeredAsset?.triggeredPoint?.pointUuid === + triggeredPointUuid + ) { + triggers.push(trigger); + } + } + } + } + } + } + } + + return triggers; + }, + + getIsEventInProduct: (productUuid, modelUuid) => { + const product = get().getProductById(productUuid); + if (!product) return false; + return product.eventDatas.some( + (e) => "modelUuid" in e && e.modelUuid === modelUuid + ); + }, + })) + ); +}; + +export type ProductStoreType = ReturnType; From c9cc8d3534f9a41c11a028f4f282c5e0c46e92f9 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 4 Aug 2025 12:00:48 +0530 Subject: [PATCH 06/38] feat: Enhance material handling and storage unit interactions; refactor MaterialAnimator and useRetrieveHandler for improved state management --- .../actionHandler/useRetrieveHandler.ts | 6 +- .../instances/animator/roboticArmAnimator.tsx | 73 ++++++++++- .../instances/animator/MaterialAnimator.tsx | 121 ++++++++++-------- .../instances/storageUnitInstances.tsx | 1 - .../simulation/storageUnit/storageUnit.tsx | 1 - app/src/store/simulation/useMaterialStore.ts | 29 +++++ 6 files changed, 168 insertions(+), 63 deletions(-) diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index 0373fd0..a92ad09 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -60,9 +60,9 @@ export function useRetrieveHandler() { actionUuid: action.actionUuid }, current: { - modelUuid: action.triggers[0]?.triggeredAsset.triggeredModel.modelUuid, - pointUuid: action.triggers[0]?.triggeredAsset.triggeredPoint.pointUuid, - actionUuid: action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid + modelUuid: modelUuid, + pointUuid: pointUuid, + actionUuid: action.actionUuid }, }; diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index 1f81c2d..755e3ba 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -33,8 +33,8 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone const [customCurvePoints, setCustomCurvePoints] = useState(null); const { armBotStore, productStore, materialStore } = useSceneContext(); const { getArmBotById } = armBotStore(); - const { getMaterialById } = materialStore(); - const { getEventByModelUuid } = productStore(); + const { getMaterialById, getMaterialPosition } = materialStore(); + const { getEventByModelUuid, getActionByUuid, getPointByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { scene } = useThree(); @@ -167,13 +167,18 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone let start = currentPath[0]; let end = currentPath[currentPath.length - 1]; + const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone); const armbotStatus = getArmBotById(armBot.modelUuid); const currentMaterial = armbotStatus?.currentAction?.materialId; - if (armbotStatus && currentMaterial && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end')) { + const currentAction = getActionByUuid(selectedProduct.productUuid, armbotStatus?.currentAction?.actionUuid || ''); + + if (armbotStatus && currentMaterial && currentAction && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end' || currentPhase === 'end-to-rest')) { const materialData = getMaterialById(currentMaterial); if (materialData) { const prevModel = getEventByModelUuid(selectedProduct.productUuid, materialData.current.modelUuid); + const nextModel = getEventByModelUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || ''); + const nextPoint = getPointByUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '', currentAction?.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || ''); if (prevModel && prevModel.type === 'transfer') { const material = scene.getObjectByProperty("uuid", currentMaterial); @@ -194,7 +199,69 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; } } + } else if (prevModel && prevModel.type === 'storageUnit') { + const position = getMaterialPosition(currentMaterial); + const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid); + + if (armbotModel) { + const armbotWorldPos = new THREE.Vector3(); + + let materialWorldPos = new THREE.Vector3(); + + if (position) { + materialWorldPos.copy(position); + } else { + materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos)); + } + + const materialLocalPos = materialWorldPos.clone(); + armbotModel.worldToLocal(materialLocalPos); + + if (currentPhase === 'rest-to-start') { + end = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; + } else if (currentPhase === 'start-to-end') { + start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; + } else if (currentPhase === 'end-to-rest') { + start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; + } + } } + + if (nextModel && nextPoint && nextModel.type === 'transfer') { + const conveyorModel = scene.getObjectByProperty("uuid", nextModel.modelUuid); + const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid); + if (conveyorModel && armbotModel) { + const localPoint = new THREE.Vector3( + nextPoint.position[0], + nextPoint.position[1], + nextPoint.position[2] + ); + + const worldPoint = conveyorModel.localToWorld(localPoint); + + armbotModel.worldToLocal(worldPoint); + + if (currentPhase === 'start-to-end') { + end = [worldPoint.x, worldPoint.y + 0.35, worldPoint.z]; + } + } + } + } + + } + + if (currentPhase === 'end-to-rest') { + console.log('currentPhase: ', currentPhase); + const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid); + const armbotWorldPos = new THREE.Vector3(); + + if (armbotModel) { + let materialWorldPos = new THREE.Vector3(); + materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos)); + + const materialLocalPos = materialWorldPos.clone(); + armbotModel.worldToLocal(materialLocalPos); + start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z]; } } diff --git a/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx b/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx index 2a95262..60d3733 100644 --- a/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx +++ b/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx @@ -1,72 +1,83 @@ -import { useRef, useMemo } from "react"; -import { MaterialModel } from "../../../materials/instances/material/materialModel"; +import { useEffect, useMemo } from "react"; import { Object3D, Box3, Vector3 } from "three"; import { useThree } from "@react-three/fiber"; import { useLoadingProgress } from "../../../../../store/builder/store"; +import { MaterialModel } from "../../../materials/instances/material/materialModel"; +import { useSceneContext } from "../../../../scene/sceneContext"; const MaterialAnimator = ({ - storage, + storage, }: Readonly<{ storage: StorageUnitStatus }>) => { - const meshRef = useRef(null!); - const { scene } = useThree(); - const padding = 0.1; - const { loadingProgress } = useLoadingProgress(); + const { scene } = useThree(); + const padding = 0.1; + const { loadingProgress } = useLoadingProgress(); + const { materialStore } = useSceneContext(); + const { materialPositions, setMaterialPositions } = materialStore(); - const storageModel = useMemo(() => { - return scene.getObjectByProperty("uuid", storage.modelUuid) as Object3D; - }, [scene, storage.modelUuid, loadingProgress]); + const storageModel = useMemo(() => { + return scene.getObjectByProperty("uuid", storage.modelUuid) as Object3D; + }, [scene, storage.modelUuid, loadingProgress]); - const materialPositions = useMemo(() => { - if (!storageModel || storage.currentMaterials.length === 0) return []; + useEffect(() => { + if (!storageModel || storage.currentMaterials.length === 0) { + setMaterialPositions([]); + return; + } - const box = new Box3().setFromObject(storageModel); - const size = new Vector3(); - box.getSize(size); + const box = new Box3().setFromObject(storageModel); + const size = new Vector3(); + box.getSize(size); - const matCount = storage.currentMaterials.length; + const materialWidth = 0.45; + const materialDepth = 0.45; + const materialHeight = 0.3; + const cols = Math.floor(size.x / materialWidth); + const rows = Math.floor(size.z / materialDepth); + const itemsPerLayer = cols * rows; - // Assumed size each material needs in world units - const materialWidth = 0.45; - const materialDepth = 0.45; - const materialHeight = 0.3; + const origin = new Vector3( + box.min.x + materialWidth / 2, + box.max.y + padding, + box.min.z + materialDepth / 2 + ); - const cols = Math.floor(size.x / materialWidth); - const rows = Math.floor(size.z / materialDepth); - const itemsPerLayer = cols * rows; + const newMaterials = storage.currentMaterials.map((mat, i) => { + const layer = Math.floor(i / itemsPerLayer); + const layerIndex = i % itemsPerLayer; + const row = Math.floor(layerIndex / cols); + const col = layerIndex % cols; - const origin = new Vector3( - box.min.x + materialWidth / 2, - box.max.y + padding, // slightly above the surface - box.min.z + materialDepth / 2 + const position = new Vector3( + origin.x + col * materialWidth, + origin.y + layer * (materialHeight + padding), + origin.z + row * materialDepth + ); + + return { + materialId: mat.materialId, + position, + }; + }); + + setMaterialPositions(newMaterials); + }, [storageModel, storage.currentMaterials, setMaterialPositions]); + + return ( + + {materialPositions.map(({ materialId, position }) => { + const mat = storage.currentMaterials.find((m) => m.materialId === materialId); + return ( + + ); + })} + ); - - return Array.from({ length: matCount }, (_, i) => { - const layer = Math.floor(i / itemsPerLayer); - const layerIndex = i % itemsPerLayer; - const row = Math.floor(layerIndex / cols); - const col = layerIndex % cols; - - return new Vector3( - origin.x + col * materialWidth, - origin.y + layer * (materialHeight + padding), - origin.z + row * materialDepth - ); - }); - }, [storageModel, storage.currentMaterials]); - - return ( - - {storage.currentMaterials.map((mat, index) => ( - - ))} - - ); }; export default MaterialAnimator; diff --git a/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx b/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx index d9faf29..cebc4f9 100644 --- a/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx +++ b/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx @@ -7,7 +7,6 @@ import { useViewSceneStore } from "../../../../store/builder/store"; function StorageUnitInstances() { const { storageUnitStore } = useSceneContext(); const { storageUnits } = storageUnitStore(); - // console.log('storageUnits: ', storageUnits); const { viewSceneLabels } = useViewSceneStore(); return ( diff --git a/app/src/modules/simulation/storageUnit/storageUnit.tsx b/app/src/modules/simulation/storageUnit/storageUnit.tsx index eee0875..8d7f1b1 100644 --- a/app/src/modules/simulation/storageUnit/storageUnit.tsx +++ b/app/src/modules/simulation/storageUnit/storageUnit.tsx @@ -1,4 +1,3 @@ -import React from 'react' import StorageUnitInstances from './instances/storageUnitInstances' function StorageUnit() { diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts index 98a0928..777bad0 100644 --- a/app/src/store/simulation/useMaterialStore.ts +++ b/app/src/store/simulation/useMaterialStore.ts @@ -1,15 +1,26 @@ +import * as THREE from 'three'; import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; + +interface MaterialPosition { + materialId: string; + position: THREE.Vector3; +} + type MaterialsStore = { materials: MaterialsSchema; materialHistory: MaterialHistorySchema; + materialPositions: MaterialPosition[]; addMaterial: (material: MaterialSchema) => MaterialSchema | undefined; removeMaterial: (materialId: string) => MaterialSchema | undefined; clearMaterials: () => void; updateMaterial: (materialId: string, updates: Partial) => MaterialSchema | undefined; + setMaterialPositions: (materialPosition: MaterialPosition[]) => void; + getMaterialPosition: (materialId: string) => THREE.Vector3 | undefined; + setPreviousLocation: ( materialId: string, location: { @@ -61,6 +72,7 @@ export const createMaterialStore = () => { immer((set, get) => ({ materials: [], materialHistory: [], + materialPositions: [], addMaterial: (material) => { let updatedMaterial: MaterialSchema | undefined; @@ -262,6 +274,23 @@ export const createMaterialStore = () => { return updatedMaterial; }, + setMaterialPositions: (materials) => { + set((state) => { + state.materialPositions = materials; + }); + }, + + getMaterialPosition: (materialid) => { + let position: THREE.Vector3 | undefined; + set((state) => { + const material = state.materialPositions.find(m => m.materialId === materialid); + if (material) { + position = material.position; + } + }); + return position; + }, + getMaterialById: (materialId) => { return get().materials.find(m => m.materialId === materialId); }, From c9765332978db1d4e388341d0dc89f9be1a6c863 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 4 Aug 2025 15:16:35 +0530 Subject: [PATCH 07/38] feat: Refactor HumanUi and VehicleUI components; introduce MarkerPrimitive for improved marker handling and interaction --- .../human/instances/instance/humanUi.tsx | 175 +++++++++++++----- .../spatialUI/vehicle/vehicleUI.tsx | 136 +++++++++++--- 2 files changed, 236 insertions(+), 75 deletions(-) diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx index 17335b1..64c01ef 100644 --- a/app/src/modules/simulation/human/instances/instance/humanUi.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -1,5 +1,6 @@ -import { useEffect, useRef, useState } from 'react' -import { useGLTF } from '@react-three/drei'; +import { useEffect, useMemo, useRef, useState } from 'react' +import * as THREE from 'three' +import { Line, useGLTF } from '@react-three/drei'; import { useFrame, useThree } from '@react-three/fiber'; import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore'; import { useProductContext } from '../../../products/productContext'; @@ -27,7 +28,7 @@ function HumanUi() { const { humanStore, productStore } = useSceneContext(); const { selectedProduct } = selectedProductStore(); const { humans, getHumanById } = humanStore(); - const { updateEvent, updateAction, getActionByUuid } = productStore(); + const { updateEvent, getActionByUuid } = productStore(); const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]); const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]); const [assemblyPosition, setAssemblyPosition] = useState<[number, number, number]>([0, 1, 0]); @@ -313,64 +314,49 @@ function HumanUi() { rotation={[0, Math.PI, 0]} > {isAssembly ? ( - { - if (e.object.parent.name === "handle") { - handlePointerDown(e, "assembly", "assembly"); - } else { - handlePointerDown(e, "assembly", "assembly"); - } - }} - onPointerMissed={() => { - setIsDragging(null); - setIsRotating(null); - if (controls) (controls as any).enabled = true; - }} + outerGroupRef={outerGroup} + type="assembly" + subtype="assembly" + color="#0f87f7" + setIsDragging={setIsDragging} + setIsRotating={setIsRotating} + handlePointerDown={handlePointerDown} /> ) : ( <> - { - if (e.object.parent.name === "handle") { - handlePointerDown(e, "start", "start"); - } else { - handlePointerDown(e, "start", "start"); - } - }} - onPointerMissed={() => { - setIsDragging(null); - setIsRotating(null); - if (controls) (controls as any).enabled = true; - }} + outerGroupRef={outerGroup} + type="start" + subtype="start" + color="green" + setIsDragging={setIsDragging} + setIsRotating={setIsRotating} + handlePointerDown={handlePointerDown} /> - - { - if (e.object.parent.name === "handle") { - handlePointerDown(e, "end", "end"); - } else { - handlePointerDown(e, "end", "end"); - } - }} - onPointerMissed={() => { - setIsDragging(null); - setIsRotating(null); - if (controls) (controls as any).enabled = true; - }} + outerGroupRef={outerGroup} + type="end" + subtype="end" + color="orange" + setIsDragging={setIsDragging} + setIsRotating={setIsRotating} + handlePointerDown={handlePointerDown} /> )} @@ -380,4 +366,99 @@ function HumanUi() { ); } -export default HumanUi; \ No newline at end of file +export default HumanUi; + +const MarkerPrimitive = ({ + name, + refProp, + object, + position, + rotation, + outerGroupRef, + type, + subtype, + color, + setIsDragging, + setIsRotating, + handlePointerDown, +}: { + name: string; + refProp: any; + object: THREE.Object3D; + position: [number, number, number]; + rotation: [number, number, number]; + outerGroupRef: React.RefObject; + type: string; + subtype: string; + color: string; + setIsDragging: (val: any) => void; + setIsRotating: (val: any) => void; + handlePointerDown: any; +}) => { + const { controls, scene } = useThree(); + const [hitPoint, setHitPoint] = useState(null); + + const lineRef = useRef(null); + + useFrame(() => { + if (!refProp.current || !outerGroupRef.current) return; + + const worldPos = new THREE.Vector3(); + refProp.current.getWorldPosition(worldPos); + const localMarkerPos = outerGroupRef.current.worldToLocal(worldPos.clone()); + + const rayOrigin = worldPos.clone(); + const direction = new THREE.Vector3(0, -1, 0); + const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000); + const intersects = raycaster.intersectObjects(scene.children, true); + + const hit = intersects.find(i => i.object.name !== name); + + if (hit) { + const localHit = outerGroupRef.current.worldToLocal(hit.point.clone()); + setHitPoint(localHit); + + if (lineRef.current) { + const positions = new Float32Array([localMarkerPos.x, localMarkerPos.y, localMarkerPos.z, localHit.x, localHit.y, localHit.z]); + lineRef.current.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + lineRef.current.attributes.position.needsUpdate = true; + } + } else { + setHitPoint(null); + } + }); + + return ( + <> + { + handlePointerDown(e, type, subtype); + }} + onPointerMissed={() => { + setIsDragging(null); + setIsRotating(null); + if (controls) (controls as any).enabled = true; + }} + /> + + {hitPoint && ( + <> + + + + + + + + + + + )} + + ); +}; diff --git a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx index 9bd36b5..7e59da1 100644 --- a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx +++ b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from "react"; import * as Types from "../../../../types/world/worldTypes"; import { useGLTF } from "@react-three/drei"; +import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { useSelectedEventSphere, useIsDragging, useIsRotating, } from "../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; @@ -342,41 +343,120 @@ const VehicleUI = () => { {/* Start Marker */} - { - e.stopPropagation(); - handlePointerDown(e, "start", "start"); - }} + outerGroupRef={outerGroup} + color="green" + handlePointerDown={handlePointerDown} + setIsDragging={setIsDragging} + setIsRotating={setIsRotating} + /> + + {/* End Marker */} + + + ) : null; +}; + +export default VehicleUI; + +export const VehicleMarkerPrimitive = ({ + name, + object, + position, + rotation, + outerGroupRef, + color, + handlePointerDown, + setIsDragging, + setIsRotating, +}: { + name: string; + object: THREE.Object3D; + position: [number, number, number]; + rotation: [number, number, number]; + outerGroupRef: React.RefObject; + color: string; + handlePointerDown: (e: any, type: "start" | "end", rotation: "start" | "end") => void; + setIsDragging: (val: any) => void; + setIsRotating: (val: any) => void; +}) => { + const { scene } = useThree(); + const markerRef = useRef(null); + const lineRef = useRef(null); + const [hitPoint, setHitPoint] = useState(null); + + useFrame(() => { + if (!markerRef.current || !outerGroupRef.current) return; + + const worldPos = new THREE.Vector3(); + markerRef.current.getWorldPosition(worldPos); + const localMarkerPos = outerGroupRef.current.worldToLocal(worldPos.clone()); + + const rayOrigin = worldPos.clone(); + const direction = new THREE.Vector3(0, -1, 0); + const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000); + const intersects = raycaster.intersectObjects(scene.children, true); + + const hit = intersects.find(i => i.object.name !== name); + if (hit) { + const localHit = outerGroupRef.current.worldToLocal(hit.point.clone()); + setHitPoint(localHit); + + if (lineRef.current) { + const positions = new Float32Array([ + localMarkerPos.x, localMarkerPos.y, localMarkerPos.z, + localHit.x, localHit.y, localHit.z + ]); + lineRef.current.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + lineRef.current.attributes.position.needsUpdate = true; + } + } else { + setHitPoint(null); + } + }); + + return ( + <> + handlePointerDown(e, name === "startMarker" ? "start" : "end", name === "startMarker" ? "start" : "end")} onPointerMissed={() => { - controls.enabled = true; setIsDragging(null); setIsRotating(null); }} /> - {/* End Marker */} - { - e.stopPropagation(); - handlePointerDown(e, "end", "end"); - }} - onPointerMissed={() => { - controls.enabled = true; - setIsDragging(null); - setIsRotating(null); - }} - /> - - ) : null; -}; -export default VehicleUI; + {hitPoint && ( + <> + + + + + + + + + + + )} + + ); +}; \ No newline at end of file From 581069d861291c259de570add072d2ed557fc0b6 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 4 Aug 2025 15:20:11 +0530 Subject: [PATCH 08/38] feat: Update marker colors in HumanUi for improved visibility --- .../modules/simulation/human/instances/instance/humanUi.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx index 64c01ef..1fcdfeb 100644 --- a/app/src/modules/simulation/human/instances/instance/humanUi.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -339,7 +339,7 @@ function HumanUi() { outerGroupRef={outerGroup} type="start" subtype="start" - color="green" + color="lightgreen" setIsDragging={setIsDragging} setIsRotating={setIsRotating} handlePointerDown={handlePointerDown} @@ -353,7 +353,7 @@ function HumanUi() { outerGroupRef={outerGroup} type="end" subtype="end" - color="orange" + color="darkorange" setIsDragging={setIsDragging} setIsRotating={setIsRotating} handlePointerDown={handlePointerDown} From c03e524b99945cd80af4b81105a93837ded094d9 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 4 Aug 2025 18:06:22 +0530 Subject: [PATCH 09/38] bug fix in ik and code optimization --- .../asset/functions/assetBoundingBox.tsx | 28 ++++++++++++++++--- .../builder/asset/models/model/model.tsx | 6 +++- .../instances/instance/materialInstance.tsx | 22 ++------------- .../materials/instances/materialInstances.tsx | 2 +- .../simulation/spatialUI/arm/armBotUI.tsx | 11 +++++--- 5 files changed, 39 insertions(+), 30 deletions(-) diff --git a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx index da08edf..7736e29 100644 --- a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx +++ b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx @@ -3,8 +3,15 @@ import { useMemo } from "react"; import { Cylinder } from "@react-three/drei"; export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { - const { edgeCylinders, center, size } = useMemo(() => { - if (!boundingBox) return { edgeCylinders: [], center: new Vector3(), size: new Vector3() }; + const { edgeCylinders, cornerSpheres, center, size } = useMemo(() => { + if (!boundingBox) { + return { + edgeCylinders: [], + cornerSpheres: [], + center: new Vector3(), + size: new Vector3(), + }; + } const min = boundingBox.min; const max = boundingBox.max; @@ -50,7 +57,13 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam }; }); - return { edgeCylinders, center, size }; + const cornerSpheres = corners.map((corner, i) => ({ + key: `corner-sphere-${i}`, + position: corner.clone(), + radius: radius * 1.5, + })); + + return { edgeCylinders, cornerSpheres, center, size }; }, [boundingBox, lineWidth]); if (!boundingBox) return null; @@ -58,11 +71,18 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam return ( {edgeCylinders.map(({ key, position, rotation, length, radius }) => ( - + ))} + {cornerSpheres.map(({ key, position, radius }) => ( + + + + + ))} + diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 16f37ee..96076f9 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -449,7 +449,11 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere ) : ( - + <> + {!isSelected && + + } + )} {isSelected && diff --git a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx index 72cd5fe..318759a 100644 --- a/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx +++ b/app/src/modules/simulation/materials/instances/instance/materialInstance.tsx @@ -61,30 +61,12 @@ function MaterialInstance({ material }: { readonly material: MaterialSchema }) { function getCurrentSpeed(productUuid: string, modelUuid: string) { const event = getEventByModelUuid(productUuid, modelUuid) if (event) { - if (event.type === 'transfer') { - return event.speed; - } - - if (event.type === 'vehicle') { - return event.speed; - } - - if (event.type === 'machine') { + if (event.type === 'transfer' || event.type === 'machine' || event.type === 'storageUnit') { return 1; } - - if (event.type === 'roboticArm') { + if (event.type === 'vehicle' || event.type === 'roboticArm' || event.type === 'human') { return event.speed; } - - if (event.type === 'storageUnit') { - return 1; - } - - if (event.type === 'human') { - return event.speed; - } - } else { return 1; } diff --git a/app/src/modules/simulation/materials/instances/materialInstances.tsx b/app/src/modules/simulation/materials/instances/materialInstances.tsx index 88b127f..35bdaef 100644 --- a/app/src/modules/simulation/materials/instances/materialInstances.tsx +++ b/app/src/modules/simulation/materials/instances/materialInstances.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import { useEffect } from 'react' import MaterialInstance from './instance/materialInstance' import { useSceneContext } from '../../../scene/sceneContext'; diff --git a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx index 1f4dee8..dfd500a 100644 --- a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx +++ b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx @@ -173,13 +173,16 @@ const ArmBotUI = () => { const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || ''); + const iks = targetMesh?.userData?.iks; + const firstIK = Array.isArray(iks) && iks.length > 0 ? iks[0] : {}; + 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, + minDistance: firstIK.minDistance ?? 1.2, + maxDistance: firstIK.maxDistance ?? 2, + maxheight: firstIK.maxheight ?? 0.6, + minheight: firstIK.minheight ?? 1.9, } ); From a4bb4a4bf62a021cea2312a8611eb387634b29c1 Mon Sep 17 00:00:00 2001 From: Poovizhi Date: Tue, 5 Aug 2025 11:31:26 +0530 Subject: [PATCH 10/38] created new id for duplicate projects --- .../components/Dashboard/DashboardHome.tsx | 4 +++- .../Dashboard/DashboardProjects.tsx | 19 ++++++++++--------- app/src/components/ui/FileMenu.tsx | 1 + app/src/pages/Project.tsx | 2 -- .../services/dashboard/duplicateProject.ts | 12 ++++++------ 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/components/Dashboard/DashboardHome.tsx b/app/src/components/Dashboard/DashboardHome.tsx index 9e05994..0569c08 100644 --- a/app/src/components/Dashboard/DashboardHome.tsx +++ b/app/src/components/Dashboard/DashboardHome.tsx @@ -8,6 +8,7 @@ import { recentlyViewed } from "../../services/dashboard/recentlyViewed"; import { searchProject } from "../../services/dashboard/searchProjects"; import { deleteProject } from "../../services/dashboard/deleteProject"; import ProjectSocketRes from "./socket/projectSocketRes.dev"; +import { generateUniqueId } from "../../functions/generateUniqueId"; interface Project { _id: string; @@ -101,7 +102,8 @@ const DashboardHome: React.FC = () => { userId, thumbnail, organization, - projectUuid: projectId, + projectUuid: generateUniqueId(), + refProjectID: projectId, projectName, }; projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData); diff --git a/app/src/components/Dashboard/DashboardProjects.tsx b/app/src/components/Dashboard/DashboardProjects.tsx index 51888d0..ba2db7b 100644 --- a/app/src/components/Dashboard/DashboardProjects.tsx +++ b/app/src/components/Dashboard/DashboardProjects.tsx @@ -103,22 +103,23 @@ const DashboardProjects: React.FC = () => { projectName: string, thumbnail: string ) => { - const duplicatedProject = await duplicateProject( - projectId, - generateUniqueId(), - thumbnail, - projectName - ); - console.log("duplicatedProject: ", duplicatedProject); + // const duplicatedProject = await duplicateProject( + // projectId, + // generateUniqueId(), + // thumbnail, + // projectName + // ); + // console.log("duplicatedProject: ", duplicatedProject); const duplicateProjectData = { userId, thumbnail, organization, - projectUuid: projectId, + projectUuid: generateUniqueId(), + refProjectID: projectId, projectName, }; - // projectSocket.emit("v1:project:Duplicate", duplicateProjectData); + projectSocket.emit("v1:project:Duplicate", duplicateProjectData); }; const renderProjects = () => { diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 0f0aabe..843af5d 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -74,6 +74,7 @@ const FileMenu: React.FC = () => { // } //API for projects rename + const updatedProjectName = await updateProject( projectId, userId, diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 2ee6b29..62a5759 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -34,7 +34,6 @@ const Project: React.FC = () => { const { setOrganization } = useOrganization(); const { projectId } = useParams(); const { projectName, setProjectName } = useProjectName(); - console.log("projectName: ", projectName); const { userId, email, organization, userName } = getUserData(); const { selectedUser } = useSelectedUserStore(); const { isLogListVisible } = useLogger(); @@ -50,7 +49,6 @@ const Project: React.FC = () => { const fetchProjects = async () => { try { const projects = await getAllProjects(userId, organization); - console.log("projects: ", projects); const shared = await sharedWithMeProjects(); const allProjects = [...(projects?.Projects || []), ...(shared || [])]; diff --git a/app/src/services/dashboard/duplicateProject.ts b/app/src/services/dashboard/duplicateProject.ts index 68fe5c6..ca341de 100644 --- a/app/src/services/dashboard/duplicateProject.ts +++ b/app/src/services/dashboard/duplicateProject.ts @@ -27,22 +27,22 @@ export const duplicateProject = async ( ); const newAccessToken = response.headers.get("x-access-token"); if (newAccessToken) { - //console.log("New token received:", newAccessToken); + // localStorage.setItem("token", newAccessToken); } - console.log("response: ", response); + if (!response.ok) { - console.error("Failed to add project"); + } const result = await response.json(); - // console.log("result: ", result); + // return result; } catch (error) { if (error instanceof Error) { - console.log(error.message); + } else { - console.log("An unknown error occurred"); + } } }; From dead5ea571d462e2f9bd0ca749d88c6b08b0ba63 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Tue, 5 Aug 2025 11:35:18 +0530 Subject: [PATCH 11/38] refactor: updated transformControls with Y-axis --- .../scene/controls/transformControls/transformControls.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/modules/scene/controls/transformControls/transformControls.tsx b/app/src/modules/scene/controls/transformControls/transformControls.tsx index e0ce1cf..964588b 100644 --- a/app/src/modules/scene/controls/transformControls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls/transformControls.tsx @@ -187,7 +187,7 @@ export default function TransformControl() { {(selectedFloorItem && transformMode) && Date: Tue, 5 Aug 2025 13:10:36 +0530 Subject: [PATCH 12/38] bug fix human --- .../actionHandler/useRetrieveHandler.ts | 29 ++++- .../triggerConnections/triggerConnector.tsx | 24 ++-- .../human/instances/instance/humanUi.tsx | 36 +++--- .../instances/animator/roboticArmAnimator.tsx | 2 +- .../spatialUI/vehicle/vehicleUI.tsx | 83 +++++++------- .../instances/animator/MaterialAnimator.tsx | 9 +- .../triggerHandler/useTriggerHandler.ts | 108 +++++++++++------- app/src/store/simulation/useMaterialStore.ts | 26 ++--- 8 files changed, 183 insertions(+), 134 deletions(-) diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index a92ad09..ebe01ff 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -6,12 +6,13 @@ import { useProductContext } from "../../../products/productContext"; import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager"; export function useRetrieveHandler() { - const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext(); + const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, 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 { getConveyorById } = conveyorStore(); const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore(); const { getAssetById, setCurrentAnimation } = assetStore(); const { selectedProduct } = selectedProductStore(); @@ -371,6 +372,32 @@ export function useRetrieveHandler() { } return; } + } else if (triggeredModel?.type === 'transfer') { + const model = getConveyorById(triggeredModel.modelUuid); + if (model && !model.isPaused) { + if (humanAsset?.animationState?.current === 'idle') { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } else if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) { + const lastMaterial = getLastMaterial(storageUnit.modelUuid); + if (lastMaterial) { + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + incrementHumanLoad(human.modelUuid, 1); + addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId); + retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`); + + retrievalCountRef.current.set(actionUuid, currentCount + 1); + } + } + } + return; + } } else if (triggeredModel?.type === 'machine') { const machine = getMachineById(triggeredModel.modelUuid); if (machine && !machine.isActive && machine.state === 'idle' && !machine.currentAction) { diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 236ab55..1469d7f 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -182,31 +182,23 @@ function TriggerConnector() { const canvasElement = gl.domElement; let drag = false; - let isRightMouseDown = false; + let isLeftMouseDown = false; const onMouseDown = (evt: MouseEvent) => { if (selectedAsset) { clearSelectedAsset(); } - if (evt.button === 2) { - isRightMouseDown = true; + if (evt.button === 0) { + isLeftMouseDown = true; drag = false; } }; const onMouseUp = (evt: MouseEvent) => { - if (evt.button === 2) { - isRightMouseDown = false; + if (evt.button === 0) { + isLeftMouseDown = false; } - } - const onMouseMove = () => { - if (isRightMouseDown) { - drag = true; - } - }; - - const handleRightClick = (evt: MouseEvent) => { if (drag) return; evt.preventDefault(); @@ -368,13 +360,16 @@ function TriggerConnector() { } else if (firstSelectedPoint) { setFirstSelectedPoint(null); } + } + + const onMouseMove = () => { + drag = true; }; if (subModule === 'mechanics' && toolMode === 'cursor' && selectedAction.actionId && selectedAction.actionName) { canvasElement.addEventListener("mousedown", onMouseDown); canvasElement.addEventListener("mouseup", onMouseUp); canvasElement.addEventListener("mousemove", onMouseMove); - canvasElement.addEventListener('contextmenu', handleRightClick); } else { setFirstSelectedPoint(null); } @@ -383,7 +378,6 @@ function TriggerConnector() { canvasElement.removeEventListener("mousedown", onMouseDown); canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener('contextmenu', handleRightClick); }; }, [gl, subModule, selectedProduct, firstSelectedPoint, toolMode, selectedAction]); diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx index 1fcdfeb..043811a 100644 --- a/app/src/modules/simulation/human/instances/instance/humanUi.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -1,6 +1,6 @@ -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import * as THREE from 'three' -import { Line, useGLTF } from '@react-three/drei'; +import { Tube, useGLTF } from '@react-three/drei'; import { useFrame, useThree } from '@react-three/fiber'; import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore'; import { useProductContext } from '../../../products/productContext'; @@ -395,13 +395,12 @@ const MarkerPrimitive = ({ setIsRotating: (val: any) => void; handlePointerDown: any; }) => { - const { controls, scene } = useThree(); + const { controls, scene, camera } = useThree(); const [hitPoint, setHitPoint] = useState(null); - - const lineRef = useRef(null); + const [curve, setCurve] = useState(null); useFrame(() => { - if (!refProp.current || !outerGroupRef.current) return; + if (!refProp.current || !outerGroupRef.current || !scene) return; const worldPos = new THREE.Vector3(); refProp.current.getWorldPosition(worldPos); @@ -410,6 +409,7 @@ const MarkerPrimitive = ({ const rayOrigin = worldPos.clone(); const direction = new THREE.Vector3(0, -1, 0); const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000); + raycaster.camera = camera; const intersects = raycaster.intersectObjects(scene.children, true); const hit = intersects.find(i => i.object.name !== name); @@ -418,13 +418,14 @@ const MarkerPrimitive = ({ const localHit = outerGroupRef.current.worldToLocal(hit.point.clone()); setHitPoint(localHit); - if (lineRef.current) { - const positions = new Float32Array([localMarkerPos.x, localMarkerPos.y, localMarkerPos.z, localHit.x, localHit.y, localHit.z]); - lineRef.current.setAttribute('position', new THREE.BufferAttribute(positions, 3)); - lineRef.current.attributes.position.needsUpdate = true; - } + const newCurve = new THREE.CatmullRomCurve3([ + localMarkerPos.clone(), + localHit.clone(), + ]); + setCurve(newCurve); } else { setHitPoint(null); + setCurve(null); } }); @@ -450,15 +451,16 @@ const MarkerPrimitive = ({ <> - + - - - - + {curve && ( + + + + )} )} ); -}; +}; \ No newline at end of file diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index 755e3ba..be3a452 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -200,7 +200,7 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone } } } else if (prevModel && prevModel.type === 'storageUnit') { - const position = getMaterialPosition(currentMaterial); + const position = getMaterialPosition(prevModel.modelUuid, currentMaterial); const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid); if (armbotModel) { diff --git a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx index 7e59da1..e287b8e 100644 --- a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx +++ b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import * as Types from "../../../../types/world/worldTypes"; -import { useGLTF } from "@react-three/drei"; +import { Tube, useGLTF } from "@react-three/drei"; import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { useSelectedEventSphere, useIsDragging, useIsRotating, } from "../../../../store/simulation/useSimulationStore"; @@ -17,8 +17,6 @@ import { useVersionContext } from "../../../builder/version/versionContext"; const VehicleUI = () => { const { scene: startScene } = useGLTF(startPoint) as any; const { scene: endScene } = useGLTF(startEnd) as any; - const startMarker = useRef(null); - const endMarker = useRef(null); const prevMousePos = useRef({ x: 0, y: 0 }); const { selectedEventSphere } = useSelectedEventSphere(); const { selectedProductStore } = useProductContext(); @@ -163,7 +161,7 @@ const VehicleUI = () => { let globalStartPosition = null; let globalEndPosition = null; - if (outerGroup.current && startMarker.current && endMarker.current) { + if (outerGroup.current) { const worldPosStart = new Vector3(...startPosition); globalStartPosition = outerGroup.current.localToWorld( worldPosStart.clone() @@ -278,24 +276,19 @@ const VehicleUI = () => { const currentPointerX = state.pointer.x; const deltaX = currentPointerX - prevMousePos.current.x; prevMousePos.current.x = currentPointerX; - const marker = - isRotating === "start" ? startMarker.current : endMarker.current; - if (marker) { - const rotationSpeed = 10; - marker.rotation.y += deltaX * rotationSpeed; - if (isRotating === "start") { - setStartRotation([ - marker.rotation.x, - marker.rotation.y, - marker.rotation.z, - ]); - } else { - setEndRotation([ - marker.rotation.x, - marker.rotation.y, - marker.rotation.z, - ]); - } + const rotationSpeed = 10; + if (isRotating === "start") { + setStartRotation([ + startRotation[0], + startRotation[1] + deltaX * rotationSpeed, + startRotation[2], + ]); + } else { + setEndRotation([ + endRotation[0], + endRotation[1] + deltaX * rotationSpeed, + endRotation[2], + ]); } }); @@ -362,7 +355,7 @@ const VehicleUI = () => { position={endPosition} rotation={endRotation} outerGroupRef={outerGroup} - color="red" + color="orange" handlePointerDown={handlePointerDown} setIsDragging={setIsDragging} setIsRotating={setIsRotating} @@ -394,10 +387,10 @@ export const VehicleMarkerPrimitive = ({ setIsDragging: (val: any) => void; setIsRotating: (val: any) => void; }) => { - const { scene } = useThree(); + const { scene, camera } = useThree(); const markerRef = useRef(null); - const lineRef = useRef(null); const [hitPoint, setHitPoint] = useState(null); + const [curve, setCurve] = useState(null); useFrame(() => { if (!markerRef.current || !outerGroupRef.current) return; @@ -409,6 +402,7 @@ export const VehicleMarkerPrimitive = ({ const rayOrigin = worldPos.clone(); const direction = new THREE.Vector3(0, -1, 0); const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000); + raycaster.camera = camera; const intersects = raycaster.intersectObjects(scene.children, true); const hit = intersects.find(i => i.object.name !== name); @@ -416,16 +410,14 @@ export const VehicleMarkerPrimitive = ({ const localHit = outerGroupRef.current.worldToLocal(hit.point.clone()); setHitPoint(localHit); - if (lineRef.current) { - const positions = new Float32Array([ - localMarkerPos.x, localMarkerPos.y, localMarkerPos.z, - localHit.x, localHit.y, localHit.z - ]); - lineRef.current.setAttribute('position', new THREE.BufferAttribute(positions, 3)); - lineRef.current.attributes.position.needsUpdate = true; - } + const newCurve = new THREE.CatmullRomCurve3([ + localMarkerPos.clone(), + localHit.clone(), + ]); + setCurve(newCurve); } else { setHitPoint(null); + setCurve(null); } }); @@ -437,7 +429,13 @@ export const VehicleMarkerPrimitive = ({ object={object} position={position} rotation={rotation} - onPointerDown={(e: any) => handlePointerDown(e, name === "startMarker" ? "start" : "end", name === "startMarker" ? "start" : "end")} + onPointerDown={(e: any) => + handlePointerDown( + e, + name === "startMarker" ? "start" : "end", + name === "startMarker" ? "start" : "end" + ) + } onPointerMissed={() => { setIsDragging(null); setIsRotating(null); @@ -446,15 +444,20 @@ export const VehicleMarkerPrimitive = ({ {hitPoint && ( <> - + - + - - - - + {curve && ( + + + + )} )} diff --git a/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx b/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx index 60d3733..87ffd32 100644 --- a/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx +++ b/app/src/modules/simulation/storageUnit/instances/animator/MaterialAnimator.tsx @@ -20,7 +20,7 @@ const MaterialAnimator = ({ useEffect(() => { if (!storageModel || storage.currentMaterials.length === 0) { - setMaterialPositions([]); + setMaterialPositions(storage.modelUuid, []); return; } @@ -59,12 +59,12 @@ const MaterialAnimator = ({ }; }); - setMaterialPositions(newMaterials); - }, [storageModel, storage.currentMaterials, setMaterialPositions]); + setMaterialPositions(storage.modelUuid, newMaterials); + }, [storageModel, storage.currentMaterials]); return ( - {materialPositions.map(({ materialId, position }) => { + {(materialPositions[storage.modelUuid] || []).map(({ materialId, position }) => { const mat = storage.currentMaterials.find((m) => m.materialId === materialId); return ( ); + }; export default MaterialAnimator; diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 04e7fa7..b98a527 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -370,40 +370,18 @@ export function useTriggerHandler() { } } } else { - if (human.isActive === false && human.state === 'idle') { - - // Handle current action from arm bot - setIsPaused(materialId, true); - handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setHumanScheduled(human.modelUuid, true); - setIsPaused(materialId, true); - addHumanToMonitor(human.modelUuid, () => { - handleAction(action, materialId) - }, action.actionUuid); - - } - } - } else { - if (human.isActive === false && human.state === 'idle') { - - // Handle current action from arm bot - setIsPaused(materialId, true); setHumanScheduled(human.modelUuid, true); - handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); addHumanToMonitor(human.modelUuid, () => { handleAction(action, materialId) }, action.actionUuid); } + } else { + setHumanScheduled(human.modelUuid, true); + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, () => { + handleAction(action, materialId) + }, action.actionUuid); } } } @@ -1260,12 +1238,6 @@ export function useTriggerHandler() { actionUuid: material.current.actionUuid, }) - setCurrentLocation(material.materialId, { - modelUuid: material.current.modelUuid, - pointUuid: material.current.pointUuid, - actionUuid: material.current.actionUuid, - }); - setIsPaused(material.materialId, true); setIsVisible(material.materialId, true); @@ -1279,6 +1251,13 @@ export function useTriggerHandler() { if (model?.type === 'roboticArm') { addArmBotToMonitor(model.modelUuid, () => { + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }); + setNextLocation(material.materialId, { modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', @@ -1288,6 +1267,13 @@ export function useTriggerHandler() { }) } else if (model?.type === 'vehicle') { addVehicleToMonitor(model.modelUuid, () => { + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }); + setNextLocation(material.materialId, { modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', @@ -1296,6 +1282,13 @@ export function useTriggerHandler() { setIsPaused(material.materialId, false); }) } else if (model?.type === 'transfer') { + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }); + setNextLocation(material.materialId, { modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', @@ -1303,15 +1296,48 @@ export function useTriggerHandler() { 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 || '', - }) - + if (fromEvent.modelUuid === model.modelUuid) { setIsPaused(material.materialId, false); - }, action.actionUuid) + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }); + + setTimeout(() => { + setIsPaused(material.materialId, false); + setNextLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', + }) + }, 0) + + } else { + addHumanToMonitor(model.modelUuid, () => { + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }); + + setNextLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', + }) + + setIsPaused(material.materialId, false); + }, action.actionUuid) + } } else { + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }); + setNextLocation(material.materialId, { modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '', pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '', diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts index 777bad0..68839a7 100644 --- a/app/src/store/simulation/useMaterialStore.ts +++ b/app/src/store/simulation/useMaterialStore.ts @@ -8,18 +8,21 @@ interface MaterialPosition { position: THREE.Vector3; } +type MaterialPositionsMap = Record; + type MaterialsStore = { materials: MaterialsSchema; materialHistory: MaterialHistorySchema; - materialPositions: MaterialPosition[]; + materialPositions: MaterialPositionsMap; + addMaterial: (material: MaterialSchema) => MaterialSchema | undefined; removeMaterial: (materialId: string) => MaterialSchema | undefined; clearMaterials: () => void; updateMaterial: (materialId: string, updates: Partial) => MaterialSchema | undefined; - setMaterialPositions: (materialPosition: MaterialPosition[]) => void; - getMaterialPosition: (materialId: string) => THREE.Vector3 | undefined; + setMaterialPositions: (modelUuid: string, materialPositions: MaterialPosition[]) => void; + getMaterialPosition: (modelUuid: string, materialId: string) => THREE.Vector3 | undefined; setPreviousLocation: ( materialId: string, @@ -72,7 +75,7 @@ export const createMaterialStore = () => { immer((set, get) => ({ materials: [], materialHistory: [], - materialPositions: [], + materialPositions: {}, addMaterial: (material) => { let updatedMaterial: MaterialSchema | undefined; @@ -274,21 +277,14 @@ export const createMaterialStore = () => { return updatedMaterial; }, - setMaterialPositions: (materials) => { + setMaterialPositions: (modelUuid, positions) => { set((state) => { - state.materialPositions = materials; + state.materialPositions[modelUuid] = positions; }); }, - getMaterialPosition: (materialid) => { - let position: THREE.Vector3 | undefined; - set((state) => { - const material = state.materialPositions.find(m => m.materialId === materialid); - if (material) { - position = material.position; - } - }); - return position; + getMaterialPosition: (modelUuid, materialId) => { + return get().materialPositions[modelUuid]?.find(p => p.materialId === materialId)?.position; }, getMaterialById: (materialId) => { From 93b1fa8b332401c21293c99debf503b4fbd281fa Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 5 Aug 2025 18:14:42 +0530 Subject: [PATCH 13/38] bug fix --- .../simulation/human/instances/animator/materialAnimator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx index 178cc4e..0e5b8e3 100644 --- a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx @@ -53,7 +53,7 @@ const MaterialAnimator = ({ human }: { human: HumanStatus; }) => { {hasLoad && action && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && ( From 76f295d9b98cedc8f2465200a3522e4c0c83ba03 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 6 Aug 2025 09:57:36 +0530 Subject: [PATCH 14/38] bug fix --- .../modules/builder/asset/models/models.tsx | 28 +++++++++++++++++-- .../instances/animator/roboticArmAnimator.tsx | 3 +- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/app/src/modules/builder/asset/models/models.tsx b/app/src/modules/builder/asset/models/models.tsx index 5944b1c..ebfa507 100644 --- a/app/src/modules/builder/asset/models/models.tsx +++ b/app/src/modules/builder/asset/models/models.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { useThree, useFrame } from "@react-three/fiber"; -import { Vector3 } from "three"; +import { Group, Vector3 } from "three"; import { CameraControls } from '@react-three/drei'; import { useLimitDistance, useRenderDistance, useSelectedFloorItem } from '../../../../store/builder/store'; import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore'; @@ -12,7 +12,8 @@ import { GLTFLoader } from "three/examples/jsm/Addons"; const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); function Models({ loader }: { loader: GLTFLoader }) { - const { controls, camera } = useThree(); + const { controls, camera, raycaster, pointer, gl } = useThree(); + const assetGroupRef = useRef(null); const { assetStore } = useSceneContext(); const { assets } = assetStore(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); @@ -42,9 +43,32 @@ function Models({ loader }: { loader: GLTFLoader }) { } }); + useEffect(() => { + // const canvasElement = gl.domElement; + + // const onClick = () => { + // if (!assetGroupRef.current || assetGroupRef.current.children.length === 0) return; + // raycaster.setFromCamera(pointer, camera); + + // const intersects = raycaster.intersectObjects(assetGroupRef.current.children, true); + + // if (intersects.length > 0) { + // console.log('intersects: ', intersects); + // } + // } + + // canvasElement.addEventListener('click', onClick); + + // return () => { + // canvasElement.removeEventListener('click', onClick); + // } + + }, [camera]) + return ( { e.stopPropagation(); if (selectedFloorItem) { diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index be3a452..8eb1af9 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -163,7 +163,7 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone // Handle nearest points and final path (including arc points) useEffect(() => { - if (circlePoints.length > 0 && currentPath.length > 0) { + if (circlePoints.length > 0 && currentPath.length > 0 && ikSolver.mesh) { let start = currentPath[0]; let end = currentPath[currentPath.length - 1]; @@ -251,7 +251,6 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone } if (currentPhase === 'end-to-rest') { - console.log('currentPhase: ', currentPhase); const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid); const armbotWorldPos = new THREE.Vector3(); From a08cec33ab2ff1c5acf942b096ea91405b739775 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 6 Aug 2025 18:19:54 +0530 Subject: [PATCH 15/38] pillar Jig half way completed --- .../components/layout/sidebarLeft/Assets.tsx | 461 ++++++------ app/src/modules/builder/asset/assetsGroup.tsx | 31 + .../builder/asset/functions/addAssetModel.ts | 66 +- .../model/eventHandlers/useEventHandlers.ts | 128 ++-- .../builder/asset/models/model/model.tsx | 429 +++--------- .../modules/builder/asset/models/models.tsx | 26 +- .../Instances/Instances/wallAssetInstance.tsx | 4 +- .../builder/wallAsset/wallAssetCreator.tsx | 4 +- .../selection3D/copyPasteControls3D.tsx | 42 +- .../selection3D/duplicationControls3D.tsx | 42 +- app/src/modules/scene/sceneContext.tsx | 9 +- app/src/modules/simulation/crane/crane.tsx | 15 + .../instances/animator/pillarJibAnimator.tsx | 133 ++++ .../crane/instances/craneInstances.tsx | 24 + .../instances/instance/pillarJibInstance.tsx | 14 + .../points/functions/pointsCalculator.ts | 2 +- .../points/instances/pointInstances.tsx | 1 + .../triggerConnections/triggerConnector.tsx | 16 + .../modules/simulation/products/products.tsx | 25 +- app/src/modules/simulation/simulation.tsx | 3 + app/src/store/builder/store.ts | 656 +++++++++--------- app/src/store/simulation/useCraneStore.ts | 263 +++++++ app/src/store/simulation/useEventsStore.ts | 12 +- app/src/store/simulation/useProductStore.ts | 23 +- app/src/types/builderTypes.d.ts | 1 + app/src/types/simulationTypes.d.ts | 104 ++- app/src/types/world/worldTypes.d.ts | 3 +- 27 files changed, 1506 insertions(+), 1031 deletions(-) create mode 100644 app/src/modules/simulation/crane/crane.tsx create mode 100644 app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx create mode 100644 app/src/modules/simulation/crane/instances/craneInstances.tsx create mode 100644 app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx create mode 100644 app/src/store/simulation/useCraneStore.ts diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 0420d2a..8c8b4d7 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -17,255 +17,242 @@ import SkeletonUI from "../../templates/SkeletonUI"; // ------------------------------------- interface AssetProp { - filename: string; - thumbnail?: string; - category: string; - description?: string; - tags: string; - url?: string; - uploadDate?: number; - isArchieve?: boolean; - animated?: boolean; - price?: number; - CreatedBy?: string; + filename: string; + thumbnail?: string; + category: string; + description?: string; + tags: string; + url?: string; + uploadDate?: number; + isArchieve?: boolean; + animated?: boolean; + price?: number; + CreatedBy?: string; } interface CategoryListProp { - assetImage?: string; - assetName?: string; - categoryImage: string; - category: string; + assetImage?: string; + assetName?: string; + categoryImage: string; + category: string; } const Assets: React.FC = () => { - const { setSelectedItem } = useSelectedItem(); - const [searchValue, setSearchValue] = useState(""); - const [selectedCategory, setSelectedCategory] = useState(null); - const [categoryAssets, setCategoryAssets] = useState([]); - const [filtereredAssets, setFiltereredAssets] = useState([]); - const [categoryList, setCategoryList] = useState([]); - const [isLoading, setisLoading] = useState(false); // Loading state for assets + const { setSelectedItem } = useSelectedItem(); + const [searchValue, setSearchValue] = useState(""); + const [selectedCategory, setSelectedCategory] = useState(null); + const [categoryAssets, setCategoryAssets] = useState([]); + const [filtereredAssets, setFiltereredAssets] = useState([]); + const [categoryList, setCategoryList] = useState([]); + const [isLoading, setisLoading] = useState(false); // Loading state for assets - const handleSearchChange = (value: string) => { - const searchTerm = value.toLowerCase(); - setSearchValue(value); - if (searchTerm.trim() === "" && !selectedCategory) { - setCategoryAssets([]); - return; - } - const filteredModels = filtereredAssets?.filter((model) => { - if (!model?.tags || !model?.filename || !model?.category) return false; - if (searchTerm.startsWith(":") && searchTerm.length > 1) { - const tagSearchTerm = searchTerm.slice(1); - return model.tags.toLowerCase().includes(tagSearchTerm); - } else if (selectedCategory) { - return ( - model.category - .toLowerCase() - .includes(selectedCategory.toLowerCase()) && - model.filename.toLowerCase().includes(searchTerm) - ); - } else { - return model.filename.toLowerCase().includes(searchTerm); - } - }); + const handleSearchChange = (value: string) => { + const searchTerm = value.toLowerCase(); + setSearchValue(value); + if (searchTerm.trim() === "" && !selectedCategory) { + setCategoryAssets([]); + return; + } + const filteredModels = filtereredAssets?.filter((model) => { + if (!model?.tags || !model?.filename || !model?.category) return false; + if (searchTerm.startsWith(":") && searchTerm.length > 1) { + const tagSearchTerm = searchTerm.slice(1); + return model.tags.toLowerCase().includes(tagSearchTerm); + } else if (selectedCategory) { + return ( + model.category + .toLowerCase() + .includes(selectedCategory.toLowerCase()) && + model.filename.toLowerCase().includes(searchTerm) + ); + } else { + return model.filename.toLowerCase().includes(searchTerm); + } + }); - setCategoryAssets(filteredModels); - }; - - useEffect(() => { - const filteredAssets = async () => { - try { - const filt = await fetchAssets(); - setFiltereredAssets(filt); - } catch { - echo.error("Filter asset not found"); - } + setCategoryAssets(filteredModels); }; - filteredAssets(); - }, [categoryAssets]); - useEffect(() => { - setCategoryList([ - { category: "Fenestration", categoryImage: feneration }, - { category: "Vehicles", categoryImage: vehicle }, - { category: "Workstation", categoryImage: workStation }, - { category: "Machines", categoryImage: machines }, - { category: "Workers", categoryImage: worker }, - { category: "Storage", categoryImage: storage }, - { category: "Safety", categoryImage: safety }, - { category: "Office", categoryImage: office }, - ]); - }, []); - - const fetchCategoryAssets = async (asset: any) => { - setisLoading(true); - setSelectedCategory(asset); - try { - const res = await getCategoryAsset(asset); - setCategoryAssets(res); - setFiltereredAssets(res); - setisLoading(false); // End loading - // eslint-disable-next-line - } catch (error) { - echo.error("failed to fetch assets"); - setisLoading(false); - } - }; - - return ( -
- -
-
- {(() => { - if (isLoading) { - return ; // Show skeleton when loading + useEffect(() => { + const filteredAssets = async () => { + try { + const filt = await fetchAssets(); + setFiltereredAssets(filt); + } catch { + echo.error("Filter asset not found"); } - if (searchValue) { - return ( -
-
-
-

Results for {searchValue}

-
-
- {categoryAssets?.map((asset: any, index: number) => ( -
- {asset.filename} { - setSelectedItem({ - name: asset.filename, - id: asset.AssetID, - type: - asset.type === "undefined" - ? undefined - : asset.type - }); - }} - /> + }; + filteredAssets(); + }, [categoryAssets]); -
- {asset.filename - .split("_") - .map( - (word: any) => - word.charAt(0).toUpperCase() + word.slice(1) - ) - .join(" ")} -
-
- ))} -
-
-
- ); - } + useEffect(() => { + setCategoryList([ + { category: "Fenestration", categoryImage: feneration }, + { category: "Vehicles", categoryImage: vehicle }, + { category: "Workstation", categoryImage: workStation }, + { category: "Machines", categoryImage: machines }, + { category: "Workers", categoryImage: worker }, + { category: "Storage", categoryImage: storage }, + { category: "Safety", categoryImage: safety }, + { category: "Office", categoryImage: office }, + ]); + }, []); - if (selectedCategory) { - return ( -
-

- {selectedCategory} - -

-
- {categoryAssets?.map((asset: any, index: number) => ( -
- {asset.filename} { - setSelectedItem({ - name: asset.filename, - id: asset.AssetID, - type: - asset.type === "undefined" - ? undefined - : asset.type, - category: asset.category, - subCategory: asset.subCategory - }); - }} - /> -
- {asset.filename - .split("_") - .map( - (word: any) => - word.charAt(0).toUpperCase() + word.slice(1) - ) - .join(" ")} -
-
- ))} - {categoryAssets.length === 0 && ( -
- 🚧 The asset shelf is empty. We're working on filling it - up! -
- )} -
-
- ); - } + const fetchCategoryAssets = async (asset: any) => { + setisLoading(true); + setSelectedCategory(asset); + try { + const res = await getCategoryAsset(asset); + setCategoryAssets(res); + setFiltereredAssets(res); + setisLoading(false); // End loading + // eslint-disable-next-line + } catch (error) { + echo.error("failed to fetch assets"); + setisLoading(false); + } + }; - return ( -
-

Categories

-
- {Array.from( - new Set(categoryList.map((asset) => asset.category)) - ).map((category, index) => { - const categoryInfo = categoryList.find( - (asset) => asset.category === category - ); - return ( -
fetchCategoryAssets(category)} - > - {category} -
{category}
-
- ); - })} -
-
- ); - })()} -
-
-
- ); + return ( +
+ +
+
+ {(() => { + if (isLoading) { + return ; // Show skeleton when loading + } + if (searchValue) { + return ( +
+
+
+

Results for {searchValue}

+
+
+ {categoryAssets?.map((asset: any, index: number) => ( +
+ {asset.filename} { + setSelectedItem({ + name: asset.filename, + id: asset.AssetID, + type: asset.type === "undefined" ? undefined : asset.type + }); + }} + /> + +
+ {asset.filename + .split("_") + .map( + (word: any) => + word.charAt(0).toUpperCase() + word.slice(1) + ) + .join(" ")} +
+
+ ))} +
+
+
+ ); + } + + if (selectedCategory) { + return ( +
+

+ {selectedCategory} + +

+
+ {categoryAssets?.map((asset: any, index: number) => ( +
+ {asset.filename} { + setSelectedItem({ + name: asset.filename, + id: asset.AssetID, + type: asset.type === "undefined" ? undefined : asset.type, + category: asset.category, + subType: asset.subType + }); + }} + /> +
+ {asset.filename.split("_").map((word: any) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ")} +
+
+ ))} + {categoryAssets.length === 0 && ( +
+ 🚧 The asset shelf is empty. We're working on filling it up! +
+ )} +
+
+ ); + } + + return ( +
+

Categories

+
+ {Array.from( + new Set(categoryList.map((asset) => asset.category)) + ).map((category, index) => { + const categoryInfo = categoryList.find( + (asset) => asset.category === category + ); + return ( +
fetchCategoryAssets(category)} + > + {category} +
{category}
+
+ ); + })} +
+
+ ); + })()} +
+
+
+ ); }; export default Assets; diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 6c06be2..072c9a7 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -119,6 +119,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "vehicle", + subType: item.eventData.subType as VehicleEventSchema['subType'] || '', speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -151,6 +152,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "transfer", + subType: item.eventData.subType || '', speed: 1, points: item.eventData.points?.map((point: any, index: number) => ({ uuid: point.uuid || THREE.MathUtils.generateUUID(), @@ -177,6 +179,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "machine", + subType: item.eventData.subType || '', point: { 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], @@ -200,6 +203,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "roboticArm", + subType: item.eventData.subType || '', speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -228,6 +232,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "storageUnit", + subType: item.eventData.subType || '', point: { 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], @@ -250,6 +255,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "human", + subType: item.eventData.subType || '', speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -270,6 +276,31 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { } } addEvent(humanEvent); + } else if (item.eventData.type === 'Crane') { + const craneEvent: CraneEventSchema = { + modelUuid: item.modelUuid, + modelName: item.modelName, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "crane", + subType: item.eventData.subType as CraneEventSchema['subType'] || 'pillarJib', + point: { + 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], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); } } else { assets.push({ diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index bb66c6a..c64064a 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -1,13 +1,13 @@ import * as THREE from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; -import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; import * as Types from "../../../../types/world/worldTypes"; import { retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils"; -// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; import { Socket } from "socket.io-client"; import * as CONSTANTS from "../../../../types/world/worldConstants"; import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator"; + import { getUserData } from "../../../../functions/getUserData"; +// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; async function addAssetModel( scene: THREE.Scene, @@ -165,7 +165,7 @@ async function handleModelLoad( if (!data || !data.points) return; - const eventData: any = { type: selectedItem.type }; + const eventData: any = { type: selectedItem.type, subType: selectedItem.subType }; if (selectedItem.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { @@ -175,6 +175,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "transfer", + subType: selectedItem.subType || '', speed: 1, points: data.points.map((point: THREE.Vector3, index: number) => { const triggers: TriggerSchema[] = []; @@ -243,6 +244,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "vehicle", + subType: selectedItem.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -280,6 +282,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "roboticArm", + subType: selectedItem.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -313,6 +316,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "machine", + subType: selectedItem.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], @@ -341,6 +345,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "storageUnit", + subType: selectedItem.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], @@ -368,6 +373,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "human", + subType: selectedItem.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -393,6 +399,36 @@ async function handleModelLoad( position: humanEvent.point.position, rotation: humanEvent.point.rotation, } + } else if (selectedItem.type === "Crane") { + const craneEvent: CraneEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: newFloorItem.rotation, + state: "idle", + type: "crane", + subType: selectedItem.subType || '', + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); + eventData.point = { + uuid: craneEvent.point.uuid, + position: craneEvent.point.position, + rotation: craneEvent.point.rotation, + } } const completeData = { @@ -401,11 +437,7 @@ async function handleModelLoad( modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { - x: model.rotation.x, - y: model.rotation.y, - z: model.rotation.z, - }, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, isLocked: false, isVisible: true, socketId: socket.id, @@ -422,11 +454,7 @@ async function handleModelLoad( modelName: completeData.modelName, assetId: completeData.assetId, position: completeData.position, - rotation: [ - completeData.rotation.x, - completeData.rotation.y, - completeData.rotation.z, - ] as [number, number, number], + rotation: [completeData.rotation.x, completeData.rotation.y, completeData.rotation.z,] as [number, number, number], isLocked: completeData.isLocked, isCollidable: false, isVisible: completeData.isVisible, @@ -442,11 +470,7 @@ async function handleModelLoad( modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { - x: model.rotation.x, - y: model.rotation.y, - z: model.rotation.z, - }, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, isLocked: false, isVisible: true, socketId: socket.id, @@ -462,11 +486,7 @@ async function handleModelLoad( modelName: data.modelName, assetId: data.assetId, position: data.position, - rotation: [data.rotation.x, data.rotation.y, data.rotation.z] as [ - number, - number, - number - ], + rotation: [data.rotation.x, data.rotation.y, data.rotation.z] as [number, number, number], isLocked: data.isLocked, isCollidable: false, isVisible: data.isVisible, diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts index d678382..c08144c 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -1,12 +1,9 @@ import * as THREE from 'three'; import { CameraControls } from '@react-three/drei'; -import { ThreeEvent } from '@react-three/fiber'; -import { useCallback } from 'react'; -import { ProductStoreType } from '../../../../../../store/simulation/useProductStore'; -import { EventStoreType } from '../../../../../../store/simulation/useEventsStore'; -import { Socket } from 'socket.io-client'; +import { ThreeEvent, useThree } from '@react-three/fiber'; +import { useCallback, useEffect, useRef } from 'react'; -import { useActiveTool, useToolMode } from '../../../../../../store/builder/store'; +import { useActiveTool, useDeletableFloorItem, useSelectedFloorItem, useToggleView } from '../../../../../../store/builder/store'; import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore'; import { useSocketStore } from '../../../../../../store/builder/store'; import { useSceneContext } from '../../../../../scene/sceneContext'; @@ -14,60 +11,60 @@ import { useProductContext } from '../../../../../simulation/products/productCon import { useVersionContext } from '../../../../version/versionContext'; import { useParams } from 'react-router-dom'; import { getUserData } from '../../../../../../functions/getUserData'; +import { useLeftData, useTopData } from '../../../../../../store/visualization/useZone3DWidgetStore'; +import { useSelectedAsset } from '../../../../../../store/simulation/useSimulationStore'; +import { upsertProductOrEventApi } from '../../../../../../services/simulation/products/UpsertProductOrEventApi'; // import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi'; export function useModelEventHandlers({ - controls, boundingBox, groupRef, - toggleView, - deletableFloorItem, - setDeletableFloorItem, - setSelectedFloorItem, - gl, - setTop, - setLeft, - getIsEventInProduct, - getEventByModelUuid, - setSelectedAsset, - clearSelectedAsset, - removeAsset, - updateBackend, - leftDrag, - rightDrag }: { - controls: CameraControls | any, boundingBox: THREE.Box3 | null, groupRef: React.RefObject, - toggleView: boolean, - deletableFloorItem: THREE.Object3D | null, - setDeletableFloorItem: (item: THREE.Object3D | null) => void, - setSelectedFloorItem: (item: THREE.Object3D | null) => void, - gl: THREE.WebGLRenderer, - setTop: (top: number) => void, - setLeft: (left: number) => void, - getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean, - getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined, - setSelectedAsset: (EventData: EventsSchema) => void, - clearSelectedAsset: () => void, - removeAsset: (modelUuid: string) => void, - updateBackend: (productName: string, productUuid: string, projectId: string, event: EventsSchema) => void, - leftDrag: React.MutableRefObject, - rightDrag: React.MutableRefObject }) { - + const { controls, gl } = useThree(); const { activeTool } = useActiveTool(); const { activeModule } = useModuleStore(); + const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { socket } = useSocketStore(); - const { eventStore, productStore } = useSceneContext(); + const { eventStore, productStore, assetStore } = useSceneContext(); + const { removeAsset } = assetStore(); + const { removeEvent } = eventStore(); + const { getIsEventInProduct, addPoint, deleteEvent } = productStore(); + const { getEventByModelUuid } = eventStore(); + const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); + const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); + const { setSelectedFloorItem } = useSelectedFloorItem(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { userId, organization } = getUserData(); + const leftDrag = useRef(false); + const isLeftMouseDown = useRef(false); + const rightDrag = useRef(false); + const isRightMouseDown = useRef(false); + const { setTop } = useTopData(); + const { setLeft } = useLeftData(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; const handleDblClick = (asset: Asset) => { if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { @@ -117,8 +114,8 @@ export function useModelEventHandlers({ const response = socket.emit('v1:model-asset:delete', data) - eventStore.getState().removeEvent(asset.modelUuid); - const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid); + removeEvent(asset.modelUuid); + const updatedEvents = deleteEvent(asset.modelUuid); updatedEvents.forEach((event) => { updateBackend( @@ -157,7 +154,7 @@ export function useModelEventHandlers({ } } - const event = productStore.getState().addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); + const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); if (event) { updateBackend( @@ -169,7 +166,6 @@ export function useModelEventHandlers({ } } } - } }; @@ -228,6 +224,50 @@ export function useModelEventHandlers({ } } + useEffect(() => { + const canvasElement = gl.domElement; + + const onPointerDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = true; + leftDrag.current = false; + } + if (evt.button === 2) { + isRightMouseDown.current = true; + rightDrag.current = false; + } + }; + + const onPointerMove = () => { + if (isLeftMouseDown.current) { + leftDrag.current = true; + } + if (isRightMouseDown.current) { + rightDrag.current = true; + } + }; + + const onPointerUp = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = false; + } + if (evt.button === 2) { + isRightMouseDown.current = false; + } + }; + + canvasElement.addEventListener('pointerdown', onPointerDown); + canvasElement.addEventListener('pointermove', onPointerMove); + canvasElement.addEventListener('pointerup', onPointerUp); + + return () => { + canvasElement.removeEventListener('pointerdown', onPointerDown); + canvasElement.removeEventListener('pointermove', onPointerMove); + canvasElement.removeEventListener('pointerup', onPointerUp); + } + + }, [gl]) + return { handleDblClick, handleClick, diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 96076f9..9767138 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -1,76 +1,34 @@ import * as THREE from 'three'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; -import { ThreeEvent, useThree } from '@react-three/fiber'; -import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; +import { useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; -import { CameraControls } from '@react-three/drei'; -import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; -import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore'; -import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore'; -import { useProductContext } from '../../../../simulation/products/productContext'; -import { useParams } from 'react-router-dom'; -import { getUserData } from '../../../../../functions/getUserData'; +import useModuleStore from '../../../../../store/useModuleStore'; import { useSceneContext } from '../../../../scene/sceneContext'; -import { useVersionContext } from '../../../version/versionContext'; import { SkeletonUtils } from 'three-stdlib'; -import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; import { ModelAnimator } from './animator/modelAnimator'; +import { useModelEventHandlers } from './eventHandlers/useEventHandlers'; function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendered: boolean, loader: GLTFLoader }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const savedTheme: string = localStorage.getItem("theme") || "light"; - const { controls, gl } = useThree(); - const { activeTool } = useActiveTool(); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); - const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); - const { assetStore, eventStore, productStore } = useSceneContext(); - const { removeAsset, resetAnimation } = assetStore(); - const { setTop } = useTopData(); - const { setLeft } = useLeftData(); - const { getIsEventInProduct, addPoint } = productStore(); - const { getEventByModelUuid } = eventStore(); - const { selectedProductStore } = useProductContext(); - const { selectedProduct } = selectedProductStore(); - const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); - const { socket } = useSocketStore(); - const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); + const { assetStore } = useSceneContext(); + const { resetAnimation } = assetStore(); + const { setDeletableFloorItem } = useDeletableFloorItem(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); - const leftDrag = useRef(false); - const isLeftMouseDown = useRef(false); - const rightDrag = useRef(false); - const isRightMouseDown = useRef(false); const [gltfScene, setGltfScene] = useState(null); const [boundingBox, setBoundingBox] = useState(null); const [isSelected, setIsSelected] = useState(false); const groupRef = useRef(null); const [ikData, setIkData] = useState(); - const { selectedVersionStore } = useVersionContext(); - const { selectedVersion } = selectedVersionStore(); - const { userId, organization } = getUserData(); - const { projectId } = useParams(); const { selectedAssets } = useSelectedAssets(); - const updateBackend = ( - productName: string, - productUuid: string, - projectId: string, - eventData: EventsSchema - ) => { - upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, - eventDatas: eventData, - versionId: selectedVersion?.versionId || '', - }); - }; - useEffect(() => { if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') { getAssetIksApi(asset.assetId).then((data) => { @@ -82,17 +40,6 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere } }, [asset.modelUuid, ikData]) - useEffect(() => { - if (gltfScene) { - gltfScene.traverse((child: any) => { - if (child.isMesh) { - child.castShadow = true; - child.receiveShadow = true; - } - }) - } - }, [gltfScene]); - useEffect(() => { setDeletableFloorItem(null); if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) { @@ -106,285 +53,6 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere } }, [isRendered, selectedFloorItem]) - useEffect(() => { - const loadModel = async () => { - try { - - // Check Cache - const assetId = asset.assetId; - const cachedModel = THREE.Cache.get(assetId); - if (cachedModel) { - const clone: any = SkeletonUtils.clone(cachedModel.scene); - clone.animations = cachedModel.animations || []; - setGltfScene(clone); - calculateBoundingBox(clone); - return; - } - - // Check IndexedDB - const indexedDBModel = await retrieveGLTF(assetId); - if (indexedDBModel) { - const blobUrl = URL.createObjectURL(indexedDBModel); - loader.load(blobUrl, (gltf) => { - URL.revokeObjectURL(blobUrl); - THREE.Cache.remove(blobUrl); - THREE.Cache.add(assetId, gltf); - setGltfScene(gltf.scene.clone()); - calculateBoundingBox(gltf.scene); - }, - undefined, - (error) => { - echo.error(`[IndexedDB] Error loading ${asset.modelName}:`); - URL.revokeObjectURL(blobUrl); - } - ); - return; - } - - // Fetch from Backend - const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`; - const handleBackendLoad = async (gltf: GLTF) => { - try { - const response = await fetch(modelUrl); - const modelBlob = await response.blob(); - await storeGLTF(assetId, modelBlob); - THREE.Cache.add(assetId, gltf); - setGltfScene(gltf.scene.clone()); - calculateBoundingBox(gltf.scene); - } catch (error) { - console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error); - } - }; - loader.load(modelUrl, - handleBackendLoad, - undefined, - (error) => { - echo.error(`[Backend] Error loading ${asset.modelName}:`); - } - ); - } catch (err) { - console.error("Failed to load model:", asset.assetId, err); - } - }; - - const calculateBoundingBox = (scene: THREE.Object3D) => { - const box = new THREE.Box3().setFromObject(scene); - setBoundingBox(box); - }; - - loadModel(); - - }, []); - - const handleDblClick = (asset: Asset) => { - if (asset) { - if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { - const size = boundingBox.getSize(new THREE.Vector3()); - const center = boundingBox.getCenter(new THREE.Vector3()); - - const front = new THREE.Vector3(0, 0, 1); - groupRef.current.localToWorld(front); - front.sub(groupRef.current.position).normalize(); - - const distance = Math.max(size.x, size.y, size.z) * 2; - const newPosition = center.clone().addScaledVector(front, distance); - - (controls as CameraControls).setPosition( - newPosition.x, - newPosition.y, - newPosition.z, - true - ); - (controls as CameraControls).setTarget(center.x, center.y, center.z, true); - (controls as CameraControls).fitToBox(groupRef.current, true, { - cover: true, - paddingTop: 5, - paddingLeft: 5, - paddingBottom: 5, - paddingRight: 5, - }); - setSelectedFloorItem(groupRef.current); - } - } - }; - - const handleClick = (evt: ThreeEvent, asset: Asset) => { - if (leftDrag.current || toggleView) return; - if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { - - //REST - - // const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName); - - //SOCKET - - const data = { - organization, - modelUuid: asset.modelUuid, - modelName: asset.modelName, - socketId: socket.id, - userId, - versionId: selectedVersion?.versionId || '', - projectId - } - - const response = socket.emit('v1:model-asset:delete', data) - - eventStore.getState().removeEvent(asset.modelUuid); - const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid); - - updatedEvents.forEach((event) => { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - }) - - if (response) { - - removeAsset(asset.modelUuid); - - echo.success("Model Removed!"); - } - - } else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') { - if (asset.eventData && asset.eventData.type === 'Conveyor') { - const intersectedPoint = evt.point; - const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone()); - if (localPosition) { - const conveyorPoint: ConveyorPointSchema = { - uuid: THREE.MathUtils.generateUUID(), - position: [localPosition?.x, localPosition?.y, localPosition?.z], - rotation: [0, 0, 0], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: `Action 1`, - actionType: 'default', - material: 'Default Material', - delay: 0, - spawnInterval: 5, - spawnCount: 1, - triggers: [] - } - } - - const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - } - } - } - - } - } - - const handlePointerOver = useCallback((asset: Asset) => { - if (activeTool === "delete" && activeModule === 'builder') { - if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { - return; - } else { - setDeletableFloorItem(groupRef.current); - } - } - }, [activeTool, activeModule, deletableFloorItem]); - - const handlePointerOut = useCallback((evt: ThreeEvent, asset: Asset) => { - if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { - setDeletableFloorItem(null); - } - }, [activeTool, deletableFloorItem]); - - const handleContextMenu = (asset: Asset, evt: ThreeEvent) => { - if (rightDrag.current || toggleView) return; - if (activeTool === "cursor" && subModule === 'simulations') { - if (asset.modelUuid) { - const canvasElement = gl.domElement; - const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid); - if (isInProduct) { - const event = getEventByModelUuid(asset.modelUuid); - if (event) { - setSelectedAsset(event); - const canvasRect = canvasElement.getBoundingClientRect(); - const relativeX = evt.clientX - canvasRect.left; - const relativeY = evt.clientY - canvasRect.top; - setTop(relativeY); - setLeft(relativeX); - } else { - clearSelectedAsset(); - } - } else { - const event = getEventByModelUuid(asset.modelUuid); - if (event) { - setSelectedAsset(event) - const canvasRect = canvasElement.getBoundingClientRect(); - const relativeX = evt.clientX - canvasRect.left; - const relativeY = evt.clientY - canvasRect.top; - setTop(relativeY); - setLeft(relativeX); - } else { - clearSelectedAsset() - } - } - } else { - clearSelectedAsset() - } - } else { - clearSelectedAsset() - } - } - - useEffect(() => { - const canvasElement = gl.domElement; - - const onPointerDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown.current = true; - leftDrag.current = false; - } - if (evt.button === 2) { - isRightMouseDown.current = true; - rightDrag.current = false; - } - }; - - const onPointerMove = () => { - if (isLeftMouseDown.current) { - leftDrag.current = true; - } - if (isRightMouseDown.current) { - rightDrag.current = true; - } - }; - - const onPointerUp = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown.current = false; - } - if (evt.button === 2) { - isRightMouseDown.current = false; - } - }; - - canvasElement.addEventListener('pointerdown', onPointerDown); - canvasElement.addEventListener('pointermove', onPointerMove); - canvasElement.addEventListener('pointerup', onPointerUp); - - return () => { - canvasElement.removeEventListener('pointerdown', onPointerDown); - canvasElement.removeEventListener('pointermove', onPointerMove); - canvasElement.removeEventListener('pointerup', onPointerUp); - } - - }, [gl]) - useEffect(() => { if (selectedAssets.length > 0) { if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) { @@ -397,6 +65,89 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere } }, [selectedAssets]) + useEffect(() => { + if (gltfScene) { + gltfScene.traverse((child: any) => { + if (child.isMesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }) + } + }, [gltfScene]); + + useEffect(() => { + // Calculate Bounding Box + const calculateBoundingBox = (scene: THREE.Object3D) => { + const box = new THREE.Box3().setFromObject(scene); + setBoundingBox(box); + }; + + // Check Cache + const assetId = asset.assetId; + const cachedModel = THREE.Cache.get(assetId); + if (cachedModel) { + const clone: any = SkeletonUtils.clone(cachedModel.scene); + clone.animations = cachedModel.animations || []; + setGltfScene(clone); + calculateBoundingBox(clone); + return; + } + + // Check IndexedDB + retrieveGLTF(assetId).then((indexedDBModel) => { + if (indexedDBModel) { + const blobUrl = URL.createObjectURL(indexedDBModel); + loader.load( + blobUrl, + (gltf) => { + URL.revokeObjectURL(blobUrl); + THREE.Cache.remove(blobUrl); + THREE.Cache.add(assetId, gltf); + setGltfScene(gltf.scene.clone()); + calculateBoundingBox(gltf.scene); + }, + undefined, + (error) => { + echo.error(`[IndexedDB] Error loading ${asset.modelName}:`); + URL.revokeObjectURL(blobUrl); + } + ); + return; + } + + // Fetch from Backend + const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`; + loader.load( + modelUrl, + (gltf: GLTF) => { + fetch(modelUrl) + .then((response) => response.blob()) + .then((modelBlob) => storeGLTF(assetId, modelBlob)) + .then(() => { + THREE.Cache.add(assetId, gltf); + setGltfScene(gltf.scene.clone()); + calculateBoundingBox(gltf.scene); + }) + .catch((error) => { + console.error( + `[Backend] Error storing/loading ${asset.modelName}:`, + error + ); + }); + }, + undefined, + (error) => { + echo.error(`[Backend] Error loading ${asset.modelName}:`); + } + ); + }).catch((err) => { + console.error("Failed to load model:", asset.assetId, err); + }); + }, []); + + const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef }); + return ( (null); const { assetStore } = useSceneContext(); const { assets } = assetStore(); @@ -43,28 +43,6 @@ function Models({ loader }: { loader: GLTFLoader }) { } }); - useEffect(() => { - // const canvasElement = gl.domElement; - - // const onClick = () => { - // if (!assetGroupRef.current || assetGroupRef.current.children.length === 0) return; - // raycaster.setFromCamera(pointer, camera); - - // const intersects = raycaster.intersectObjects(assetGroupRef.current.children, true); - - // if (intersects.length > 0) { - // console.log('intersects: ', intersects); - // } - // } - - // canvasElement.addEventListener('click', onClick); - - // return () => { - // canvasElement.removeEventListener('click', onClick); - // } - - }, [camera]) - return ( ([]); const [centerOffset, setCenterOffset] = useState(null); - const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ - left: false, - right: false, - }); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => { if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] }; @@ -227,6 +224,7 @@ const CopyPasteControls3D = ({ const eventData: any = { type: pastedAsset.userData.eventData.type, + subType: pastedAsset.userData.eventData.subType, }; if (pastedAsset.userData.eventData.type === "Conveyor") { @@ -237,6 +235,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: 'transfer', + subType: pastedAsset.userData.eventData.subType || '', speed: 1, points: updatedEventData.points.map((point: any, index: number) => ({ uuid: THREE.MathUtils.generateUUID(), @@ -269,6 +268,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "vehicle", + subType: pastedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -307,6 +307,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "roboticArm", + subType: pastedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -341,6 +342,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "machine", + subType: pastedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -369,6 +371,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "storageUnit", + subType: pastedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -396,6 +399,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "human", + subType: pastedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -421,6 +425,36 @@ const CopyPasteControls3D = ({ position: humanEvent.point.position, rotation: humanEvent.point.rotation }; + } else if (pastedAsset.userData.eventData.type === "Crane") { + const craneEvent: CraneEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "crane", + subType: pastedAsset.userData.eventData.subType || '', + point: { + 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]], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); + eventData.point = { + uuid: craneEvent.point.uuid, + position: craneEvent.point.position, + rotation: craneEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 55e88fb..f494025 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -37,10 +37,7 @@ const DuplicationControls3D = ({ const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [isDuplicating, setIsDuplicating] = useState(false); - const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ - left: false, - right: false, - }); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); @@ -228,6 +225,7 @@ const DuplicationControls3D = ({ const eventData: any = { type: duplicatedAsset.userData.eventData.type, + subType: duplicatedAsset.userData.eventData.subType, }; if (duplicatedAsset.userData.eventData.type === "Conveyor") { @@ -238,6 +236,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: 'transfer', + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, points: updatedEventData.points.map((point: any, index: number) => ({ uuid: THREE.MathUtils.generateUUID(), @@ -270,6 +269,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "vehicle", + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -308,6 +308,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "roboticArm", + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -342,6 +343,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "machine", + subType: duplicatedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -370,6 +372,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "storageUnit", + subType: duplicatedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -397,6 +400,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "human", + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -422,6 +426,36 @@ const DuplicationControls3D = ({ position: humanEvent.point.position, rotation: humanEvent.point.rotation }; + } else if (duplicatedAsset.userData.eventData.type === "Crane") { + const craneEvent: CraneEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "crane", + subType: duplicatedAsset.userData.eventData.subType || '', + point: { + 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]], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); + eventData.point = { + uuid: craneEvent.point.uuid, + position: craneEvent.point.position, + rotation: craneEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index bf65c36..ac17198 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -19,6 +19,7 @@ import { createConveyorStore, ConveyorStoreType } from '../../store/simulation/u import { createVehicleStore, VehicleStoreType } from '../../store/simulation/useVehicleStore'; import { createStorageUnitStore, StorageUnitStoreType } from '../../store/simulation/useStorageUnitStore'; import { createHumanStore, HumanStoreType } from '../../store/simulation/useHumanStore'; +import { createCraneStore, CraneStoreType } from '../../store/simulation/useCraneStore'; type SceneContextValue = { @@ -41,6 +42,7 @@ type SceneContextValue = { vehicleStore: VehicleStoreType; storageUnitStore: StorageUnitStoreType; humanStore: HumanStoreType; + craneStore: CraneStoreType; humanEventManagerRef: React.RefObject; @@ -78,6 +80,7 @@ export function SceneProvider({ const vehicleStore = useMemo(() => createVehicleStore(), []); const storageUnitStore = useMemo(() => createStorageUnitStore(), []); const humanStore = useMemo(() => createHumanStore(), []); + const craneStore = useMemo(() => createCraneStore(), []); const humanEventManagerRef = useRef({ humanStates: [] }); @@ -98,8 +101,9 @@ export function SceneProvider({ vehicleStore.getState().clearVehicles(); storageUnitStore.getState().clearStorageUnits(); humanStore.getState().clearHumans(); + craneStore.getState().clearCranes(); humanEventManagerRef.current.humanStates = []; - }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); + }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); const contextValue = useMemo(() => ( { @@ -119,11 +123,12 @@ export function SceneProvider({ vehicleStore, storageUnitStore, humanStore, + craneStore, humanEventManagerRef, clearStores, layout } - ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]); + ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]); return ( diff --git a/app/src/modules/simulation/crane/crane.tsx b/app/src/modules/simulation/crane/crane.tsx new file mode 100644 index 0000000..fdbb430 --- /dev/null +++ b/app/src/modules/simulation/crane/crane.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import CraneInstances from './instances/craneInstances' + +function Crane() { + + return ( + <> + + + + + ) +} + +export default Crane \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx new file mode 100644 index 0000000..95a531c --- /dev/null +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -0,0 +1,133 @@ +import * as THREE from 'three'; +import { useEffect, useMemo } from 'react'; +import { useThree } from '@react-three/fiber'; + +function PillarJibAnimator({ crane }: { crane: CraneStatus }) { + const { scene } = useThree(); + + useEffect(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + + if (model) { + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (base && trolley && hook) { + let trolleyDir = 1; + let hookDir = 1; + + const trolleySpeed = 0.01; + const hookSpeed = 0.01; + const rotationSpeed = 0.005; + + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + + const originalTrolleyX = trolley.position.x; + const originalHookY = hook.position.y; + + const animate = () => { + if (base) { + base.rotation.y += rotationSpeed; + } + + if (trolley) { + trolley.position.x += trolleyDir * trolleySpeed; + if (trolley.position.x >= originalTrolleyX + trolleyMaxOffset || + trolley.position.x <= originalTrolleyX + trolleyMinOffset) { + trolleyDir *= -1; + } + } + + if (hook) { + hook.position.y += hookDir * hookSpeed; + if (hook.position.y >= originalHookY + hookMinOffset || + hook.position.y <= originalHookY + hookMaxOffset) { + hookDir *= -1; + } + } + + requestAnimationFrame(animate); + }; + + animate(); + } + } + }, [crane, scene]); + + return ( + + ); +} + +export default PillarJibAnimator; + +function PillarJibHelper({ crane }: { crane: CraneStatus }) { + const { scene } = useThree(); + + const { geometry, position } = useMemo(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return { geometry: null, position: null }; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook) return { geometry: null, position: null }; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const trolleyWorld = new THREE.Vector3(); + trolley.getWorldPosition(trolleyWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); + const outerRadius = distFromBase + 1.75; + const innerRadius = Math.max(distFromBase - 1, 0.05); + const height = (0.25 - (-1.5)); + const cylinderYPosition = hookWorld.y + (height / 2) + (-1.5 + 0.25) / 2; + + const shape = new THREE.Shape(); + shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + + const hole = new THREE.Path(); + hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); + shape.holes.push(hole); + + const extrudeSettings = { + depth: height, + bevelEnabled: false, + steps: 1 + }; + + const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); + const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + + return { geometry, position }; + }, [scene, crane.modelUuid]); + + if (!geometry || !position) return null; + + return ( + + + + ); +} diff --git a/app/src/modules/simulation/crane/instances/craneInstances.tsx b/app/src/modules/simulation/crane/instances/craneInstances.tsx new file mode 100644 index 0000000..bd6a96b --- /dev/null +++ b/app/src/modules/simulation/crane/instances/craneInstances.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import PillarJibInstance from './instance/pillarJibInstance'; +import { useSceneContext } from '../../../scene/sceneContext'; + +function CraneInstances() { + const { craneStore } = useSceneContext(); + const { cranes } = craneStore(); + + return ( + <> + {cranes.map((crane: CraneStatus) => ( + + + {crane.subType === "pillarJib" && + + } + + + ))} + + ) +} + +export default CraneInstances \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx new file mode 100644 index 0000000..bef6a17 --- /dev/null +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -0,0 +1,14 @@ +import PillarJibAnimator from '../animator/pillarJibAnimator' + +function PillarJibInstance({ crane }: { crane: CraneStatus }) { + + return ( + <> + + + + + ) +} + +export default PillarJibInstance \ No newline at end of file diff --git a/app/src/modules/simulation/events/points/functions/pointsCalculator.ts b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts index ccc2dbb..c59a070 100644 --- a/app/src/modules/simulation/events/points/functions/pointsCalculator.ts +++ b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts @@ -40,7 +40,7 @@ function PointsCalculator( } return { - points: [worldTopMiddle] + points: [new THREE.Vector3(worldTopMiddle.x, worldTopMiddle.y + 0.1, worldTopMiddle.z)] }; } diff --git a/app/src/modules/simulation/events/points/instances/pointInstances.tsx b/app/src/modules/simulation/events/points/instances/pointInstances.tsx index 0b7ca60..edd1243 100644 --- a/app/src/modules/simulation/events/points/instances/pointInstances.tsx +++ b/app/src/modules/simulation/events/points/instances/pointInstances.tsx @@ -18,6 +18,7 @@ function PointInstances() { machine: "purple", storageUnit: "red", human: "white", + crane: "yellow", }; return ( diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 1469d7f..736667b 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -173,6 +173,22 @@ function TriggerConnector() { }); }); } + // Handle Human point + else if (event.type === "crane" && 'point' in event) { + const point = event.point; + point.actions?.forEach(action => { + action.triggers?.forEach(trigger => { + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { + newConnections.push({ + id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, + startPointUuid: point.uuid, + endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + trigger + }); + } + }); + }); + } }); setConnections(newConnections); diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index eeaf7fc..32c1162 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom'; import { useVersionContext } from '../../builder/version/versionContext'; function Products() { - const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, layout, productStore } = useSceneContext(); + const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, layout, productStore } = useSceneContext(); const { products, getProductById, addProduct, setProducts } = productStore(); const { selectedProductStore } = useProductContext(); const { setMainProduct } = useMainProduct(); @@ -20,7 +20,8 @@ function Products() { const { addMachine, clearMachines } = machineStore(); const { addConveyor, clearConveyors } = conveyorStore(); const { setCurrentMaterials, clearStorageUnits, updateCurrentLoad, addStorageUnit } = storageUnitStore(); - const { addHuman, addCurrentAction, clearHumans } = humanStore(); + const { addHuman, addCurrentAction: addCurrentActionHuman, clearHumans } = humanStore(); + const { addCrane, addCurrentAction: addCurrentActionCrane, clearCranes } = craneStore(); const { isReset } = useResetButtonStore(); const { isPlaying } = usePlayButtonStore(); const { mainProduct } = useMainProduct(); @@ -164,7 +165,25 @@ function Products() { addHuman(selectedProduct.productUuid, events); if (events.point.actions.length > 0) { - addCurrentAction(events.modelUuid, events.point.actions[0].actionUuid); + addCurrentActionHuman(events.modelUuid, events.point.actions[0].actionUuid); + } + } + }); + } + } + }, [selectedProduct, products, isReset, isPlaying]); + + useEffect(() => { + if (selectedProduct.productUuid) { + const product = getProductById(selectedProduct.productUuid); + if (product) { + clearCranes(); + product.eventDatas.forEach(events => { + if (events.type === 'crane') { + addCrane(selectedProduct.productUuid, events); + + if (events.point.actions.length > 0) { + addCurrentActionCrane(events.modelUuid, events.point.actions[0].actionUuid); } } }); diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index bb9d91f..4490aeb 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -7,6 +7,7 @@ import Materials from './materials/materials'; import Machine from './machine/machine'; import StorageUnit from './storageUnit/storageUnit'; import Human from './human/human'; +import Crane from './crane/crane'; import Simulator from './simulator/simulator'; import Products from './products/products'; import Trigger from './triggers/trigger'; @@ -55,6 +56,8 @@ function Simulation() { + + diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 12c231a..aa1499b 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -3,74 +3,74 @@ 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, - refreshToken?: string - ) => { - const existingSocket = get().socket; - if (existingSocket) { - return; - } + socket: null, + initializeSocket: ( + email?: string, + organization?: string, + token?: string, + refreshToken?: string + ) => { + const existingSocket = get().socket; + if (existingSocket) { + return; + } - const socket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); + const socket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); - const visualizationSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); + const visualizationSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); - const dashBoardSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/dashboard`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); - const projectSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); - const threadSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); + const dashBoardSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/dashboard`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); + const projectSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); + const threadSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); - set({ - socket, - visualizationSocket, - dashBoardSocket, - projectSocket, - threadSocket, - }); - }, - disconnectSocket: () => { - set((state: any) => { - state.socket?.disconnect(); - state.visualizationSocket?.disconnect(); - state.dashBoardSocket?.disconnect(); - state.projectSocket?.disconnect(); - state.threadSocket?.disconnect(); - return { socket: null }; - }); - }, + set({ + socket, + visualizationSocket, + dashBoardSocket, + projectSocket, + threadSocket, + }); + }, + disconnectSocket: () => { + set((state: any) => { + state.socket?.disconnect(); + state.visualizationSocket?.disconnect(); + state.dashBoardSocket?.disconnect(); + state.projectSocket?.disconnect(); + state.threadSocket?.disconnect(); + return { socket: null }; + }); + }, })); // export const useSocketStore = create((set: any, get: any) => ({ // socket: null, @@ -128,507 +128,507 @@ export const useSocketStore = create((set: any, get: any) => ({ // }, // })); export const useLoadingProgress = create<{ - loadingProgress: number; - setLoadingProgress: (x: number) => void; + loadingProgress: number; + setLoadingProgress: (x: number) => void; }>((set) => ({ - loadingProgress: 1, - setLoadingProgress: (x: number) => set({ loadingProgress: x }), + loadingProgress: 1, + setLoadingProgress: (x: number) => set({ loadingProgress: x }), })); export const useOrganization = create((set: any) => ({ - organization: "", - setOrganization: (x: any) => set(() => ({ organization: x })), + organization: "", + setOrganization: (x: any) => set(() => ({ organization: x })), })); export const useToggleView = create((set: any) => ({ - toggleView: false, - setToggleView: (x: any) => set(() => ({ toggleView: x })), + toggleView: false, + setToggleView: (x: any) => set(() => ({ toggleView: x })), })); export const useRoomsState = create((set: any) => ({ - roomsState: [], - setRoomsState: (x: any) => set(() => ({ roomsState: x })), + roomsState: [], + setRoomsState: (x: any) => set(() => ({ roomsState: x })), })); export const useSelectedItem = create((set: any) => ({ - selectedItem: { - name: "", - id: "", - type: undefined, - category: "", - subCatergory: "", - }, - setSelectedItem: (x: any) => set(() => ({ selectedItem: x })), + selectedItem: { + name: "", + id: "", + type: undefined, + category: "", + subType: "", + }, + setSelectedItem: (x: any) => set(() => ({ selectedItem: x })), })); export const useNavMesh = create((set: any) => ({ - navMesh: null, - setNavMesh: (x: any) => set({ navMesh: x }), + navMesh: null, + setNavMesh: (x: any) => set({ navMesh: x }), })); export const useSelectedAssets = create((set: any) => ({ - selectedAssets: [], - setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), + selectedAssets: [], + setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), })); export const useLayers = create((set: any) => ({ - Layers: 1, - setLayers: (x: any) => set(() => ({ Layers: x })), + Layers: 1, + setLayers: (x: any) => set(() => ({ Layers: x })), })); export const useCamPosition = create((set: any) => ({ - camPosition: { x: undefined, y: undefined, z: undefined }, - setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }), + camPosition: { x: undefined, y: undefined, z: undefined }, + setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }), })); export const useMenuVisible = create((set: any) => ({ - menuVisible: false, - setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), + menuVisible: false, + setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), })); export const useToolMode = create((set: any) => ({ - toolMode: null, - setToolMode: (x: any) => set(() => ({ toolMode: x })), + toolMode: null, + setToolMode: (x: any) => set(() => ({ toolMode: x })), })); export const useSelectedWallItem = create((set: any) => ({ - selectedWallItem: null, - setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), + selectedWallItem: null, + setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), })); export const useSelectedFloorItem = create((set: any) => ({ - selectedFloorItem: null, - setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), + selectedFloorItem: null, + setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), })); export const useDeletableFloorItem = create((set: any) => ({ - deletableFloorItem: null, - setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })), + deletableFloorItem: null, + setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })), })); export const useSetScale = create((set: any) => ({ - scale: null, - setScale: (x: any) => set(() => ({ scale: x })), + scale: null, + setScale: (x: any) => set(() => ({ scale: x })), })); export const useRoofVisibility = create((set: any) => ({ - roofVisibility: false, - setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })), + roofVisibility: false, + setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })), })); export const useWallVisibility = create((set: any) => ({ - wallVisibility: false, - setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })), + wallVisibility: false, + setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })), })); export const useShadows = create((set: any) => ({ - shadows: false, - setShadows: (x: any) => set(() => ({ shadows: x })), + shadows: false, + setShadows: (x: any) => set(() => ({ shadows: x })), })); export const useSunPosition = create((set: any) => ({ - sunPosition: { x: undefined, y: undefined, z: undefined }, - setSunPosition: (newSuntPosition: any) => - set({ sunPosition: newSuntPosition }), + sunPosition: { x: undefined, y: undefined, z: undefined }, + setSunPosition: (newSuntPosition: any) => + set({ sunPosition: newSuntPosition }), })); export const useRemoveLayer = create((set: any) => ({ - removeLayer: false, - setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })), + removeLayer: false, + setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })), })); export const useRemovedLayer = create((set: any) => ({ - removedLayer: null, - setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })), + removedLayer: null, + setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })), })); export const useProjectName = create((set: any) => ({ - projectName: "Creating Your Project", - setProjectName: (x: any) => set({ projectName: x }), + projectName: "Creating Your Project", + setProjectName: (x: any) => set({ projectName: x }), })); export const useActiveLayer = create((set: any) => ({ - activeLayer: 1, - setActiveLayer: (x: any) => set({ activeLayer: x }), + activeLayer: 1, + setActiveLayer: (x: any) => set({ activeLayer: x }), })); export const useResetCamera = create((set: any) => ({ - resetCamera: false, - setResetCamera: (x: any) => set({ resetCamera: x }), + resetCamera: false, + setResetCamera: (x: any) => set({ resetCamera: x }), })); export const useAddAction = create((set: any) => ({ - addAction: null, - setAddAction: (x: any) => set({ addAction: x }), + addAction: null, + setAddAction: (x: any) => set({ addAction: x }), })); export const useActiveTool = create((set: any) => ({ - activeTool: "cursor", - setActiveTool: (x: any) => set({ activeTool: x }), + activeTool: "cursor", + setActiveTool: (x: any) => set({ activeTool: x }), })); export const useActiveSubTool = create((set: any) => ({ - activeSubTool: "cursor", - setActiveSubTool: (x: any) => set({ activeSubTool: x }), + activeSubTool: "cursor", + setActiveSubTool: (x: any) => set({ activeSubTool: x }), })); export const useElevation = create((set: any) => ({ - elevation: 45, - setElevation: (x: any) => set({ elevation: x }), + elevation: 45, + setElevation: (x: any) => set({ elevation: x }), })); export const useAzimuth = create((set: any) => ({ - azimuth: -160, - setAzimuth: (x: any) => set({ azimuth: x }), + azimuth: -160, + setAzimuth: (x: any) => set({ azimuth: x }), })); export const useRenderDistance = create((set: any) => ({ - renderDistance: 40, - setRenderDistance: (x: any) => set({ renderDistance: x }), + renderDistance: 40, + setRenderDistance: (x: any) => set({ renderDistance: x }), })); export const useCamMode = create((set: any) => ({ - camMode: "ThirdPerson", - setCamMode: (x: any) => set({ camMode: x }), + camMode: "ThirdPerson", + setCamMode: (x: any) => set({ camMode: x }), })); export const useUserName = create((set: any) => ({ - userName: "", - setUserName: (x: any) => set({ userName: x }), + userName: "", + setUserName: (x: any) => set({ userName: x }), })); export const useRenameModeStore = create((set: any) => ({ - isRenameMode: false, - setIsRenameMode: (state: boolean) => set({ isRenameMode: state }), + isRenameMode: false, + setIsRenameMode: (state: boolean) => set({ isRenameMode: state }), })); export const useObjectPosition = create((set: any) => ({ - objectPosition: { x: undefined, y: undefined, z: undefined }, - setObjectPosition: (newObjectPosition: any) => - set({ objectPosition: newObjectPosition }), + objectPosition: { x: undefined, y: undefined, z: undefined }, + setObjectPosition: (newObjectPosition: any) => + set({ objectPosition: newObjectPosition }), })); export const useObjectRotation = create((set: any) => ({ - objectRotation: { x: undefined, y: undefined, z: undefined }, - setObjectRotation: (newObjectRotation: any) => - set({ objectRotation: newObjectRotation }), + objectRotation: { x: undefined, y: undefined, z: undefined }, + setObjectRotation: (newObjectRotation: any) => + set({ objectRotation: newObjectRotation }), })); export const useDrieTemp = create((set: any) => ({ - drieTemp: undefined, - setDrieTemp: (x: any) => set({ drieTemp: x }), + drieTemp: undefined, + setDrieTemp: (x: any) => set({ drieTemp: x }), })); export const useActiveUsers = create((set: any) => ({ - activeUsers: [], - setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => - set((state: { activeUsers: any[] }) => ({ - activeUsers: - typeof callback === "function" ? callback(state.activeUsers) : callback, - })), + activeUsers: [], + setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => + set((state: { activeUsers: any[] }) => ({ + activeUsers: + typeof callback === "function" ? callback(state.activeUsers) : callback, + })), })); export const useDrieUIValue = create((set: any) => ({ - drieUIValue: { touch: null, temperature: null, humidity: null }, + drieUIValue: { touch: null, temperature: null, humidity: null }, - setDrieUIValue: (x: any) => - set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })), + setDrieUIValue: (x: any) => + set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })), - setTouch: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, touch: value }, - })), - setTemperature: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, temperature: value }, - })), - setHumidity: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, humidity: value }, - })), + setTouch: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, touch: value }, + })), + setTemperature: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, temperature: value }, + })), + setHumidity: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, humidity: value }, + })), })); export const usezoneTarget = create((set: any) => ({ - zoneTarget: [], - setZoneTarget: (x: any) => set({ zoneTarget: x }), + zoneTarget: [], + setZoneTarget: (x: any) => set({ zoneTarget: x }), })); export const usezonePosition = create((set: any) => ({ - zonePosition: [], - setZonePosition: (x: any) => set({ zonePosition: x }), + zonePosition: [], + setZonePosition: (x: any) => set({ zonePosition: x }), })); interface EditPositionState { - Edit: boolean; - setEdit: (value: boolean) => void; + Edit: boolean; + setEdit: (value: boolean) => void; } export const useEditPosition = create((set) => ({ - Edit: false, - setEdit: (value) => set({ Edit: value }), + Edit: false, + setEdit: (value) => set({ Edit: value }), })); export const useAsset3dWidget = create((set: any) => ({ - widgetSelect: "", - setWidgetSelect: (x: any) => set({ widgetSelect: x }), + widgetSelect: "", + setWidgetSelect: (x: any) => set({ widgetSelect: x }), })); export const useWidgetSubOption = create((set: any) => ({ - widgetSubOption: "2D", - setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), + widgetSubOption: "2D", + setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), })); export const useLimitDistance = create((set: any) => ({ - limitDistance: true, - setLimitDistance: (x: any) => set({ limitDistance: x }), + limitDistance: true, + setLimitDistance: (x: any) => set({ limitDistance: x }), })); export const useTileDistance = create((set: any) => ({ - gridValue: { - size: CONSTANTS.gridConfig.size, - divisions: CONSTANTS.gridConfig.divisions, - }, - planeValue: { - height: CONSTANTS.planeConfig.height, - width: CONSTANTS.planeConfig.width, - }, + gridValue: { + size: CONSTANTS.gridConfig.size, + divisions: CONSTANTS.gridConfig.divisions, + }, + planeValue: { + height: CONSTANTS.planeConfig.height, + width: CONSTANTS.planeConfig.width, + }, - setGridValue: (value: any) => - set((state: any) => ({ - gridValue: { ...state.gridValue, ...value }, - })), + setGridValue: (value: any) => + set((state: any) => ({ + gridValue: { ...state.gridValue, ...value }, + })), - setPlaneValue: (value: any) => - set((state: any) => ({ - planeValue: { ...state.planeValue, ...value }, - })), + setPlaneValue: (value: any) => + set((state: any) => ({ + planeValue: { ...state.planeValue, ...value }, + })), })); export const usePlayAgv = create((set, get) => ({ - PlayAgv: [], - setPlayAgv: (updateFn: (prev: any[]) => any[]) => - set({ PlayAgv: updateFn(get().PlayAgv) }), + PlayAgv: [], + setPlayAgv: (updateFn: (prev: any[]) => any[]) => + set({ PlayAgv: updateFn(get().PlayAgv) }), })); // Define the Asset type type Asset = { - id: string; - name: string; - position?: [number, number, number]; // Optional: 3D position - rotation?: { x: number; y: number; z: number }; // Optional: Euler rotation + id: string; + name: string; + position?: [number, number, number]; // Optional: 3D position + rotation?: { x: number; y: number; z: number }; // Optional: Euler rotation }; // Zustand store type type ZoneAssetState = { - zoneAssetId: Asset | null; - setZoneAssetId: (asset: Asset | null) => void; + zoneAssetId: Asset | null; + setZoneAssetId: (asset: Asset | null) => void; }; // Zustand store export const useZoneAssetId = create((set) => ({ - zoneAssetId: null, - setZoneAssetId: (asset) => set({ zoneAssetId: asset }), + zoneAssetId: null, + setZoneAssetId: (asset) => set({ zoneAssetId: asset }), })); // version visible hidden interface VersionHistoryState { - viewVersionHistory: boolean; - setVersionHistoryVisible: (value: boolean) => void; + viewVersionHistory: boolean; + setVersionHistoryVisible: (value: boolean) => void; } const useVersionHistoryVisibleStore = create((set) => ({ - viewVersionHistory: false, - setVersionHistoryVisible: (value) => set({ viewVersionHistory: value }), + viewVersionHistory: false, + setVersionHistoryVisible: (value) => set({ viewVersionHistory: value }), })); export default useVersionHistoryVisibleStore; interface ShortcutStore { - showShortcuts: boolean; - setShowShortcuts: (value: boolean) => void; - toggleShortcuts: () => void; + showShortcuts: boolean; + setShowShortcuts: (value: boolean) => void; + toggleShortcuts: () => void; } export const useShortcutStore = create((set) => ({ - showShortcuts: false, - setShowShortcuts: (value) => set({ showShortcuts: value }), - toggleShortcuts: () => - set((state) => ({ showShortcuts: !state.showShortcuts })), + showShortcuts: false, + setShowShortcuts: (value) => set({ showShortcuts: value }), + toggleShortcuts: () => + set((state) => ({ showShortcuts: !state.showShortcuts })), })); export const useMachineCount = create((set: any) => ({ - machineCount: 0, - setMachineCount: (x: any) => set({ machineCount: x }), + machineCount: 0, + setMachineCount: (x: any) => set({ machineCount: x }), })); export const useMachineUptime = create((set: any) => ({ - machineActiveTime: 0, - setMachineActiveTime: (x: any) => set({ machineActiveTime: x }), + machineActiveTime: 0, + setMachineActiveTime: (x: any) => set({ machineActiveTime: x }), })); export const useMachineDowntime = create((set: any) => ({ - machineIdleTime: 0, - setMachineIdleTime: (x: any) => set({ machineIdleTime: x }), + machineIdleTime: 0, + setMachineIdleTime: (x: any) => set({ machineIdleTime: x }), })); export const useMaterialCycle = create((set: any) => ({ - materialCycleTime: 0, - setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }), + materialCycleTime: 0, + setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }), })); export const useThroughPutData = create((set: any) => ({ - throughputData: 0, - setThroughputData: (x: any) => set({ throughputData: x }), + throughputData: 0, + setThroughputData: (x: any) => set({ throughputData: x }), })); export const useProductionCapacityData = create((set: any) => ({ - productionCapacityData: 0, - setProductionCapacityData: (x: any) => set({ productionCapacityData: x }), + productionCapacityData: 0, + setProductionCapacityData: (x: any) => set({ productionCapacityData: x }), })); export const useProcessBar = create((set: any) => ({ - processBar: [], - setProcessBar: (x: any) => set({ processBar: x }), + processBar: [], + setProcessBar: (x: any) => set({ processBar: x }), })); export const useDfxUpload = create((set: any) => ({ - dfxuploaded: [], - dfxWallGenerate: [], - objValue: { x: 0, y: 0, z: 0 }, - setDfxUploaded: (x: any) => set({ dfxuploaded: x }), - setDxfWallGenerate: (x: any) => set({ dfxWallGenerate: x }), - setObjValue: (x: any) => set({ objValue: x }), + dfxuploaded: [], + dfxWallGenerate: [], + objValue: { x: 0, y: 0, z: 0 }, + setDfxUploaded: (x: any) => set({ dfxuploaded: x }), + setDxfWallGenerate: (x: any) => set({ dfxWallGenerate: x }), + setObjValue: (x: any) => set({ objValue: x }), })); type InputValuesStore = { - inputValues: Record; - setInputValues: (values: Record) => void; - updateInputValue: (label: string, value: string) => void; // <- New + inputValues: Record; + setInputValues: (values: Record) => void; + updateInputValue: (label: string, value: string) => void; // <- New }; export const useInputValues = create((set) => ({ - inputValues: {}, - setInputValues: (values) => set({ inputValues: values }), - updateInputValue: (label, value) => - set((state) => ({ - inputValues: { - ...state.inputValues, - [label]: value, - }, - })), + inputValues: {}, + setInputValues: (values) => set({ inputValues: values }), + updateInputValue: (label, value) => + set((state) => ({ + inputValues: { + ...state.inputValues, + [label]: value, + }, + })), })); export interface ROISummaryData { - productName: string; - roiPercentage: number; - paybackPeriod: number; - totalCost: number; - revenueGenerated: number; - netProfit: number; - netLoss: number; + productName: string; + roiPercentage: number; + paybackPeriod: number; + totalCost: number; + revenueGenerated: number; + netProfit: number; + netLoss: number; } interface ROISummaryStore { - roiSummary: ROISummaryData; - setRoiSummaryData: (values: ROISummaryData) => void; + roiSummary: ROISummaryData; + setRoiSummaryData: (values: ROISummaryData) => void; } export const useROISummaryData = create((set) => ({ - roiSummary: { - productName: "", - roiPercentage: 0, - paybackPeriod: 0, - totalCost: 0, - revenueGenerated: 0, - netProfit: 0, - netLoss: 0, - }, - setRoiSummaryData: (values) => set({ roiSummary: values }), + roiSummary: { + productName: "", + roiPercentage: 0, + paybackPeriod: 0, + totalCost: 0, + revenueGenerated: 0, + netProfit: 0, + netLoss: 0, + }, + setRoiSummaryData: (values) => set({ roiSummary: values }), })); interface CompareStore { - comparePopUp: boolean; - setComparePopUp: (value: boolean) => void; - toggleComparePopUp: () => void; + comparePopUp: boolean; + setComparePopUp: (value: boolean) => void; + toggleComparePopUp: () => void; } export const useCompareStore = create((set) => ({ - comparePopUp: false, - setComparePopUp: (value) => set({ comparePopUp: value }), - toggleComparePopUp: () => - set((state) => ({ comparePopUp: !state.comparePopUp })), + comparePopUp: false, + setComparePopUp: (value) => set({ comparePopUp: value }), + toggleComparePopUp: () => + set((state) => ({ comparePopUp: !state.comparePopUp })), })); // Save state store interface SaveVersionStore { - isVersionSaved: boolean; - setIsVersionSaved: (value: boolean) => void; + isVersionSaved: boolean; + setIsVersionSaved: (value: boolean) => void; } export const useSaveVersion = create((set) => ({ - isVersionSaved: false, - setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }), + isVersionSaved: false, + setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }), })); interface ViewSceneState { - viewSceneLabels: boolean; - setViewSceneLabels: (value: boolean | ((prev: boolean) => boolean)) => void; + viewSceneLabels: boolean; + setViewSceneLabels: (value: boolean | ((prev: boolean) => boolean)) => void; } export const useViewSceneStore = create((set) => ({ - viewSceneLabels: getInitialViewSceneLabels(), - setViewSceneLabels: (value) => { - set((state) => { - const newValue = - typeof value === "function" ? value(state.viewSceneLabels) : value; + viewSceneLabels: getInitialViewSceneLabels(), + setViewSceneLabels: (value) => { + set((state) => { + const newValue = + typeof value === "function" ? value(state.viewSceneLabels) : value; - // Store in localStorage manually - localStorage.setItem("viewSceneLabels", JSON.stringify(newValue)); + // Store in localStorage manually + localStorage.setItem("viewSceneLabels", JSON.stringify(newValue)); - return { viewSceneLabels: newValue }; - }); - }, + return { viewSceneLabels: newValue }; + }); + }, })); function getInitialViewSceneLabels(): boolean { - if (typeof window === "undefined") return false; // SSR safety - const saved = localStorage.getItem("viewSceneLabels"); - return saved ? JSON.parse(saved) : false; + if (typeof window === "undefined") return false; // SSR safety + const saved = localStorage.getItem("viewSceneLabels"); + return saved ? JSON.parse(saved) : false; } export interface CompareProduct { - productUuid: string; - productName: string; - simulationData: { - // costPerUnit: number; - // workingDaysPerYear: number; - // shiftLength: number; - // shiftsPerDay: number; - roiPercentage: number; - // paybackPeriod: number; - // totalCost: number; - // revenueGenerated: number; - netProfit: number; - productionCapacity: number; - paybackPeriod: number; - // netLoss: number; - machineIdleTime: number; - machineActiveTime: number; - throughputData: number; - }; + productUuid: string; + productName: string; + simulationData: { + // costPerUnit: number; + // workingDaysPerYear: number; + // shiftLength: number; + // shiftsPerDay: number; + roiPercentage: number; + // paybackPeriod: number; + // totalCost: number; + // revenueGenerated: number; + netProfit: number; + productionCapacity: number; + paybackPeriod: number; + // netLoss: number; + machineIdleTime: number; + machineActiveTime: number; + throughputData: number; + }; } export const useCompareProductDataStore = create<{ - compareProductsData: CompareProduct[]; - setCompareProductsData: (x: CompareProduct[]) => void; + compareProductsData: CompareProduct[]; + setCompareProductsData: (x: CompareProduct[]) => void; }>((set) => ({ - compareProductsData: [], - setCompareProductsData: (x) => set({ compareProductsData: x }), + compareProductsData: [], + setCompareProductsData: (x) => set({ compareProductsData: x }), })); export const useSelectedComment = create((set: any) => ({ - selectedComment: null, - setSelectedComment: (x: any) => set({ selectedComment: x }), - position2Dstate: {}, - setPosition2Dstate: (x: any) => set({ position2Dstate: x }), - commentPositionState: null, - setCommentPositionState: (x: any) => set({ commentPositionState: x }), + selectedComment: null, + setSelectedComment: (x: any) => set({ selectedComment: x }), + position2Dstate: {}, + setPosition2Dstate: (x: any) => set({ position2Dstate: x }), + commentPositionState: null, + setCommentPositionState: (x: any) => set({ commentPositionState: x }), })); export const useSelectedPath = create((set: any) => ({ - selectedPath: "auto", - setSelectedPath: (x: any) => set({ selectedPath: x }), + selectedPath: "auto", + setSelectedPath: (x: any) => set({ selectedPath: x }), })); diff --git a/app/src/store/simulation/useCraneStore.ts b/app/src/store/simulation/useCraneStore.ts new file mode 100644 index 0000000..d8389e2 --- /dev/null +++ b/app/src/store/simulation/useCraneStore.ts @@ -0,0 +1,263 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; + +interface CraneStore { + cranes: CraneStatus[]; + + addCrane: (productUuid: string, event: CraneEventSchema) => void; + removeCrane: (modelUuid: string) => void; + updateCrane: ( + modelUuid: string, + updates: Partial> + ) => void; + clearCranes: () => void; + + setCurrentPhase: (modelUuid: string, phase: string) => void; + + addCurrentAction: (modelUuid: string, actionUuid: string) => void; + removeCurrentAction: (modelUuid: string) => void; + + setCraneActive: (modelUuid: string, isActive: boolean) => void; + setCraneScheduled: (modelUuid: string, isScheduled: boolean) => void; + setCraneLoad: (modelUuid: string, load: number) => void; + setCraneState: (modelUuid: string, newState: CraneStatus["state"]) => void; + incrementCraneLoad: (modelUuid: string, incrementBy: number) => void; + decrementCraneLoad: (modelUuid: string, decrementBy: number) => 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; + getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; + clearCurrentMaterials: (modelUuid: string) => void; + + incrementActiveTime: (modelUuid: string, incrementBy: number) => void; + incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + resetTime: (modelUuid: string) => void; + + getCraneById: (modelUuid: string) => CraneStatus | undefined; + getCranesByProduct: (productUuid: string) => CraneStatus[]; + getActiveCranes: () => CraneStatus[]; +} + +export const createCraneStore = () => { + return create()( + immer((set, get) => ({ + cranes: [], + + addCrane: (productUuid, event) => { + set((state) => { + const exists = state.cranes.some(c => c.modelUuid === event.modelUuid); + if (!exists) { + state.cranes.push({ + ...event, + productUuid, + currentPhase: 'init', + isActive: false, + isScheduled: false, + idleTime: 0, + activeTime: 0, + currentLoad: 0, + currentMaterials: [] + }); + } + }); + }, + + removeCrane: (modelUuid) => { + set((state) => { + state.cranes = state.cranes.filter(c => c.modelUuid !== modelUuid); + }); + }, + + updateCrane: (modelUuid, updates) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + Object.assign(crane, updates); + } + }); + }, + + clearCranes: () => { + set((state) => { + state.cranes = []; + }); + }, + + setCurrentPhase: (modelUuid, phase) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentPhase = phase; + } + }); + }, + + addCurrentAction: (modelUuid, actionUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + const action = crane.point.actions.find(a => a.actionUuid === actionUuid); + if (action) { + crane.currentAction = { + actionUuid: action.actionUuid, + actionName: action.actionName + }; + } + } + }); + }, + + removeCurrentAction: (modelUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentAction = undefined; + } + }); + }, + + setCraneActive: (modelUuid, isActive) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.isActive = isActive; + } + }); + }, + + setCraneScheduled: (modelUuid, isScheduled) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.isScheduled = isScheduled; + } + }); + }, + + setCraneLoad: (modelUuid, load) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentLoad = load; + } + }); + }, + + setCraneState: (modelUuid, newState) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.state = newState; + } + }); + }, + + incrementCraneLoad: (modelUuid, incrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentLoad += incrementBy; + } + }); + }, + + decrementCraneLoad: (modelUuid, decrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentLoad = Math.max(0, crane.currentLoad - decrementBy); + } + }); + }, + + addCurrentMaterial: (modelUuid, materialType, materialId) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentMaterials.push({ materialType, materialId }); + } + }); + }, + + setCurrentMaterials: (modelUuid, materials) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentMaterials = materials; + } + }); + }, + + removeLastMaterial: (modelUuid) => { + let removed; + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane && crane.currentMaterials.length > 0) { + removed = JSON.parse(JSON.stringify(crane.currentMaterials.pop())); + } + }); + return removed; + }, + + getLastMaterial: (modelUuid) => { + const crane = get().cranes.find(c => c.modelUuid === modelUuid); + if (crane && crane.currentMaterials.length > 0) { + return crane.currentMaterials[crane.currentMaterials.length - 1]; + } + return undefined; + }, + + clearCurrentMaterials: (modelUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentMaterials = []; + } + }); + }, + + incrementActiveTime: (modelUuid, incrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.activeTime += incrementBy; + } + }); + }, + + incrementIdleTime: (modelUuid, incrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.idleTime += incrementBy; + } + }); + }, + + resetTime: (modelUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.activeTime = 0; + crane.idleTime = 0; + } + }); + }, + + getCraneById: (modelUuid) => { + return get().cranes.find(c => c.modelUuid === modelUuid); + }, + + getCranesByProduct: (productUuid) => { + return get().cranes.filter(c => c.productUuid === productUuid); + }, + + getActiveCranes: () => { + return get().cranes.filter(c => c.isActive); + } + })) + ); +}; + +export type CraneStoreType = ReturnType; \ No newline at end of file diff --git a/app/src/store/simulation/useEventsStore.ts b/app/src/store/simulation/useEventsStore.ts index becf55d..76661a8 100644 --- a/app/src/store/simulation/useEventsStore.ts +++ b/app/src/store/simulation/useEventsStore.ts @@ -11,24 +11,24 @@ type EventsStore = { updateEvent: (modelUuid: string, updates: Partial) => EventsSchema | undefined; // Point-level actions - addPoint: (modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void; + addPoint: (modelUuid: string, point: PointsScheme) => void; removePoint: (modelUuid: string, pointUuid: string) => void; updatePoint: ( modelUuid: string, pointUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Action-level actions addAction: ( modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] + action: ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction ) => void; removeAction: (actionUuid: string) => void; updateAction: ( actionUuid: string, - updates: Partial + updates: Partial ) => void; // Trigger-level actions @@ -38,8 +38,8 @@ type EventsStore = { // Helper functions getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined; - getPointByUuid: (modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined; - getActionByUuid: (actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined; + getPointByUuid: (modelUuid: string, pointUuid: string) => PointsScheme | undefined; + getActionByUuid: (actionUuid: string) => (ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction) | undefined; getTriggerByUuid: (triggerUuid: string) => TriggerSchema | undefined; }; diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 258a8cb..f9533a1 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -18,13 +18,13 @@ type ProductsStore = { updateEvent: (productUuid: string, modelUuid: string, updates: Partial) => EventsSchema | undefined; // Point-level actions - addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema) => EventsSchema | undefined; + addPoint: (productUuid: string, modelUuid: string, point: PointsScheme) => EventsSchema | undefined; removePoint: (productUuid: string, modelUuid: string, pointUuid: string) => EventsSchema | undefined; updatePoint: ( productUuid: string, modelUuid: string, pointUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Action-level actions @@ -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['actions'][0] + action: ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss @@ -65,9 +65,9 @@ type ProductsStore = { getEventByActionUuid: (productUuid: string, actionUuid: string) => EventsSchema | undefined; 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['actions'][0]) | undefined; - getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]) | undefined; + getPointByUuid: (productUuid: string, modelUuid: string, pointUuid: string) => PointsScheme | undefined; + getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction) | undefined; + getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction) | undefined; getModelUuidByPointUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getModelUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getPointUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; @@ -375,6 +375,15 @@ export const createProductStore = () => { return; } } + } else if (event.type === "crane") { + if ('actions' in point) { + const index = point.actions.findIndex((a: any) => a.actionUuid === actionUuid); + if (index !== -1) { + point.actions.splice(index, 1); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } } else if ('action' in point && point.action?.actionUuid === actionUuid) { point.action = undefined; updatedEvent = JSON.parse(JSON.stringify(event)); diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index 318050a..63ed97b 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -32,6 +32,7 @@ interface Asset { }; eventData?: { type: string; + subType: string; point?: { uuid: string; position: [number, number, number]; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 01a1603..e257ed8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -1,4 +1,5 @@ // Base Types + interface AssetEventSchema { modelUuid: string; modelName: string; @@ -19,7 +20,9 @@ interface TriggerSchema { } | null; } + // Actions + interface ConveyorAction { actionUuid: string; actionName: string; @@ -107,9 +110,19 @@ interface HumanAction { triggers: TriggerSchema[]; } -type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction; +interface CraneAction { + actionUuid: string; + actionName: string; + actionType: "pickAndDrop"; + maxPickUpCount: number; + triggers: TriggerSchema[]; +} + +type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction; + // Points + interface ConveyorPointSchema { uuid: string; position: [number, number, number]; @@ -152,46 +165,69 @@ interface HumanPointSchema { actions: HumanAction[]; } -type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; +interface CranePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: CraneAction[]; +} + +type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema | CranePointSchema; + // Events + interface ConveyorEventSchema extends AssetEventSchema { type: "transfer"; + subType: string; speed: number; points: ConveyorPointSchema[]; } interface VehicleEventSchema extends AssetEventSchema { type: "vehicle"; + subType: "manual" | "automatic" | "semiAutomatic" | ''; speed: number; point: VehiclePointSchema; } interface RoboticArmEventSchema extends AssetEventSchema { type: "roboticArm"; + subType: string; speed: number; point: RoboticArmPointSchema; } interface MachineEventSchema extends AssetEventSchema { type: "machine"; + subType: string; point: MachinePointSchema; } interface StorageEventSchema extends AssetEventSchema { type: "storageUnit"; + subType: string; point: StoragePointSchema; } interface HumanEventSchema extends AssetEventSchema { type: "human"; + subType: string; speed: number; point: HumanPointSchema; } -type EventsSchema = | ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | HumanEventSchema; +interface CraneEventSchema extends AssetEventSchema { + type: "crane"; + subType: "pillarJib" | ''; + point: CranePointSchema; +} + +type EventsSchema = | ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | HumanEventSchema | CraneEventSchema; + // Statuses + interface ConveyorStatus extends ConveyorEventSchema { productUuid: string; isActive: boolean; @@ -262,6 +298,24 @@ interface HumanStatus extends HumanEventSchema { }; } +interface CraneStatus extends CraneEventSchema { + productUuid: string; + currentPhase: string; + isActive: boolean; + isScheduled: boolean; + idleTime: number; + activeTime: number; + currentLoad: number; + currentMaterials: { materialType: string; materialId: string; }[]; + currentAction?: { + actionUuid: string; + actionName: string; + }; +} + + +// Event Manager + type HumanEventState = { humanId: string; actionQueue: { @@ -282,7 +336,9 @@ type HumanEventManagerState = { humanStates: HumanEventState[]; }; + // Materials + interface MaterialSchema { materialId: string; materialName: string; @@ -316,14 +372,18 @@ interface MaterialSchema { type MaterialsSchema = MaterialSchema[]; + // Products + type productsSchema = { productName: string; productUuid: string; eventDatas: EventsSchema[]; }[]; + // Material History + interface MaterialHistoryEntry { material: MaterialSchema; removedAt: string; @@ -331,7 +391,8 @@ interface MaterialHistoryEntry { type MaterialHistorySchema = MaterialHistoryEntry[]; -//IK + +// IK Constraints type Link = { index: number; @@ -350,3 +411,38 @@ type IK = { maxheight?: number; minheight?: number; }; + +// Conveyor Spline Points + +type NormalConveyor = { + type: 'normal'; + points: [number, number, number][][]; +} + +type YJunctionConveyor = { + type: 'y-junction'; + points: [number, number, number][][]; +} + +type CurvedConveyor = { + type: 'curved'; + points: [number, number, number][][]; +} + +type ConveyorPoints = NormalConveyor | YJunctionConveyor | CurvedConveyor; + + +// Crane Constraints + + +type PillarJibCrane = { + trolleySpeed: number; + hookSpeed: number; + rotationSpeed: number; + trolleyMinOffset: number + trolleyMaxOffset: number; + hookMinOffset: number; + hookMaxOffset: number; +} + +type CraneConstraints = PillarJibCrane; \ No newline at end of file diff --git a/app/src/types/world/worldTypes.d.ts b/app/src/types/world/worldTypes.d.ts index da92fd9..6b4552b 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -198,6 +198,7 @@ export type FloorItemType = { isVisible: boolean; eventData?: { type: string; + subType: string; point?: { uuid: string; position: [number, number, number]; @@ -221,7 +222,7 @@ export type setFloorItemSetState = React.Dispatch Date: Thu, 7 Aug 2025 14:53:20 +0530 Subject: [PATCH 16/38] pillar jib animator added --- .../instances/animator/pillarJibAnimator.tsx | 299 ++++++++++++------ .../instances/helper/pillarJibHelper.tsx | 77 +++++ .../instances/instance/pillarJibInstance.tsx | 17 +- 3 files changed, 290 insertions(+), 103 deletions(-) create mode 100644 app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 95a531c..04bfbba 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -1,82 +1,40 @@ +import { useEffect, useState } from 'react'; import * as THREE from 'three'; -import { useEffect, useMemo } from 'react'; -import { useThree } from '@react-three/fiber'; +import { useFrame, useThree } from '@react-three/fiber'; +import { Sphere, Box } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; -function PillarJibAnimator({ crane }: { crane: CraneStatus }) { +function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3]; }) { const { scene } = useThree(); + const { assetStore } = useSceneContext(); + const { resetAsset } = assetStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + + const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); + const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); + const [animationPhase, setAnimationPhase] = useState('idle'); // 0: idle, 1: init-hook-adjust, 2: 'rotate-base', 3: 'move-trolley', 4: 'final-hook-adjust' + + useEffect(() => { + if (!isPlaying) { + resetAsset(crane.modelUuid); + setAnimationPhase('idle'); + } else if (animationPhase === 'idle') { + setAnimationPhase('init-hook-adjust'); + } + }, [isPlaying, scene, crane.modelUuid]); useEffect(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); - - if (model) { - const base = model.getObjectByName('base'); - const trolley = model.getObjectByName('trolley'); - const hook = model.getObjectByName('hook'); - - if (base && trolley && hook) { - let trolleyDir = 1; - let hookDir = 1; - - const trolleySpeed = 0.01; - const hookSpeed = 0.01; - const rotationSpeed = 0.005; - - const trolleyMinOffset = -1; - const trolleyMaxOffset = 1.75; - const hookMinOffset = 0.25; - const hookMaxOffset = -1.5; - - const originalTrolleyX = trolley.position.x; - const originalHookY = hook.position.y; - - const animate = () => { - if (base) { - base.rotation.y += rotationSpeed; - } - - if (trolley) { - trolley.position.x += trolleyDir * trolleySpeed; - if (trolley.position.x >= originalTrolleyX + trolleyMaxOffset || - trolley.position.x <= originalTrolleyX + trolleyMinOffset) { - trolleyDir *= -1; - } - } - - if (hook) { - hook.position.y += hookDir * hookSpeed; - if (hook.position.y >= originalHookY + hookMinOffset || - hook.position.y <= originalHookY + hookMaxOffset) { - hookDir *= -1; - } - } - - requestAnimationFrame(animate); - }; - - animate(); - } - } - }, [crane, scene]); - - return ( - - ); -} - -export default PillarJibAnimator; - -function PillarJibHelper({ crane }: { crane: CraneStatus }) { - const { scene } = useThree(); - - const { geometry, position } = useMemo(() => { - const model = scene.getObjectByProperty('uuid', crane.modelUuid); - if (!model) return { geometry: null, position: null }; + if (!model) return; const base = model.getObjectByName('base'); const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook) return { geometry: null, position: null }; + if (!base || !trolley || !hook) return; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); @@ -87,47 +45,186 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) { const hookWorld = new THREE.Vector3(); hook.getWorldPosition(hookWorld); + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); - const outerRadius = distFromBase + 1.75; - const innerRadius = Math.max(distFromBase - 1, 0.05); - const height = (0.25 - (-1.5)); - const cylinderYPosition = hookWorld.y + (height / 2) + (-1.5 + 0.25) / 2; + const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); + const outerRadius = Math.max( + new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length() + trolleyMaxOffset, + innerRadius + ); - const shape = new THREE.Shape(); - shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + const yMin = hookWorld.y + hookMaxOffset; + const yMax = hookWorld.y + hookMinOffset; - const hole = new THREE.Path(); - hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); - shape.holes.push(hole); + function clampToCylinder(pos: THREE.Vector3) { + const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z); + const distance = xzDist.length(); - const extrudeSettings = { - depth: height, - bevelEnabled: false, - steps: 1 - }; + let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius); + if (distance > 0) { + clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius)); + } - const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); - const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance); + const y = THREE.MathUtils.clamp(pos.y, yMin, yMax); - return { geometry, position }; - }, [scene, crane.modelUuid]); + return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y); + } - if (!geometry || !position) return null; + const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()]; + const newIsInside: [boolean, boolean] = [false, false]; + + points.forEach((point, i) => { + const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length(); + const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius; + const insideY = point.y >= yMin && point.y <= yMax; + newIsInside[i] = insideXZ && insideY; + + newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point); + }); + + setClampedPoints(newClampedPoints); + setIsInside(newIsInside); + }, [scene, points, crane.modelUuid]); + + useFrame(() => { + if (!isPlaying || isPaused || !points || animationPhase === 'idle') return; + + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook || !clampedPoints || !trolley.parent) return; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + if (!model.userData.animationData) { + model.userData.animationData = { + originalHookY: hook.position.y, + targetHookY: points[0].y - baseWorld.y + 0.5, + targetDirection: new THREE.Vector2(), + targetTrolleyX: 0, + targetWorldPosition: points[0].clone(), + finalHookTargetY: 0, + }; + } + + const { animationData } = model.userData; + const hookSpeed = 0.01 * speed; + const rotationSpeed = 0.005 * speed; + const trolleySpeed = 0.01 * speed; + + switch (animationPhase) { + case 'init-hook-adjust': { + const direction = Math.sign(animationData.targetHookY - hook.position.y); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { + hook.position.y = animationData.targetHookY; + setAnimationPhase('rotate-base'); + } + break; + } + + case 'rotate-base': { + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const baseForward = new THREE.Vector3(1, 0, 0); + base.localToWorld(baseForward); + baseForward.sub(baseWorld); + + const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); + + const targetWorld = clampedPoints[0]; + const targetDir = new THREE.Vector2( + targetWorld.x - baseWorld.x, + targetWorld.z - baseWorld.z + ).normalize(); + + const currentAngle = Math.atan2(currentDir.y, currentDir.x); + const targetAngle = Math.atan2(targetDir.y, targetDir.x); + let angleDiff = currentAngle - targetAngle; + + angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff)); + + if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) { + base.rotation.y += Math.sign(angleDiff) * rotationSpeed; + } else { + base.rotation.y += angleDiff; + const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); + animationData.targetTrolleyX = localTarget?.x; + setAnimationPhase('move-trolley'); + } + + break; + } + + case 'move-trolley': { + const dx = animationData.targetTrolleyX - trolley.position.x; + const direction = Math.sign(dx); + trolley.position.x += direction * trolleySpeed; + + if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { + trolley.position.x = animationData.targetTrolleyX; + + animationData.finalHookTargetY = hook.position.y - 0.5; + setAnimationPhase('final-hook-adjust'); + } + break; + } + + case 'final-hook-adjust': { + const dy = animationData.finalHookTargetY - hook.position.y; + const direction = Math.sign(dy); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { + hook.position.y = animationData.finalHookTargetY; + setAnimationPhase('idle'); + console.log('hii'); + } + break; + } + } + }); + + if (!clampedPoints) return null; return ( - - - + <> + {points.map((point, i) => ( + + + + ))} + + {clampedPoints.map((point, i) => ( + + + + ))} + ); } + +export default PillarJibAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx new file mode 100644 index 0000000..945cf2f --- /dev/null +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -0,0 +1,77 @@ +import { useMemo } from "react"; +import * as THREE from "three"; +import { useThree } from "@react-three/fiber"; + +function PillarJibHelper({ crane }: { crane: CraneStatus }) { + const { scene } = useThree(); + + const { geometry, position } = useMemo(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return { geometry: null, position: null }; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook) return { geometry: null, position: null }; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const trolleyWorld = new THREE.Vector3(); + trolley.getWorldPosition(trolleyWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); + const outerRadius = distFromBase + trolleyMaxOffset; + const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); + const height = (hookMinOffset - (hookMaxOffset)); + const cylinderYPosition = hookWorld.y + (height / 2) + (hookMaxOffset + hookMinOffset) / 2; + + const shape = new THREE.Shape(); + shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + + const hole = new THREE.Path(); + hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); + shape.holes.push(hole); + + const extrudeSettings = { + depth: height, + bevelEnabled: false, + steps: 1 + }; + + const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); + const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + + return { geometry, position }; + }, [scene, crane.modelUuid]); + + if (!geometry || !position) return null; + + return ( + + + + ); +} + +export default PillarJibHelper; \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index bef6a17..a736a24 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,14 +1,27 @@ +import * as THREE from 'three' import PillarJibAnimator from '../animator/pillarJibAnimator' +import PillarJibHelper from '../helper/pillarJibHelper' function PillarJibInstance({ crane }: { crane: CraneStatus }) { + const position1: [number, number, number] = [5, 1, -4]; + const position2: [number, number, number] = [-2, 2, -2]; + return ( <> - + + + ) } -export default PillarJibInstance \ No newline at end of file +export default PillarJibInstance; \ No newline at end of file From 54740ffbfcc2b316d22b726eaf2f5c69b960f3bd Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 7 Aug 2025 14:53:38 +0530 Subject: [PATCH 17/38] fix --- .../simulation/crane/instances/animator/pillarJibAnimator.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 04bfbba..8c28254 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -193,7 +193,6 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { hook.position.y = animationData.finalHookTargetY; setAnimationPhase('idle'); - console.log('hii'); } break; } From 2760e73ab8393a9627b1ffff2ec38961019dd99b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 7 Aug 2025 16:49:14 +0530 Subject: [PATCH 18/38] crane animator --- .../instances/animator/pillarJibAnimator.tsx | 68 +++++++++++++------ .../crane/instances/craneInstances.tsx | 2 +- .../instances/instance/pillarJibInstance.tsx | 18 +++++ 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 8c28254..d287d04 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -5,7 +5,19 @@ import { Sphere, Box } from '@react-three/drei'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; -function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3]; }) { +function PillarJibAnimator({ + crane, + points, + animationPhase, + setAnimationPhase, + onAnimationComplete +}: { + crane: CraneStatus; + points: [THREE.Vector3, THREE.Vector3]; + animationPhase: string; + setAnimationPhase: (phase: string) => void; + onAnimationComplete: (action: string) => void; +}) { const { scene } = useThree(); const { assetStore } = useSceneContext(); const { resetAsset } = assetStore(); @@ -15,12 +27,13 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); - const [animationPhase, setAnimationPhase] = useState('idle'); // 0: idle, 1: init-hook-adjust, 2: 'rotate-base', 3: 'move-trolley', 4: 'final-hook-adjust' + const [currentTargetIndex, setCurrentTargetIndex] = useState(0); useEffect(() => { if (!isPlaying) { resetAsset(crane.modelUuid); setAnimationPhase('idle'); + setCurrentTargetIndex(0); } else if (animationPhase === 'idle') { setAnimationPhase('init-hook-adjust'); } @@ -89,10 +102,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR setClampedPoints(newClampedPoints); setIsInside(newIsInside); - }, [scene, points, crane.modelUuid]); + }, [crane.modelUuid]); useFrame(() => { - if (!isPlaying || isPaused || !points || animationPhase === 'idle') return; + if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return; const model = scene.getObjectByProperty('uuid', crane.modelUuid); if (!model) return; @@ -101,7 +114,7 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook || !clampedPoints || !trolley.parent) return; + if (!base || !trolley || !hook || !trolley.parent) return; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); @@ -112,10 +125,10 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR if (!model.userData.animationData) { model.userData.animationData = { originalHookY: hook.position.y, - targetHookY: points[0].y - baseWorld.y + 0.5, + targetHookY: clampedPoints[currentTargetIndex].y - baseWorld.y + 0.5, targetDirection: new THREE.Vector2(), targetTrolleyX: 0, - targetWorldPosition: points[0].clone(), + targetWorldPosition: clampedPoints[currentTargetIndex].clone(), finalHookTargetY: 0, }; } @@ -132,22 +145,19 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { hook.position.y = animationData.targetHookY; - setAnimationPhase('rotate-base'); + setAnimationPhase('init-rotate-base'); } break; } - case 'rotate-base': { - const baseWorld = new THREE.Vector3(); - base.getWorldPosition(baseWorld); - + case 'init-rotate-base': { const baseForward = new THREE.Vector3(1, 0, 0); base.localToWorld(baseForward); baseForward.sub(baseWorld); const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); - const targetWorld = clampedPoints[0]; + const targetWorld = clampedPoints[currentTargetIndex]; const targetDir = new THREE.Vector2( targetWorld.x - baseWorld.x, targetWorld.z - baseWorld.z @@ -163,15 +173,14 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR base.rotation.y += Math.sign(angleDiff) * rotationSpeed; } else { base.rotation.y += angleDiff; - const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); + const localTarget = trolley.parent.worldToLocal(clampedPoints[currentTargetIndex].clone()); animationData.targetTrolleyX = localTarget?.x; - setAnimationPhase('move-trolley'); + setAnimationPhase('init-move-trolley'); } - break; } - case 'move-trolley': { + case 'init-move-trolley': { const dx = animationData.targetTrolleyX - trolley.position.x; const direction = Math.sign(dx); trolley.position.x += direction * trolleySpeed; @@ -180,19 +189,38 @@ function PillarJibAnimator({ crane, points }: { crane: CraneStatus; points: [THR trolley.position.x = animationData.targetTrolleyX; animationData.finalHookTargetY = hook.position.y - 0.5; - setAnimationPhase('final-hook-adjust'); + setAnimationPhase('init-final-hook-adjust'); } break; } - case 'final-hook-adjust': { + case 'init-final-hook-adjust': { const dy = animationData.finalHookTargetY - hook.position.y; const direction = Math.sign(dy); hook.position.y += direction * hookSpeed; if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { hook.position.y = animationData.finalHookTargetY; - setAnimationPhase('idle'); + + if (currentTargetIndex < points.length - 1) { + setCurrentTargetIndex(currentTargetIndex + 1); + + model.userData.animationData = { + originalHookY: hook.position.y, + targetHookY: clampedPoints[currentTargetIndex + 1].y - baseWorld.y + 0.5, + targetDirection: new THREE.Vector2(), + targetTrolleyX: 0, + targetWorldPosition: clampedPoints[currentTargetIndex + 1].clone(), + finalHookTargetY: 0, + }; + + setAnimationPhase('picking'); + onAnimationComplete('picking'); + + } else { + setAnimationPhase('dropping'); + onAnimationComplete('dropping'); + } } break; } diff --git a/app/src/modules/simulation/crane/instances/craneInstances.tsx b/app/src/modules/simulation/crane/instances/craneInstances.tsx index bd6a96b..240062c 100644 --- a/app/src/modules/simulation/crane/instances/craneInstances.tsx +++ b/app/src/modules/simulation/crane/instances/craneInstances.tsx @@ -12,7 +12,7 @@ function CraneInstances() { {crane.subType === "pillarJib" && - + } diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index a736a24..d6b8fd8 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,21 +1,39 @@ +import { useState } from 'react'; import * as THREE from 'three' import PillarJibAnimator from '../animator/pillarJibAnimator' import PillarJibHelper from '../helper/pillarJibHelper' function PillarJibInstance({ crane }: { crane: CraneStatus }) { + const [animationPhase, setAnimationPhase] = useState('idle'); const position1: [number, number, number] = [5, 1, -4]; const position2: [number, number, number] = [-2, 2, -2]; + const handleAnimationComplete = (action: string) => { + if (action === 'picking') { + setTimeout(() => { + setAnimationPhase('init-hook-adjust'); + }, 3000) + } else if (action === 'dropping') { + setTimeout(() => { + setAnimationPhase('idle'); + }, 3000) + } + } + return ( <> From a7a55bf1379d2d9be134c243ecf20032ae84709c Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 7 Aug 2025 18:11:11 +0530 Subject: [PATCH 19/38] added crane mechanics ui and dblclick zoom effect chnage --- .../eventProperties/EventProperties.tsx | 4 + .../mechanics/craneMechanics.tsx | 210 ++++++++++++++++++ .../model/eventHandlers/useEventHandlers.ts | 56 +++-- .../instances/animator/pillarJibAnimator.tsx | 24 -- .../instances/helper/pillarJibHelper.tsx | 96 ++++++-- .../instances/instance/pillarJibInstance.tsx | 8 +- 6 files changed, 340 insertions(+), 58 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index 252e44d..a6332db 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -15,6 +15,7 @@ import { useProductContext } from "../../../../../modules/simulation/products/pr import { useParams } from "react-router-dom"; import { useVersionContext } from "../../../../../modules/builder/version/versionContext"; import { useSceneContext } from "../../../../../modules/scene/sceneContext"; +import CraneMechanics from "./mechanics/craneMechanics"; const EventProperties: React.FC = () => { const { selectedEventData } = useSelectedEventData(); @@ -63,6 +64,8 @@ const EventProperties: React.FC = () => { return "storageUnit"; case "human": return "human"; + case "crane": + return "crane"; default: return null; } @@ -83,6 +86,7 @@ const EventProperties: React.FC = () => { {assetType === "machine" && } {assetType === "storageUnit" && } {assetType === "human" && } + {assetType === "crane" && } )} {!currentEventData && selectedEventSphere && ( diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx new file mode 100644 index 0000000..f5d1229 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/craneMechanics.tsx @@ -0,0 +1,210 @@ +import { useEffect, useState } from "react"; +import { MathUtils } from "three"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import ActionsList from "../components/ActionsList"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; +import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; +import { useParams } from "react-router-dom"; +import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; + +function CraneMechanics() { + const [activeOption, setActiveOption] = useState<"pickAndDrop">("pickAndDrop"); + const [selectedPointData, setSelectedPointData] = useState(); + + const { selectedEventData } = useSelectedEventData(); + const { productStore } = useSceneContext(); + const { getPointByUuid, updateAction, addAction, removeAction } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as CranePointSchema | undefined; + + if (point?.actions) { + setSelectedPointData(point); + + if (point.actions.length > 0) { + const firstAction = point.actions[0]; + setActiveOption(firstAction.actionType); + setSelectedAction(firstAction.actionUuid, firstAction.actionName); + } + } + } else { + clearSelectedAction(); + } + }, [selectedEventData, selectedProduct]); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName, + productUuid, + projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || "", + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { actionName: newName } + ); + + const updatedActions = selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions, + }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + }; + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "pickAndDrop" as const, + maxPickUpCount: 1, + triggers: [] as TriggerSchema[], + }; + + const event = addAction( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + + setSelectedPointData({ + ...selectedPointData, + actions: [...selectedPointData.actions, newAction], + }); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + const event = removeAction( + selectedProduct.productUuid, + actionUuid + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + + const index = selectedPointData.actions.findIndex(a => a.actionUuid === actionUuid); + const newActions = selectedPointData.actions.filter(a => a.actionUuid !== actionUuid); + + setSelectedPointData({ + ...selectedPointData, + actions: newActions, + }); + + if (selectedAction.actionId === actionUuid) { + const nextAction = newActions[index] || newActions[index - 1]; + if (nextAction) { + setSelectedAction(nextAction.actionUuid, nextAction.actionName); + } else { + clearSelectedAction(); + } + } + }; + + const availableActions = { + defaultOption: "pickAndDrop", + options: ["pickAndDrop"], + }; + + const currentAction = selectedPointData?.actions.find(a => a.actionUuid === selectedAction.actionId); + + return ( + <> +
+ + + {selectedAction.actionId && currentAction && ( +
+
+ +
+
+ { }} + disabled={true} + /> +
+
+ +
+
+ )} +
+ + ); +} + +export default CraneMechanics \ No newline at end of file diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts index c08144c..a0ea33a 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -24,7 +24,7 @@ export function useModelEventHandlers({ boundingBox: THREE.Box3 | null, groupRef: React.RefObject, }) { - const { controls, gl } = useThree(); + const { controls, gl, camera } = useThree(); const { activeTool } = useActiveTool(); const { activeModule } = useModuleStore(); const { toggleView } = useToggleView(); @@ -68,25 +68,47 @@ export function useModelEventHandlers({ const handleDblClick = (asset: Asset) => { if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { - const size = boundingBox.getSize(new THREE.Vector3()); - const center = boundingBox.getCenter(new THREE.Vector3()); - const front = new THREE.Vector3(0, 0, 1); - groupRef.current.localToWorld(front); - front.sub(groupRef.current.position).normalize(); + const frontView = false; - const distance = Math.max(size.x, size.y, size.z) * 2; - const newPosition = center.clone().addScaledVector(front, distance); + if (frontView) { + const size = boundingBox.getSize(new THREE.Vector3()); + const center = boundingBox.getCenter(new THREE.Vector3()); - (controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true); - (controls as CameraControls).setTarget(center.x, center.y, center.z, true); - (controls as CameraControls).fitToBox(groupRef.current, true, { - cover: true, - paddingTop: 5, - paddingLeft: 5, - paddingBottom: 5, - paddingRight: 5, - }); + const front = new THREE.Vector3(0, 0, 1); + groupRef.current.localToWorld(front); + front.sub(groupRef.current.position).normalize(); + + const distance = Math.max(size.x, size.y, size.z) * 2; + const newPosition = center.clone().addScaledVector(front, distance); + + (controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true); + (controls as CameraControls).setTarget(center.x, center.y, center.z, true); + (controls as CameraControls).fitToBox(groupRef.current, true, { + cover: true, + paddingTop: 5, + paddingLeft: 5, + paddingBottom: 5, + paddingRight: 5, + }); + } else { + + const collisionPos = new THREE.Vector3(); + groupRef.current.getWorldPosition(collisionPos); + + const currentPos = new THREE.Vector3().copy(camera.position); + + const target = new THREE.Vector3(); + if (!controls) return; + (controls as CameraControls).getTarget(target); + const direction = new THREE.Vector3().subVectors(target, currentPos).normalize(); + + const offsetDistance = 5; + const newCameraPos = new THREE.Vector3().copy(collisionPos).sub(direction.multiplyScalar(offsetDistance)); + + camera.position.copy(newCameraPos); + (controls as CameraControls).setLookAt(newCameraPos.x, newCameraPos.y, newCameraPos.z, collisionPos.x, 0, collisionPos.z, true); + } setSelectedFloorItem(groupRef.current); } diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index d287d04..9c7f3ab 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; -import { Sphere, Box } from '@react-three/drei'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; @@ -26,7 +25,6 @@ function PillarJibAnimator({ const { speed } = useAnimationPlaySpeed(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); - const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); const [currentTargetIndex, setCurrentTargetIndex] = useState(0); useEffect(() => { @@ -101,7 +99,6 @@ function PillarJibAnimator({ }); setClampedPoints(newClampedPoints); - setIsInside(newIsInside); }, [crane.modelUuid]); useFrame(() => { @@ -227,29 +224,8 @@ function PillarJibAnimator({ } }); - if (!clampedPoints) return null; - return ( <> - {points.map((point, i) => ( - - - - ))} - - {clampedPoints.map((point, i) => ( - - - - ))} ); } diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx index 945cf2f..84883fa 100644 --- a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -1,9 +1,18 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; +import { Box, Sphere } from "@react-three/drei"; -function PillarJibHelper({ crane }: { crane: CraneStatus }) { +function PillarJibHelper({ + crane, + points +}: { + crane: CraneStatus, + points: [THREE.Vector3, THREE.Vector3]; +}) { const { scene } = useThree(); + const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); + const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); const { geometry, position } = useMemo(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); @@ -51,26 +60,81 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) { const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + const yMin = hookWorld.y + hookMaxOffset; + const yMax = hookWorld.y + hookMinOffset; + + function clampToCylinder(pos: THREE.Vector3) { + const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z); + const distance = xzDist.length(); + + let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius); + if (distance > 0) { + clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius)); + } + + const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance); + const y = THREE.MathUtils.clamp(pos.y, yMin, yMax); + + return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y); + } + + const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()]; + const newIsInside: [boolean, boolean] = [false, false]; + + points.forEach((point, i) => { + const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length(); + const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius; + const insideY = point.y >= yMin && point.y <= yMax; + newIsInside[i] = insideXZ && insideY; + + newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point); + }); + + setClampedPoints(newClampedPoints); + setIsInside(newIsInside); + return { geometry, position }; }, [scene, crane.modelUuid]); if (!geometry || !position) return null; return ( - - - + <> + + + + + {points.map((point, i) => ( + + + + ))} + + {clampedPoints && clampedPoints.map((point, i) => ( + + + + ))} + ); } diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index d6b8fd8..bf0fa43 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -36,7 +36,13 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { onAnimationComplete={handleAnimationComplete} /> - + {/* */} ) From fcc5fa64e929eb151568966d109cd4324692374b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 8 Aug 2025 18:05:25 +0530 Subject: [PATCH 20/38] added event handler --- .../actions/pillarJibAction.tsx | 39 ++++++++ .../mechanics/humanMechanics.tsx | 4 +- .../transformControls/transformControls.tsx | 4 +- app/src/modules/scene/scene.tsx | 2 +- app/src/modules/scene/sceneContext.tsx | 4 + .../actionHandler/usePickAndDropHandler.ts | 37 +++++++ .../actions/crane/useCraneActions.ts | 36 +++++++ .../simulation/actions/useActionHandler.ts | 10 +- .../eventManager/useCraneEventManager.ts | 99 +++++++++++++++++++ .../instances/animator/pillarJibAnimator.tsx | 4 +- .../instances/instance/pillarJibInstance.tsx | 32 ++++-- .../triggerHandler/useTriggerHandler.ts | 87 +++++++++++++++- app/src/types/simulationTypes.d.ts | 17 ++++ 13 files changed, 359 insertions(+), 16 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx create mode 100644 app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts create mode 100644 app/src/modules/simulation/actions/crane/useCraneActions.ts create mode 100644 app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx new file mode 100644 index 0000000..e7ff2c5 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; + +interface WorkerActionProps { + loadCount: { + value: number; + min: number; + max: number; + step: number; + defaultValue: string, + disabled: false, + onChange: (value: number) => void; + }; +} + +const WorkerAction: React.FC = ({ + loadCount +}) => { + return ( +
+ {loadCount && ( + { }} + onChange={(value) => loadCount.onChange(parseInt(value))} + /> + )} +
+ ); +}; + +export default WorkerAction; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index fc73e58..9d8e393 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -6,8 +6,8 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import ActionsList from "../components/ActionsList"; -import WorkerAction from "../actions/workerAction"; -import AssemblyAction from "../actions/assemblyAction"; +import WorkerAction from "../actions/WorkerAction"; +import AssemblyAction from "../actions/AssemblyAction"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; diff --git a/app/src/modules/scene/controls/transformControls/transformControls.tsx b/app/src/modules/scene/controls/transformControls/transformControls.tsx index e0ce1cf..6d5e7d5 100644 --- a/app/src/modules/scene/controls/transformControls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls/transformControls.tsx @@ -3,7 +3,7 @@ import * as THREE from "three"; import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store"; import { useThree } from "@react-three/fiber"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys"; import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; // import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; @@ -15,6 +15,7 @@ import { useVersionContext } from "../../../builder/version/versionContext"; export default function TransformControl() { const state = useThree(); + const ref = useRef(null); const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { setObjectPosition } = useObjectPosition(); @@ -186,6 +187,7 @@ export default function TransformControl() { <> {(selectedFloorItem && transformMode) && { e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d); }} - gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} + gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true }} > diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index ac17198..6dbfce8 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -45,6 +45,7 @@ type SceneContextValue = { craneStore: CraneStoreType; humanEventManagerRef: React.RefObject; + craneEventManagerRef: React.RefObject; clearStores: () => void; @@ -83,6 +84,7 @@ export function SceneProvider({ const craneStore = useMemo(() => createCraneStore(), []); const humanEventManagerRef = useRef({ humanStates: [] }); + const craneEventManagerRef = useRef({ craneStates: [] }); const clearStores = useMemo(() => () => { assetStore.getState().clearAssets(); @@ -103,6 +105,7 @@ export function SceneProvider({ humanStore.getState().clearHumans(); craneStore.getState().clearCranes(); humanEventManagerRef.current.humanStates = []; + craneEventManagerRef.current.craneStates = []; }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); const contextValue = useMemo(() => ( @@ -125,6 +128,7 @@ export function SceneProvider({ humanStore, craneStore, humanEventManagerRef, + craneEventManagerRef, clearStores, layout } diff --git a/app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts b/app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts new file mode 100644 index 0000000..48896f4 --- /dev/null +++ b/app/src/modules/simulation/actions/crane/actionHandler/usePickAndDropHandler.ts @@ -0,0 +1,37 @@ +import { useCallback } from "react"; +import { useSceneContext } from "../../../../scene/sceneContext"; +import { useProductContext } from "../../../products/productContext"; + +export function usePickAndDropHandler() { + const { materialStore, craneStore, productStore } = useSceneContext(); + const { getMaterialById } = materialStore(); + const { getModelUuidByActionUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { incrementCraneLoad, addCurrentMaterial, addCurrentAction } = craneStore(); + + const pickAndDropLogStatus = (materialUuid: string, status: string) => { + echo.info(`${materialUuid}, ${status}`); + } + + const handlePickAndDrop = useCallback((action: CraneAction, materialId?: string) => { + if (!action || action.actionType !== 'pickAndDrop' || !materialId) return; + + const material = getMaterialById(materialId); + if (!material) return; + + const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); + if (!modelUuid) return; + + incrementCraneLoad(modelUuid, 1); + addCurrentAction(modelUuid, action.actionUuid); + addCurrentMaterial(modelUuid, material.materialType, material.materialId); + + pickAndDropLogStatus(material.materialName, `performing pickAndDrop action`); + + }, [getMaterialById]); + + return { + handlePickAndDrop, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/crane/useCraneActions.ts b/app/src/modules/simulation/actions/crane/useCraneActions.ts new file mode 100644 index 0000000..aa7de6f --- /dev/null +++ b/app/src/modules/simulation/actions/crane/useCraneActions.ts @@ -0,0 +1,36 @@ +import { useEffect, useCallback } from 'react'; +import { usePickAndDropHandler } from './actionHandler/usePickAndDropHandler'; + +export function useCraneActions() { + const { handlePickAndDrop } = usePickAndDropHandler(); + + const handleWorkerAction = useCallback((action: CraneAction, materialId: string) => { + handlePickAndDrop(action, materialId); + }, [handlePickAndDrop]); + + const handleCraneAction = useCallback((action: CraneAction, materialId: string) => { + if (!action) return; + + switch (action.actionType) { + case 'pickAndDrop': + handleWorkerAction(action, materialId); + break; + default: + console.warn(`Unknown Human action type: ${action.actionType}`); + } + }, [handleWorkerAction]); + + const cleanup = useCallback(() => { + }, []); + + useEffect(() => { + return () => { + cleanup(); + }; + }, [cleanup]); + + return { + handleCraneAction, + cleanup + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts index 7352ae1..99d8225 100644 --- a/app/src/modules/simulation/actions/useActionHandler.ts +++ b/app/src/modules/simulation/actions/useActionHandler.ts @@ -8,6 +8,7 @@ import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions"; import { useStorageActions } from "./storageUnit/useStorageUnitActions"; import { useVehicleActions } from "./vehicle/useVehicleActions"; import { useHumanActions } from "./human/useHumanActions"; +import { useCraneActions } from "./crane/useCraneActions"; import { useCallback, useEffect } from "react"; export function useActionHandler() { @@ -19,6 +20,7 @@ export function useActionHandler() { const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions(); const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions(); const { handleHumanAction, cleanup: cleanupHuman } = useHumanActions(); + const { handleCraneAction, cleanup: cleanupCrane } = useCraneActions(); const handleAction = useCallback((action: Action, materialId?: string) => { if (!action) return; @@ -42,6 +44,9 @@ export function useActionHandler() { case 'worker': case 'assembly': handleHumanAction(action as HumanAction, materialId as string); break; + case 'pickAndDrop': + handleCraneAction(action as CraneAction, materialId as string); + break; default: console.warn(`Unknown action type: ${(action as Action).actionType}`); } @@ -49,7 +54,7 @@ export function useActionHandler() { echo.error("Failed to handle action"); console.error("Error handling action:", error); } - }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction]); + }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction, handleCraneAction]); const cleanup = useCallback(() => { cleanupConveyor(); @@ -58,7 +63,8 @@ export function useActionHandler() { cleanupMachine(); cleanupStorage(); cleanupHuman(); - }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman]); + cleanupCrane(); + }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman, cleanupCrane]); useEffect(() => { return () => { diff --git a/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts new file mode 100644 index 0000000..f8d40d9 --- /dev/null +++ b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts @@ -0,0 +1,99 @@ +import { useEffect } from 'react'; +import { useFrame } from '@react-three/fiber'; +import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../scene/sceneContext'; +import { useProductContext } from '../../products/productContext'; + +export function useCraneEventManager() { + const { craneStore, productStore, assetStore, craneEventManagerRef } = useSceneContext(); + const { getCraneById, setCurrentPhase, removeCurrentAction } = craneStore(); + const { getAssetById } = assetStore(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + + const { isPlaying } = usePlayButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { isReset } = useResetButtonStore(); + + useEffect(() => { + if ((isReset || !isPlaying) && craneEventManagerRef.current) { + craneEventManagerRef.current.craneStates = []; + } + }, [isReset, isPlaying]); + + const addCraneToMonitor = (craneId: string, callback: () => void, actionUuid: string) => { + const crane = getCraneById(craneId); + const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as CraneAction | undefined; + + if (!crane || !action || action.actionType !== 'pickAndDrop' || !craneEventManagerRef.current) return; + + let state = craneEventManagerRef.current.craneStates.find(c => c.craneId === craneId); + if (!state) { + state = { + craneId, + pendingActions: [], + currentAction: null, + isProcessing: false + }; + craneEventManagerRef.current.craneStates.push(state); + } + + state.pendingActions.push({ + actionUuid, + callback + }); + + if (!state.isProcessing) { + processNextAction(state); + } + }; + + const processNextAction = (state: CraneEventState) => { + if (state.pendingActions.length === 0) { + state.currentAction = null; + return; + } + + state.isProcessing = true; + state.currentAction = state.pendingActions.shift() || null; + }; + + const completeCurrentAction = (state: CraneEventState) => { + processNextAction(state); + }; + + useFrame(() => { + if (!craneEventManagerRef.current || craneEventManagerRef.current.craneStates.length === 0 || !isPlaying || isPaused) return; + + for (const craneState of craneEventManagerRef.current.craneStates) { + if (!craneState.currentAction || !craneState.isProcessing) continue; + + const { craneId, currentAction } = craneState; + const crane = getCraneById(craneId); + const craneAsset = getAssetById(craneId); + const currentCraneAction = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '') as CraneAction | undefined; + + if (!crane || !craneAsset || !currentCraneAction) continue; + if (crane.isActive || crane.state !== "idle") continue; + + if (currentCraneAction.actionType === 'pickAndDrop' && crane.currentLoad < currentCraneAction.maxPickUpCount) { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } + }, 0); + + return { + addCraneToMonitor + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 9c7f3ab..fefad50 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -12,7 +12,7 @@ function PillarJibAnimator({ onAnimationComplete }: { crane: CraneStatus; - points: [THREE.Vector3, THREE.Vector3]; + points: [THREE.Vector3, THREE.Vector3] | null; animationPhase: string; setAnimationPhase: (phase: string) => void; onAnimationComplete: (action: string) => void; @@ -45,7 +45,7 @@ function PillarJibAnimator({ const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook) return; + if (!base || !trolley || !hook || !points) return; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index bf0fa43..9c16e78 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -1,13 +1,34 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import * as THREE from 'three' +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; + import PillarJibAnimator from '../animator/pillarJibAnimator' import PillarJibHelper from '../helper/pillarJibHelper' function PillarJibInstance({ crane }: { crane: CraneStatus }) { + const { isPlaying } = usePlayButtonStore(); + const { craneStore, productStore } = useSceneContext(); + const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { getCraneById } = craneStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const [currentPhase, setCurrentPhase] = useState('idle'); const [animationPhase, setAnimationPhase] = useState('idle'); + const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null); - const position1: [number, number, number] = [5, 1, -4]; - const position2: [number, number, number] = [-2, 2, -2]; + useEffect(() => { + if (isPlaying) { + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + if (!action || action.actionType !== 'pickAndDrop') return; + + if (!crane.isActive && currentPhase === 'idle' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { + console.log('crane: ', crane); + + } + } + }, [crane, currentPhase]) const handleAnimationComplete = (action: string) => { if (action === 'picking') { @@ -27,10 +48,7 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { 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 === 'transfer') { + const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (crane) { + setIsPaused(materialId, true); + setCraneScheduled(crane.modelUuid, true); + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + addConveyorToMonitor(conveyor.modelUuid, () => { + addCraneToMonitor(crane.modelUuid, () => { + setIsPaused(materialId, true); + + handleAction(action, materialId); + }, action.actionUuid) + }) + } + } + } + } + } + } + } + } + } } } else if (fromEvent?.type === 'vehicle') { if (toEvent?.type === 'transfer') { @@ -1547,6 +1609,29 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'human') { // Human to Human + } + } else if (fromEvent?.type === 'crane') { + if (toEvent?.type === 'transfer') { + // Crane Unit to Transfer + + } else if (toEvent?.type === 'vehicle') { + // Crane Unit to Vehicle + + } else if (toEvent?.type === 'machine') { + // Crane Unit to Machine + + } else if (toEvent?.type === 'roboticArm') { + // Crane Unit to Robotic Arm + + } else if (toEvent?.type === 'storageUnit') { + // Crane Unit to Storage Unit + + } else if (toEvent?.type === 'human') { + // Crane Unit to Human + + } else if (toEvent?.type === 'crane') { + // Crane Unit to Human + } } } diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index e257ed8..1e90fe8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -336,6 +336,23 @@ type HumanEventManagerState = { humanStates: HumanEventState[]; }; +type CraneEventState = { + craneId: string; + pendingActions: { + actionUuid: string; + callback: () => void; + }[]; + currentAction: { + actionUuid: string; + callback: () => void; + } | null; + isProcessing: boolean; +}; + +type CraneEventManagerState = { + craneStates: CraneEventState[]; +}; + // Materials From a7dc3665ca52fb10726f54678202d05439b6d449 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 11 Aug 2025 12:23:05 +0530 Subject: [PATCH 21/38] added pillarJib trigger --- .../instances/animator/pillarJibAnimator.tsx | 150 ++++++++++++++---- .../instances/helper/pillarJibHelper.tsx | 8 +- .../instances/instance/pillarJibInstance.tsx | 32 ++-- 3 files changed, 138 insertions(+), 52 deletions(-) diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index fefad50..1b1332d 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -1,18 +1,20 @@ import { useEffect, useState } from 'react'; import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; -import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; function PillarJibAnimator({ crane, points, + setPoints, animationPhase, setAnimationPhase, onAnimationComplete }: { crane: CraneStatus; points: [THREE.Vector3, THREE.Vector3] | null; + setPoints: (points: [THREE.Vector3, THREE.Vector3] | null) => void; animationPhase: string; setAnimationPhase: (phase: string) => void; onAnimationComplete: (action: string) => void; @@ -22,20 +24,46 @@ function PillarJibAnimator({ const { resetAsset } = assetStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); + const { isReset } = useResetButtonStore(); const { speed } = useAnimationPlaySpeed(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); - const [currentTargetIndex, setCurrentTargetIndex] = useState(0); useEffect(() => { - if (!isPlaying) { + if (!isPlaying || isReset) { resetAsset(crane.modelUuid); setAnimationPhase('idle'); - setCurrentTargetIndex(0); - } else if (animationPhase === 'idle') { - setAnimationPhase('init-hook-adjust'); + setPoints(null); } - }, [isPlaying, scene, crane.modelUuid]); + }, [isPlaying, scene, crane.modelUuid, isReset]); + + useEffect(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + + if (!model) return; + const hook = model.getObjectByName('hook'); + + if (!hook) return; + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + if (crane.currentPhase === 'init-pickup') { + if (crane.currentMaterials.length > 0) { + const material = scene.getObjectByProperty('uuid', crane.currentMaterials[0].materialId); + if (material) { + const materialWorld = new THREE.Vector3(); + material.getWorldPosition(materialWorld); + setAnimationPhase('init-hook-adjust'); + setPoints( + [ + new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z), + new THREE.Vector3(materialWorld.x, materialWorld.y + 0.5, materialWorld.z) + ] + ); + } + } + } + }, [crane.currentPhase]) useEffect(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); @@ -99,7 +127,7 @@ function PillarJibAnimator({ }); setClampedPoints(newClampedPoints); - }, [crane.modelUuid]); + }, [crane.modelUuid, points]); useFrame(() => { if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return; @@ -122,10 +150,10 @@ function PillarJibAnimator({ if (!model.userData.animationData) { model.userData.animationData = { originalHookY: hook.position.y, - targetHookY: clampedPoints[currentTargetIndex].y - baseWorld.y + 0.5, + targetHookY: clampedPoints[0].y - baseWorld.y, targetDirection: new THREE.Vector2(), targetTrolleyX: 0, - targetWorldPosition: clampedPoints[currentTargetIndex].clone(), + targetWorldPosition: clampedPoints[0].clone(), finalHookTargetY: 0, }; } @@ -137,11 +165,12 @@ function PillarJibAnimator({ switch (animationPhase) { case 'init-hook-adjust': { - const direction = Math.sign(animationData.targetHookY - hook.position.y); + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + const direction = Math.sign((clampedPoints[0].y - baseWorld.y) - hookWorld.y); hook.position.y += direction * hookSpeed; - if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { - hook.position.y = animationData.targetHookY; + if (parseFloat(Math.abs(hookWorld.y - clampedPoints[0].y).toFixed(2)) < 0.05) { setAnimationPhase('init-rotate-base'); } break; @@ -154,7 +183,7 @@ function PillarJibAnimator({ const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); - const targetWorld = clampedPoints[currentTargetIndex]; + const targetWorld = clampedPoints[0]; const targetDir = new THREE.Vector2( targetWorld.x - baseWorld.x, targetWorld.z - baseWorld.z @@ -170,7 +199,7 @@ function PillarJibAnimator({ base.rotation.y += Math.sign(angleDiff) * rotationSpeed; } else { base.rotation.y += angleDiff; - const localTarget = trolley.parent.worldToLocal(clampedPoints[currentTargetIndex].clone()); + const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); animationData.targetTrolleyX = localTarget?.x; setAnimationPhase('init-move-trolley'); } @@ -185,7 +214,7 @@ function PillarJibAnimator({ if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { trolley.position.x = animationData.targetTrolleyX; - animationData.finalHookTargetY = hook.position.y - 0.5; + animationData.finalHookTargetY = hook.position.y; setAnimationPhase('init-final-hook-adjust'); } break; @@ -199,22 +228,87 @@ function PillarJibAnimator({ if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { hook.position.y = animationData.finalHookTargetY; - if (currentTargetIndex < points.length - 1) { - setCurrentTargetIndex(currentTargetIndex + 1); + model.userData.animationData = { + originalHookY: hook.position.y, + targetHookY: clampedPoints[1].y - baseWorld.y + 0.5, + targetDirection: new THREE.Vector2(), + targetTrolleyX: 0, + targetWorldPosition: clampedPoints[1].clone(), + finalHookTargetY: 0, + }; - model.userData.animationData = { - originalHookY: hook.position.y, - targetHookY: clampedPoints[currentTargetIndex + 1].y - baseWorld.y + 0.5, - targetDirection: new THREE.Vector2(), - targetTrolleyX: 0, - targetWorldPosition: clampedPoints[currentTargetIndex + 1].clone(), - finalHookTargetY: 0, - }; + setAnimationPhase('starting'); + onAnimationComplete('starting'); + } + break; + } + case 'first-hook-adjust': { + const direction = Math.sign(animationData.targetHookY - hook.position.y); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) { + hook.position.y = animationData.targetHookY; + setAnimationPhase('first-rotate-base'); + } + break; + } + + case 'first-rotate-base': { + const baseForward = new THREE.Vector3(1, 0, 0); + base.localToWorld(baseForward); + baseForward.sub(baseWorld); + + const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); + + const targetWorld = clampedPoints[1]; + const targetDir = new THREE.Vector2( + targetWorld.x - baseWorld.x, + targetWorld.z - baseWorld.z + ).normalize(); + + const currentAngle = Math.atan2(currentDir.y, currentDir.x); + const targetAngle = Math.atan2(targetDir.y, targetDir.x); + let angleDiff = currentAngle - targetAngle; + + angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff)); + + if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) { + base.rotation.y += Math.sign(angleDiff) * rotationSpeed; + } else { + base.rotation.y += angleDiff; + const localTarget = trolley.parent.worldToLocal(clampedPoints[1].clone()); + animationData.targetTrolleyX = localTarget?.x; + setAnimationPhase('first-move-trolley'); + } + break; + } + + case 'first-move-trolley': { + const dx = animationData.targetTrolleyX - trolley.position.x; + const direction = Math.sign(dx); + trolley.position.x += direction * trolleySpeed; + + if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { + trolley.position.x = animationData.targetTrolleyX; + + animationData.finalHookTargetY = hook.position.y - 0.5; + setAnimationPhase('first-final-hook-adjust'); + } + break; + } + + case 'first-final-hook-adjust': { + const dy = animationData.finalHookTargetY - hook.position.y; + const direction = Math.sign(dy); + hook.position.y += direction * hookSpeed; + + if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { + hook.position.y = animationData.finalHookTargetY; + if (crane.currentPhase === 'init-pickup') { setAnimationPhase('picking'); onAnimationComplete('picking'); - - } else { + } else if (crane.currentPhase === 'pickup-drop') { setAnimationPhase('dropping'); onAnimationComplete('dropping'); } diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx index 84883fa..8998865 100644 --- a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -8,7 +8,7 @@ function PillarJibHelper({ points }: { crane: CraneStatus, - points: [THREE.Vector3, THREE.Vector3]; + points: [THREE.Vector3, THREE.Vector3] | null; }) { const { scene } = useThree(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); @@ -22,7 +22,7 @@ function PillarJibHelper({ const trolley = model.getObjectByName('trolley'); const hook = model.getObjectByName('hook'); - if (!base || !trolley || !hook) return { geometry: null, position: null }; + if (!base || !trolley || !hook || !points) return { geometry: null, position: null }; const baseWorld = new THREE.Vector3(); base.getWorldPosition(baseWorld); @@ -94,7 +94,7 @@ function PillarJibHelper({ setIsInside(newIsInside); return { geometry, position }; - }, [scene, crane.modelUuid]); + }, [scene, crane.modelUuid, points]); if (!geometry || !position) return null; @@ -115,7 +115,7 @@ function PillarJibHelper({ />
- {points.map((point, i) => ( + {points && points.map((point, i) => ( ('idle'); const [animationPhase, setAnimationPhase] = useState('idle'); const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null); @@ -23,22 +22,17 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); if (!action || action.actionType !== 'pickAndDrop') return; - if (!crane.isActive && currentPhase === 'idle' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { - console.log('crane: ', crane); - + if (!crane.isActive && crane.currentPhase === 'init' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { + setCurrentPhase(crane.modelUuid, 'init-pickup'); } } - }, [crane, currentPhase]) + }, [crane]) const handleAnimationComplete = (action: string) => { - if (action === 'picking') { - setTimeout(() => { - setAnimationPhase('init-hook-adjust'); - }, 3000) - } else if (action === 'dropping') { - setTimeout(() => { - setAnimationPhase('idle'); - }, 3000) + if (action === 'starting') { + setAnimationPhase('first-hook-adjust'); + } else if (action === 'picking') { + setCurrentPhase(crane.modelUuid, 'picking'); } } @@ -49,18 +43,16 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { key={crane.modelUuid} crane={crane} points={points} + setPoints={setPoints} animationPhase={animationPhase} setAnimationPhase={setAnimationPhase} onAnimationComplete={handleAnimationComplete} /> - {/* */} + points={points} + /> ) From 78e1ccf39f72914d1aa73e25e7ceeeed67208c30 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 11 Aug 2025 16:59:36 +0530 Subject: [PATCH 22/38] added undo redo for builder (not for simulation data) --- .../model/eventHandlers/useEventHandlers.ts | 21 +- .../builder/asset/models/model/model.tsx | 30 +- app/src/modules/scene/controls/controls.tsx | 3 + .../selection3D/copyPasteControls3D.tsx | 42 ++- .../selection3D/duplicationControls3D.tsx | 42 ++- .../selection3D/moveControls3D.tsx | 53 ++- .../selection3D/rotateControls3D.tsx | 55 ++- .../selection3D/selectionControls3D.tsx | 38 ++- .../transformControls/transformControls.tsx | 29 +- ...{useRedoHandler.ts => use2DRedoHandler.ts} | 4 +- ...{useUndoHandler.ts => use2DUndoHandler.ts} | 53 ++- .../handlers/use3DRedoHandler.ts | 318 +++++++++++++++++ .../handlers/use3DUndoHandler.ts | 323 ++++++++++++++++++ .../undoRedo2D/undoRedo2DControls.tsx | 10 +- .../undoRedo3D/undoRedo3DControls.tsx | 51 +++ app/src/modules/scene/sceneContext.tsx | 9 +- .../instances/ikInstance/ikInstance.tsx | 10 +- .../simulation/spatialUI/arm/armBotUI.tsx | 4 +- .../asset/floorAsset/getAssetField.ts} | 8 +- app/src/store/builder/useUndoRedo3DStore.ts | 78 +++++ app/src/types/builderTypes.d.ts | 65 ++++ .../utils/shortcutkeys/handleShortcutKeys.ts | 5 +- 22 files changed, 1179 insertions(+), 72 deletions(-) rename app/src/modules/scene/controls/undoRedoControls/handlers/{useRedoHandler.ts => use2DRedoHandler.ts} (99%) rename app/src/modules/scene/controls/undoRedoControls/handlers/{useUndoHandler.ts => use2DUndoHandler.ts} (83%) create mode 100644 app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts create mode 100644 app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts create mode 100644 app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx rename app/src/services/{simulation/ik/getAssetIKs.ts => factoryBuilder/asset/floorAsset/getAssetField.ts} (80%) create mode 100644 app/src/store/builder/useUndoRedo3DStore.ts diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts index a0ea33a..1fc26ae 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -30,11 +30,11 @@ export function useModelEventHandlers({ const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { socket } = useSocketStore(); - const { eventStore, productStore, assetStore } = useSceneContext(); + const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { removeAsset } = assetStore(); - const { removeEvent } = eventStore(); + const { removeEvent, getEventByModelUuid } = eventStore(); const { getIsEventInProduct, addPoint, deleteEvent } = productStore(); - const { getEventByModelUuid } = eventStore(); const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); const { setSelectedFloorItem } = useSelectedFloorItem(); @@ -152,6 +152,21 @@ export function useModelEventHandlers({ removeAsset(asset.modelUuid); + push3D({ + type: 'Scene', + actions: [ + { + module: "builder", + actionType: "Asset-Delete", + asset: { + type: "Asset", + assetData: asset, + timeStap: new Date().toISOString() + } + } + ] + }); + echo.success("Model Removed!"); } diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 9767138..3f128bb 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -8,7 +8,7 @@ import useModuleStore from '../../../../../store/useModuleStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { SkeletonUtils } from 'three-stdlib'; -import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; +import { getAssetFieldApi } from '../../../../../services/factoryBuilder/asset/floorAsset/getAssetField'; import { ModelAnimator } from './animator/modelAnimator'; import { useModelEventHandlers } from './eventHandlers/useEventHandlers'; @@ -26,19 +26,31 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere const [boundingBox, setBoundingBox] = useState(null); const [isSelected, setIsSelected] = useState(false); const groupRef = useRef(null); - const [ikData, setIkData] = useState(); + const [fieldData, setFieldData] = useState(); const { selectedAssets } = useSelectedAssets(); useEffect(() => { - if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') { - getAssetIksApi(asset.assetId).then((data) => { - if (data.iks) { - const iks: IK[] = data.iks; - setIkData(iks); + if (!fieldData && asset.eventData) { + getAssetFieldApi(asset.assetId).then((data) => { + if (data.type === 'ArmBot') { + if (data.data) { + const fieldData: IK[] = data.data; + setFieldData(fieldData); + } + } else if (data.type === 'Conveyor') { + if (data.data) { + const fieldData = data.data; + setFieldData(fieldData); + } + } else if (data.type === 'Crane') { + if (data.data) { + const fieldData = data.data; + setFieldData(fieldData); + } } }) } - }, [asset.modelUuid, ikData]) + }, [asset.modelUuid, fieldData]) useEffect(() => { setDeletableFloorItem(null); @@ -157,7 +169,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere position={asset.position} rotation={asset.rotation} visible={asset.isVisible} - userData={{ ...asset, iks: ikData }} + userData={{ ...asset, fieldData: fieldData }} castShadow receiveShadow onDoubleClick={(e) => { diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index 79b9c9f..d58c362 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -16,6 +16,7 @@ import { getUserData } from "../../../functions/getUserData"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; +import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls"; export default function Controls() { const controlsRef = useRef(null); @@ -144,6 +145,8 @@ export default function Controls() { + + diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index e28da3c..e9d7bc3 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -28,7 +28,8 @@ const CopyPasteControls3D = ({ const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { socket } = useSocketStore(); - const { assetStore, eventStore } = useSceneContext(); + const { assetStore, eventStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { addEvent } = eventStore(); const { projectId } = useParams(); const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore(); @@ -198,6 +199,9 @@ const CopyPasteControls3D = ({ const addPastedObjects = () => { if (pastedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToCopy: AssetData[] = []; + pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => { if (pastedAsset) { const assetUuid = pastedAsset.userData.modelUuid; @@ -529,9 +533,45 @@ const CopyPasteControls3D = ({ updateAsset(asset.modelUuid, asset); } + + assetsToCopy.push({ + type: "Asset", + assetData: { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + assetId: newFloorItem.assetId, + position: [position.x, position.y, position.z], + rotation: [pastedAsset.rotation.x, pastedAsset.rotation.y, pastedAsset.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 1, + eventData: newFloorItem.eventData || undefined + }, + timeStap: new Date().toISOString() + }); } }); + if (assetsToCopy.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Copied", + asset: assetsToCopy[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Copied", + assets: assetsToCopy + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + echo.success("Object added!"); clearSelection(); }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index f494025..344ae4e 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -26,7 +26,8 @@ const DuplicationControls3D = ({ const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { socket } = useSocketStore(); - const { assetStore, eventStore } = useSceneContext(); + const { assetStore, eventStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { addEvent } = eventStore(); const { projectId } = useParams(); const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore(); @@ -199,6 +200,9 @@ const DuplicationControls3D = ({ const addDuplicatedAssets = () => { if (duplicatedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToDuplicate: AssetData[] = []; + duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => { if (duplicatedAsset) { const assetUuid = duplicatedAsset.userData.modelUuid; @@ -530,9 +534,45 @@ const DuplicationControls3D = ({ updateAsset(asset.modelUuid, asset); } + + assetsToDuplicate.push({ + type: "Asset", + assetData: { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + assetId: newFloorItem.assetId, + position: [position.x, position.y, position.z], + rotation: [duplicatedAsset.rotation.x, duplicatedAsset.rotation.y, duplicatedAsset.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 1, + eventData: newFloorItem.eventData || undefined + }, + timeStap: new Date().toISOString() + }); } }); + if (assetsToDuplicate.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Copied", + asset: assetsToDuplicate[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Copied", + assets: assetsToDuplicate + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + echo.success("Object duplicated!"); clearSelection(); }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 6f65300..4916886 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -37,7 +37,8 @@ function MoveControls3D({ const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const { userId, organization } = getUserData(); const { projectId } = useParams(); - const { assetStore, eventStore, productStore } = useSceneContext(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { updateAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -284,6 +285,9 @@ function MoveControls3D({ const placeMovedAssets = () => { if (movedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToUpdate: AssetData[] = []; + movedObjects.forEach(async (movedAsset: THREE.Object3D) => { if (movedAsset) { const assetUuid = movedAsset.userData.modelUuid; @@ -291,6 +295,32 @@ function MoveControls3D({ const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid); if (!asset || !model) return; const position = new THREE.Vector3().copy(model.position); + const initialState = initialStates[movedAsset.uuid]; + + if (initialState) { + assetsToUpdate.push({ + type: "Asset", + assetData: { + ...asset, + position: [ + initialState.position.x, + initialState.position.y, + initialState.position.z + ], + rotation: [ + initialState.rotation?.x || 0, + initialState.rotation?.y || 0, + initialState.rotation?.z || 0 + ] + }, + newData: { + ...asset, + position: [position.x, position.y, position.z], + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z] + }, + timeStap: new Date().toISOString() + }); + } const newFloorItem: Types.FloorItemType = { modelUuid: movedAsset.userData.modelUuid, @@ -368,6 +398,27 @@ function MoveControls3D({ } }); + if (assetsToUpdate.length > 0) { + if (assetsToUpdate.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Update", + asset: assetsToUpdate[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Update", + assets: assetsToUpdate + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + } + echo.success("Object moved!"); setIsMoving(false); clearSelection(); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index 0ca114d..bd5e254 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -32,7 +32,8 @@ function RotateControls3D({ const { socket } = useSocketStore(); const { userId, organization } = getUserData(); const { projectId } = useParams(); - const { assetStore, eventStore, productStore } = useSceneContext(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { updateAsset } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -222,12 +223,43 @@ function RotateControls3D({ const placeRotatedAssets = useCallback(() => { if (rotatedObjects.length === 0) return; + const undoActions: UndoRedo3DAction[] = []; + const assetsToUpdate: AssetData[] = []; + rotatedObjects.forEach((obj: THREE.Object3D) => { if (obj && obj.userData.modelUuid) { + const asset = assetStore.getState().getAssetById(obj.userData.modelUuid); + if (!asset) return; + const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z]; + if (initialRotations[obj.uuid] && initialPositions[obj.uuid]) { + assetsToUpdate.push({ + type: "Asset", + assetData: { + ...asset, + position: [ + initialPositions[obj.uuid].x, + initialPositions[obj.uuid].y, + initialPositions[obj.uuid].z + ], + rotation: [ + initialRotations[obj.uuid].x, + initialRotations[obj.uuid].y, + initialRotations[obj.uuid].z + ] + }, + newData: { + ...asset, + position: positionArray, + rotation: rotationArray + }, + timeStap: new Date().toISOString() + }); + } + const newFloorItem: Types.FloorItemType = { modelUuid: obj.userData.modelUuid, modelName: obj.userData.modelName, @@ -305,6 +337,27 @@ function RotateControls3D({ } }); + if (assetsToUpdate.length > 0) { + if (assetsToUpdate.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Update", + asset: assetsToUpdate[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Update", + assets: assetsToUpdate + }); + } + + push3D({ + type: 'Scene', + actions: undoActions + }); + } + setIsRotating(false); clearSelection(); }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index c72da8b..91acd3c 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -31,8 +31,9 @@ const SelectionControls3D: React.FC = () => { const boundingBoxRef = useRef(); const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); - const { assetStore, eventStore, productStore } = useSceneContext(); - const { removeAsset } = assetStore(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); + const { removeAsset, getAssetById } = assetStore(); const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); @@ -266,12 +267,18 @@ const SelectionControls3D: React.FC = () => { const deleteSelection = () => { if (selectedAssets.length > 0 && duplicatedObjects.length === 0) { + const undoActions: UndoRedo3DAction[] = []; + const assetsToDelete: AssetData[] = []; + const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid); selectedAssets.forEach((selectedMesh: THREE.Object3D) => { + const asset = getAssetById(selectedMesh.userData.modelUuid); + if (!asset) return; + //REST - // const response = await deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName); + // const response = deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName); //SOCKET @@ -321,6 +328,31 @@ const SelectionControls3D: React.FC = () => { } }); + + assetsToDelete.push({ + type: "Asset", + assetData: asset, + timeStap: new Date().toISOString() + }); + }); + + if (assetsToDelete.length === 1) { + undoActions.push({ + module: "builder", + actionType: "Asset-Delete", + asset: assetsToDelete[0] + }); + } else { + undoActions.push({ + module: "builder", + actionType: "Assets-Delete", + assets: assetsToDelete + }); + } + + push3D({ + type: 'Scene', + actions: undoActions }); selectedUUIDs.forEach((uuid: string) => { diff --git a/app/src/modules/scene/controls/transformControls/transformControls.tsx b/app/src/modules/scene/controls/transformControls/transformControls.tsx index 6d5e7d5..af51542 100644 --- a/app/src/modules/scene/controls/transformControls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls/transformControls.tsx @@ -24,7 +24,8 @@ export default function TransformControl() { const { socket } = useSocketStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { assetStore, eventStore, productStore } = useSceneContext(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D } = undoRedo3DStore(); const { updateAsset, getAssetById } = assetStore(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); @@ -137,13 +138,37 @@ export default function TransformControl() { projectId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); + + push3D({ + type: 'Scene', + actions: [ + { + module: "builder", + actionType: "Asset-Update", + asset: { + type: "Asset", + assetData: asset, + newData: { + ...asset, + position: [selectedFloorItem.position.x, 0, selectedFloorItem.position.z], + rotation: [selectedFloorItem.rotation.x, selectedFloorItem.rotation.y, selectedFloorItem.rotation.z], + }, + timeStap: new Date().toISOString() + } + } + ] + }); } } useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + const isTextInput = (element: Element | null): boolean => + element instanceof HTMLInputElement || + element instanceof HTMLTextAreaElement || + element?.getAttribute("contenteditable") === "true"; + if (isTextInput(document.activeElement)) return; const keyCombination = detectModifierKeys(e); if (!selectedFloorItem) return; if (keyCombination === "G") { diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DRedoHandler.ts similarity index 99% rename from app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts rename to app/src/modules/scene/controls/undoRedoControls/handlers/use2DRedoHandler.ts index 99902c1..12a0929 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DRedoHandler.ts @@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store"; // import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; // import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; -function useRedoHandler() { +function use2DRedoHandler() { const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext(); const { redo2D, peekRedo2D } = undoRedo2DStore(); const { addWall, removeWall, updateWall } = wallStore(); @@ -352,4 +352,4 @@ function useRedoHandler() { return { handleRedo }; } -export default useRedoHandler; +export default use2DRedoHandler; diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DUndoHandler.ts similarity index 83% rename from app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts rename to app/src/modules/scene/controls/undoRedoControls/handlers/use2DUndoHandler.ts index c822ef9..9f7ff60 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use2DUndoHandler.ts @@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store"; // import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; // import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; -function useUndoHandler() { +function use2DUndoHandler() { const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext(); const { undo2D, peekUndo2D } = undoRedo2DStore(); const { addWall, removeWall, updateWall } = wallStore(); @@ -67,38 +67,37 @@ function useUndoHandler() { } undo2D(); - }; const handleCreate = (point: UndoRedo2DDataTypeSchema) => { switch (point.type) { - case 'Wall': createWallFromBackend(point.lineData); break; - case 'Floor': createFloorFromBackend(point.lineData); break; - case 'Zone': createZoneFromBackend(point.lineData); break; - case 'Aisle': createAisleFromBackend(point.lineData); break; + case 'Wall': createWallToBackend(point.lineData); break; + case 'Floor': createFloorToBackend(point.lineData); break; + case 'Zone': createZoneToBackend(point.lineData); break; + case 'Aisle': createAisleToBackend(point.lineData); break; } }; const handleRemove = (point: UndoRedo2DDataTypeSchema) => { switch (point.type) { - case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break; - case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break; - case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break; - case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break; + case 'Wall': removeWallToBackend(point.lineData.wallUuid); break; + case 'Floor': removeFloorToBackend(point.lineData.floorUuid); break; + case 'Zone': removeZoneToBackend(point.lineData.zoneUuid); break; + case 'Aisle': removeAisleToBackend(point.lineData.aisleUuid); break; } }; const handleUpdate = (point: UndoRedo2DDataTypeSchema) => { switch (point.type) { - case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break; - case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break; - case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break; - case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break; + case 'Wall': updateWallToBackend(point.lineData.wallUuid, point.lineData); break; + case 'Floor': updateFloorToBackend(point.lineData.floorUuid, point.lineData); break; + case 'Zone': updateZoneToBackend(point.lineData.zoneUuid, point.lineData); break; + case 'Aisle': updateAisleToBackend(point.lineData.aisleUuid, point.lineData); break; } }; - const createWallFromBackend = (wallData: Wall) => { + const createWallToBackend = (wallData: Wall) => { addWall(wallData); if (projectId) { // API @@ -119,7 +118,7 @@ function useUndoHandler() { } }; - const removeWallFromBackend = (wallUuid: string) => { + const removeWallToBackend = (wallUuid: string) => { removeWall(wallUuid); if (projectId) { // API @@ -140,7 +139,7 @@ function useUndoHandler() { } }; - const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => { + const updateWallToBackend = (wallUuid: string, updatedData: Wall) => { updateWall(wallUuid, updatedData); if (projectId) { // API @@ -161,7 +160,7 @@ function useUndoHandler() { } }; - const createFloorFromBackend = (floorData: Floor) => { + const createFloorToBackend = (floorData: Floor) => { addFloor(floorData); if (projectId) { // API @@ -182,7 +181,7 @@ function useUndoHandler() { } }; - const removeFloorFromBackend = (floorUuid: string) => { + const removeFloorToBackend = (floorUuid: string) => { removeFloor(floorUuid); if (projectId) { // API @@ -203,7 +202,7 @@ function useUndoHandler() { } }; - const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => { + const updateFloorToBackend = (floorUuid: string, updatedData: Floor) => { updateFloor(floorUuid, updatedData); if (projectId) { // API @@ -224,7 +223,7 @@ function useUndoHandler() { } }; - const createZoneFromBackend = (zoneData: Zone) => { + const createZoneToBackend = (zoneData: Zone) => { addZone(zoneData); if (projectId) { // API @@ -245,7 +244,7 @@ function useUndoHandler() { } }; - const removeZoneFromBackend = (zoneUuid: string) => { + const removeZoneToBackend = (zoneUuid: string) => { removeZone(zoneUuid); if (projectId) { // API @@ -266,7 +265,7 @@ function useUndoHandler() { } }; - const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => { + const updateZoneToBackend = (zoneUuid: string, updatedData: Zone) => { updateZone(zoneUuid, updatedData); if (projectId) { // API @@ -287,7 +286,7 @@ function useUndoHandler() { } }; - const createAisleFromBackend = (aisleData: Aisle) => { + const createAisleToBackend = (aisleData: Aisle) => { addAisle(aisleData); if (projectId) { // API @@ -308,7 +307,7 @@ function useUndoHandler() { } }; - const removeAisleFromBackend = (aisleUuid: string) => { + const removeAisleToBackend = (aisleUuid: string) => { removeAisle(aisleUuid); if (projectId) { // API @@ -329,7 +328,7 @@ function useUndoHandler() { } }; - const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => { + const updateAisleToBackend = (aisleUuid: string, updatedData: Aisle) => { updateAisle(aisleUuid, updatedData); if (projectId) { // API @@ -353,4 +352,4 @@ function useUndoHandler() { return { handleUndo }; } -export default useUndoHandler; \ No newline at end of file +export default use2DUndoHandler; \ No newline at end of file diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts new file mode 100644 index 0000000..b44f6a7 --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DRedoHandler.ts @@ -0,0 +1,318 @@ +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSceneContext } from "../../../sceneContext"; +import { useProductContext } from "../../../../simulation/products/productContext"; +import { useSocketStore } from "../../../../../store/builder/store"; + +import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; + +// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; +// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi"; + +function use3DRedoHandler() { + const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext(); + const { deleteEvent } = productStore(); + const { addEvent, removeEvent } = eventStore(); + const { updateAsset, removeAsset, addAsset } = assetStore(); + const { redo3D, peekRedo3D } = undoRedo3DStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; + + const handleRedo = () => { + const redoData = peekRedo3D(); + if (!redoData) return; + + if (redoData.type === 'Scene') { + const { actions } = redoData; + + actions.forEach(action => { + const { actionType } = action; + + if ('asset' in action) { + const asset = action.asset; + + if (actionType === 'Asset-Add') { + handleAdd(asset); + } else if (actionType === 'Asset-Delete') { + handleDelete(asset); + } else if (actionType === 'Asset-Update') { + handleUpdate(asset); + } else if (actionType === 'Asset-Copied') { + handleCopy(asset); + } else if (actionType === 'Asset-Duplicated') { + handleDuplicate(asset); + } + + } else if ('assets' in action) { + const assets = action.assets; + + if (actionType === 'Assets-Add') { + assets.forEach(handleAdd); + } else if (actionType === 'Assets-Delete') { + assets.forEach(handleDelete); + } else if (actionType === 'Assets-Update') { + assets.forEach(handleUpdate); + } else if (actionType === 'Assets-Copied') { + assets.forEach(handleCopy); + } else if (actionType === 'Assets-Duplicated') { + assets.forEach(handleDuplicate); + } + } + }); + } else if (redoData.type === 'UI') { + // Handle UI actions if needed + } + + redo3D(); + }; + + + const handleAdd = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': addAssetToBackend(asset.assetData); break; + case 'WallAsset': addWallAssetToBackend(asset.assetData); break; + } + }; + + const handleDelete = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': deleteAssetToBackend(asset.assetData); break; + case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break; + } + } + + const handleUpdate = (asset: AssetData) => { + if (!asset.newData) return; + switch (asset.type) { + case 'Asset': updateAssetToBackend(asset.newData.modelUuid, asset.newData); break; + case 'WallAsset': updateWallAssetToBackend(asset.newData.modelUuid, asset.newData); break; + } + } + + const handleCopy = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': copyAssetToBackend(asset.assetData); break; + case 'WallAsset': copyWallAssetToBackend(asset.assetData); break; + } + } + + const handleDuplicate = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': duplicateAssetToBackend(asset.assetData); break; + case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break; + } + } + + + const addAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const deleteAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => { + updateAsset(modelUuid, updatedData); + if (projectId) { + const data = { + organization, + modelUuid: updatedData.modelUuid, + modelName: updatedData.modelName, + assetId: updatedData.assetId, + position: updatedData.position, + rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] }, + isLocked: false, + isVisible: true, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const copyAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const duplicateAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const addWallAssetToBackend = (assetData: WallAsset) => { + + } + + const deleteWallAssetToBackend = (assetData: WallAsset) => { + + } + + const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => { + + } + + const copyWallAssetToBackend = (assetData: WallAsset) => { + + } + + const duplicateWallAssetToBackend = (assetData: WallAsset) => { + + } + + return { handleRedo }; +} + +export default use3DRedoHandler; diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts new file mode 100644 index 0000000..50fd6cf --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/use3DUndoHandler.ts @@ -0,0 +1,323 @@ +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSceneContext } from "../../../sceneContext"; +import { useProductContext } from "../../../../simulation/products/productContext"; +import { useSocketStore } from "../../../../../store/builder/store"; + +import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; + +// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; +// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi"; + +function use3DUndoHandler() { + const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext(); + const { deleteEvent } = productStore(); + const { addEvent, removeEvent } = eventStore(); + const { updateAsset, removeAsset, addAsset } = assetStore(); + const { undo3D, peekUndo3D } = undoRedo3DStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; + + const handleUndo = () => { + const unDoData = peekUndo3D(); + if (!unDoData) return; + + if (unDoData.type === 'Scene') { + const { actions } = unDoData; + + actions.forEach(action => { + const { actionType } = action; + + if ('asset' in action) { + const asset = action.asset; + + if (actionType === 'Asset-Add') { + handleDelete(asset); + } else if (actionType === 'Asset-Delete') { + handleAdd(asset); + } else if (actionType === 'Asset-Update') { + handleUpdate(asset); + } else if (actionType === 'Asset-Copied') { + handleCopy(asset); + } else if (actionType === 'Asset-Duplicated') { + handleDuplicate(asset); + } + + } else if ('assets' in action) { + const assets = action.assets; + + if (actionType === 'Assets-Add') { + assets.forEach(handleDelete); + } else if (actionType === 'Assets-Delete') { + assets.forEach(handleAdd); + } else if (actionType === 'Assets-Update') { + assets.forEach(handleUpdate); + } else if (actionType === 'Assets-Copied') { + assets.forEach(handleCopy); + } else if (actionType === 'Assets-Duplicated') { + assets.forEach(handleDuplicate); + } + } + }); + } else if (unDoData.type === 'UI') { + // Handle UI actions if needed + } + + undo3D(); + }; + + + const handleAdd = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': addAssetToBackend(asset.assetData); break; + case 'WallAsset': addWallAssetToBackend(asset.assetData); break; + } + }; + + const handleDelete = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': deleteAssetToBackend(asset.assetData); break; + case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break; + } + } + + const handleUpdate = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': updateAssetToBackend(asset.assetData.modelUuid, asset.assetData); break; + case 'WallAsset': updateWallAssetToBackend(asset.assetData.modelUuid, asset.assetData); break; + } + } + + const handleCopy = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': copyAssetToBackend(asset.assetData); break; + case 'WallAsset': copyWallAssetToBackend(asset.assetData); break; + } + } + + const handleDuplicate = (asset: AssetData) => { + switch (asset.type) { + case 'Asset': duplicateAssetToBackend(asset.assetData); break; + case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break; + } + } + + + const addAssetToBackend = (assetData: Asset) => { + addAsset(assetData); + if (projectId) { + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + assetId: assetData.assetId, + position: assetData.position, + rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] }, + isLocked: false, + isVisible: true, + eventData: {}, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + if (assetData.eventData) { + data.eventData = assetData.eventData; + addEvent(assetData.eventData as EventsSchema); + } + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const deleteAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => { + updateAsset(modelUuid, updatedData); + if (projectId) { + const data = { + organization, + modelUuid: updatedData.modelUuid, + modelName: updatedData.modelName, + assetId: updatedData.assetId, + position: updatedData.position, + rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] }, + isLocked: false, + isVisible: true, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + projectId, + userId + }; + + // API + + // setAssetsApi(data); + + //SOCKET + + socket.emit("v1:model-asset:add", data); + } + } + + const copyAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const duplicateAssetToBackend = (assetData: Asset) => { + //REST + + // const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName); + + //SOCKET + + const data = { + organization, + modelUuid: assetData.modelUuid, + modelName: assetData.modelName, + socketId: socket.id, + userId, + versionId: selectedVersion?.versionId || '', + projectId + } + + const response = socket.emit('v1:model-asset:delete', data) + + removeEvent(assetData.modelUuid); + const updatedEvents = deleteEvent(assetData.modelUuid); + + updatedEvents.forEach((event) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + }) + + if (response) { + + removeAsset(assetData.modelUuid); + } + } + + const addWallAssetToBackend = (assetData: WallAsset) => { + + } + + const deleteWallAssetToBackend = (assetData: WallAsset) => { + + } + + const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => { + + } + + const copyWallAssetToBackend = (assetData: WallAsset) => { + + } + + const duplicateWallAssetToBackend = (assetData: WallAsset) => { + + } + + return { handleUndo }; +} + +export default use3DUndoHandler; \ No newline at end of file diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx index d45518c..1965087 100644 --- a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx +++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx @@ -4,21 +4,21 @@ import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModi import { useSocketStore, useToggleView } from '../../../../../store/builder/store'; import { useVersionContext } from '../../../../builder/version/versionContext'; -import useUndoHandler from '../handlers/useUndoHandler'; -import useRedoHandler from '../handlers/useRedoHandler'; +import use2DUndoHandler from '../handlers/use2DUndoHandler'; +import use2DRedoHandler from '../handlers/use2DRedoHandler'; function UndoRedo2DControls() { const { undoRedo2DStore } = useSceneContext(); const { undoStack, redoStack } = undoRedo2DStore(); const { toggleView } = useToggleView(); - const { handleUndo } = useUndoHandler(); - const { handleRedo } = useRedoHandler(); + const { handleUndo } = use2DUndoHandler(); + const { handleRedo } = use2DRedoHandler(); const { socket } = useSocketStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); useEffect(() => { - console.log(undoStack, redoStack); + // console.log(undoStack, redoStack); }, [undoStack, redoStack]); useEffect(() => { diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx new file mode 100644 index 0000000..9331320 --- /dev/null +++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx @@ -0,0 +1,51 @@ +import { useEffect } from 'react' +import { useSceneContext } from '../../../sceneContext' +import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys'; +import { useSocketStore, useToggleView } from '../../../../../store/builder/store'; +import { useVersionContext } from '../../../../builder/version/versionContext'; +import useModuleStore from '../../../../../store/useModuleStore'; + +import use3DUndoHandler from '../handlers/use3DUndoHandler'; +import use3DRedoHandler from '../handlers/use3DRedoHandler'; + +function UndoRedo3DControls() { + const { undoRedo3DStore } = useSceneContext(); + const { undoStack, redoStack } = undoRedo3DStore(); + const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); + const { handleUndo } = use3DUndoHandler(); + const { handleRedo } = use3DRedoHandler(); + const { socket } = useSocketStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + + useEffect(() => { + console.log(undoStack, redoStack); + }, [undoStack, redoStack]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === 'Ctrl+Z') { + handleUndo(); + } + + if (keyCombination === 'Ctrl+Y') { + handleRedo(); + } + }; + + if (!toggleView) { + window.addEventListener('keydown', handleKeyDown); + } + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [toggleView, undoStack, redoStack, socket, selectedVersion, activeModule]); + + return null; +} + +export default UndoRedo3DControls; \ No newline at end of file diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index 6dbfce8..73e5f9c 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -8,6 +8,7 @@ import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore'; import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore'; +import { createUndoRedo3DStore, UndoRedo3DStoreType } from '../../store/builder/useUndoRedo3DStore'; import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore'; import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore'; @@ -31,6 +32,7 @@ type SceneContextValue = { floorStore: FloorStoreType, undoRedo2DStore: UndoRedo2DStoreType, + undoRedo3DStore: UndoRedo3DStoreType, eventStore: EventStoreType, productStore: ProductStoreType, @@ -70,6 +72,7 @@ export function SceneProvider({ const floorStore = useMemo(() => createFloorStore(), []); const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []); + const undoRedo3DStore = useMemo(() => createUndoRedo3DStore(), []); const eventStore = useMemo(() => createEventStore(), []); const productStore = useMemo(() => createProductStore(), []); @@ -94,6 +97,7 @@ export function SceneProvider({ zoneStore.getState().clearZones(); floorStore.getState().clearFloors(); undoRedo2DStore.getState().clearUndoRedo2D(); + undoRedo3DStore.getState().clearUndoRedo3D(); eventStore.getState().clearEvents(); productStore.getState().clearProducts(); materialStore.getState().clearMaterials(); @@ -106,7 +110,7 @@ export function SceneProvider({ craneStore.getState().clearCranes(); humanEventManagerRef.current.humanStates = []; craneEventManagerRef.current.craneStates = []; - }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); + }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, undoRedo3DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); const contextValue = useMemo(() => ( { @@ -117,6 +121,7 @@ export function SceneProvider({ zoneStore, floorStore, undoRedo2DStore, + undoRedo3DStore, eventStore, productStore, materialStore, @@ -132,7 +137,7 @@ export function SceneProvider({ clearStores, layout } - ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]); + ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, undoRedo3DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]); return ( diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index ab6041c..b43c7fa 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -22,7 +22,7 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) { const trySetup = () => { const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid); - if (!targetMesh || !targetMesh.userData.iks || targetMesh.userData.iks.length < 1) { + if (!targetMesh || !targetMesh.userData.fieldData || targetMesh.userData.fieldData.length < 1) { retryId = setTimeout(trySetup, 100); return; } @@ -34,8 +34,8 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) { }); if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return; - const rawIks: IK[] = targetMesh.userData.iks; - const iks = rawIks.map((ik) => ({ + const rawIks: IK[] = targetMesh.userData.fieldData; + const fieldData = rawIks.map((ik) => ({ target: ik.target, effector: ik.effector, links: ik.links.map((link) => ({ @@ -51,10 +51,10 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) { minheight: ik.minheight, })); - const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks); + const solver = new CCDIKSolver(OOI.Skinned_Mesh, fieldData); setIkSolver(solver); - // const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05) + // const helper = new CCDIKHelper(OOI.Skinned_Mesh, fieldData, 0.05) // scene.add(helper); }; diff --git a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx index dfd500a..b39076e 100644 --- a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx +++ b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx @@ -173,8 +173,8 @@ const ArmBotUI = () => { const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || ''); - const iks = targetMesh?.userData?.iks; - const firstIK = Array.isArray(iks) && iks.length > 0 ? iks[0] : {}; + const fieldData = targetMesh?.userData?.fieldData; + const firstIK = Array.isArray(fieldData) && fieldData.length > 0 ? fieldData[0] : {}; const { handlePointerDown } = useDraggableGLTF( updatePointToState, diff --git a/app/src/services/simulation/ik/getAssetIKs.ts b/app/src/services/factoryBuilder/asset/floorAsset/getAssetField.ts similarity index 80% rename from app/src/services/simulation/ik/getAssetIKs.ts rename to app/src/services/factoryBuilder/asset/floorAsset/getAssetField.ts index cfc6ad5..83567a9 100644 --- a/app/src/services/simulation/ik/getAssetIKs.ts +++ b/app/src/services/factoryBuilder/asset/floorAsset/getAssetField.ts @@ -1,9 +1,9 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; -export const getAssetIksApi = async (assetId: string) => { +export const getAssetFieldApi = async (assetId: string) => { try { const response = await fetch( - `${url_Backend_dwinzo}/api/v2/getAssetIks/${assetId}`, + `${url_Backend_dwinzo}/api/v2/getAssetField/${assetId}`, { method: "GET", headers: { @@ -20,13 +20,13 @@ export const getAssetIksApi = async (assetId: string) => { } if (!response.ok) { - console.error("Failed to fetch assetIks"); + console.error("Failed to fetch asset field"); } const result = await response.json(); return result; } catch (error) { - echo.error("Failed to get assetIks"); + echo.error("Failed to get asset field"); if (error instanceof Error) { console.log(error.message); } else { diff --git a/app/src/store/builder/useUndoRedo3DStore.ts b/app/src/store/builder/useUndoRedo3DStore.ts new file mode 100644 index 0000000..e8c3ce3 --- /dev/null +++ b/app/src/store/builder/useUndoRedo3DStore.ts @@ -0,0 +1,78 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; +import { undoRedoConfig } from '../../types/world/worldConstants'; + +type UndoRedo3DStore = { + undoStack: UndoRedo3DTypes[]; + redoStack: UndoRedo3DTypes[]; + + push3D: (entry: UndoRedo3DTypes) => void; + undo3D: () => UndoRedo3DTypes | undefined; + redo3D: () => UndoRedo3DTypes | undefined; + clearUndoRedo3D: () => void; + + peekUndo3D: () => UndoRedo3DTypes | undefined; + peekRedo3D: () => UndoRedo3DTypes | undefined; +}; + +export const createUndoRedo3DStore = () => { + return create()( + immer((set, get) => ({ + undoStack: [], + redoStack: [], + + push3D: (entry) => { + set((state) => { + state.undoStack.push(entry); + + if (state.undoStack.length > undoRedoConfig.undoRedoCount) { + state.undoStack.shift(); + } + + state.redoStack = []; + }); + }, + + undo3D: () => { + let lastAction: UndoRedo3DTypes | undefined; + set((state) => { + lastAction = state.undoStack.pop(); + if (lastAction) { + state.redoStack.unshift(lastAction); + } + }); + return lastAction; + }, + + redo3D: () => { + let redoAction: UndoRedo3DTypes | undefined; + set((state) => { + redoAction = state.redoStack.shift(); + if (redoAction) { + state.undoStack.push(redoAction); + } + }); + return redoAction; + }, + + clearUndoRedo3D: () => { + set((state) => { + state.undoStack = []; + state.redoStack = []; + }); + }, + + peekUndo3D: () => { + const stack = get().undoStack; + return stack.length > 0 ? stack[stack.length - 1] : undefined; + }, + + peekRedo3D: () => { + const stack = get().redoStack; + return stack.length > 0 ? stack[0] : undefined; + }, + })) + ); +} + +export type UndoRedo3DStoreType = ReturnType; \ No newline at end of file diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index 63ed97b..3487fc6 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -253,4 +253,69 @@ type UndoRedo2DTypes = UndoRedo2DDraw | UndoRedo2DUi type UndoRedo2D = { undoStack: UndoRedo2DTypes[]; redoStack: UndoRedo2DTypes[]; +}; + + +// Undo/Redo 3D + +type AssetType = { + type: "Asset"; + assetData: Asset; + newData?: Asset; + eventMetaData?: EventsSchema; + timeStap: string; +} + +type WallAssetType = { + type: "WallAsset"; + assetData: WallAsset; + newData?: WallAsset; + timeStap: string; +} + +type AssetData = AssetType | WallAssetType; + +type UndoRedo3DActionBuilderSchema = { + module: "builder"; + actionType: "Asset-Add" | "Asset-Delete" | "Asset-Update" | "Asset-Duplicated" | "Asset-Copied" | "Wall-Asset-Add" | "Wall-Asset-Delete" | "Wall-Asset-Update"; + asset: AssetData; +} + +type UndoRedo3DActionSimulationSchema = { + module: "simulation"; + actionType: ''; +} + +type UndoRedo3DActionSchema = UndoRedo3DActionBuilderSchema | UndoRedo3DActionSimulationSchema; + +type UndoRedo3DActionsBuilderSchema = { + module: "builder"; + actionType: "Assets-Add" | "Assets-Delete" | "Assets-Update" | "Assets-Duplicated" | "Assets-Copied" | "Wall-Assets-Add" | "Wall-Assets-Delete" | "Wall-Assets-Update"; + assets: AssetData[]; +} + +type UndoRedo3DActionsSimulationSchema = { + module: "simulation"; + actionType: ''; +} + +type UndoRedo3DActionsSchema = UndoRedo3DActionsBuilderSchema | UndoRedo3DActionsSimulationSchema; + +type UndoRedo3DAction = UndoRedo3DActionSchema | UndoRedo3DActionsSchema; + +type UndoRedo3DDraw = { + type: 'Scene'; + actions: UndoRedo3DAction[]; +}; + +type UndoRedo3DUi = { + type: 'UI'; + action: any; // Define UI actions as needed +} + +type UndoRedo3DTypes = UndoRedo3DDraw | UndoRedo3DUi; + +type UndoRedo3D = { + undoStack: UndoRedo3DTypes[]; + redoStack: UndoRedo3DTypes[]; }; \ No newline at end of file diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index ffd9ddf..7abbe2f 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -189,12 +189,9 @@ const KeyPressListener: React.FC = () => { }; const handleKeyPress = (event: KeyboardEvent) => { - if (isTextInput(document.activeElement)) return; - const keyCombination = detectModifierKeys(event); - if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") - return; + if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") return; if (keyCombination === "ESCAPE") { setWalkMode(false); From 01f0fa7cd735710315aa15a1ceb5055f7db5026c Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 11 Aug 2025 17:09:39 +0530 Subject: [PATCH 23/38] merge --- app/src/components/ui/FileMenu.tsx | 3 +- app/src/modules/scene/scene.tsx | 46 +++++++++------------ app/src/services/dashboard/updateProject.ts | 2 - 3 files changed, 20 insertions(+), 31 deletions(-) diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx index 843af5d..4575b3b 100644 --- a/app/src/components/ui/FileMenu.tsx +++ b/app/src/components/ui/FileMenu.tsx @@ -74,7 +74,7 @@ const FileMenu: React.FC = () => { // } //API for projects rename - + const updatedProjectName = await updateProject( projectId, userId, @@ -82,7 +82,6 @@ const FileMenu: React.FC = () => { undefined, projectName ); - console.log("updatedProjectName: ", updatedProjectName, projectId); } catch (error) { console.error("Error updating project name:", error); } diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index f463e52..feddc2c 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -33,33 +33,25 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co useEffect(() => { if (!projectId && loadingProgress > 1) return; - getAllProjects(userId, organization) - .then((projects) => { - if (!projects || !projects.Projects) return; - let project = projects.Projects.find( - (val: any) => val.projectUuid === projectId || val._id === projectId - ); - const canvas = document - .getElementById("sceneCanvas") - ?.getElementsByTagName("canvas")[0]; - if (!canvas) return; - const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL( - "image/png" - ); - const updateProjects = { - projectId: project?._id, - organization, - userId, - projectName: project?.projectName, - thumbnail: screenshotDataUrl, - }; - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); - } - }) - .catch((err) => { - console.error(err); - }); + getAllProjects(userId, organization).then((projects) => { + if (!projects || !projects.Projects) return; + let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId); + const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0]; + if (!canvas) return; + const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png"); + const updateProjects = { + projectId: project?._id, + organization, + userId, + projectName: project?.projectName, + thumbnail: screenshotDataUrl, + }; + if (projectSocket) { + projectSocket.emit("v1:project:update", updateProjects); + } + }).catch((err) => { + console.error(err); + }); // eslint-disable-next-line }, [activeModule, assets, loadingProgress]); diff --git a/app/src/services/dashboard/updateProject.ts b/app/src/services/dashboard/updateProject.ts index ab9ac0f..42e0d44 100644 --- a/app/src/services/dashboard/updateProject.ts +++ b/app/src/services/dashboard/updateProject.ts @@ -30,7 +30,6 @@ export const updateProject = async ( body: JSON.stringify(body), } ); - console.log('body: ', body); const newAccessToken = response.headers.get("x-access-token"); if (newAccessToken) { @@ -42,7 +41,6 @@ export const updateProject = async ( } const result = await response.json(); - console.log('result: ', result); return result; } catch (error) { if (error instanceof Error) { From c71b25c407c1a8574caa676381c34cb4fda457b6 Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Tue, 12 Aug 2025 09:54:42 +0530 Subject: [PATCH 24/38] Add context menu and context controls for asset manipulation --- .../components/layout/scenes/MainScene.tsx | 47 ++-- app/src/components/ui/menu/contextMenu.tsx | 207 ++++++++++++++++++ .../contextControls/contextControls.tsx | 194 ++++++++++++++++ app/src/modules/scene/controls/controls.tsx | 3 + .../selection3D/copyPasteControls3D.tsx | 13 +- .../selection3D/duplicationControls3D.tsx | 10 +- .../selection3D/moveControls3D.tsx | 10 +- .../selection3D/rotateControls3D.tsx | 15 +- .../selection3D/selectionControls3D.tsx | 14 +- .../undoRedo2D/undoRedo2DControls.tsx | 2 +- app/src/store/builder/store.ts | 14 +- .../utils/shortcutkeys/handleShortcutKeys.ts | 5 +- 12 files changed, 505 insertions(+), 29 deletions(-) create mode 100644 app/src/components/ui/menu/contextMenu.tsx create mode 100644 app/src/modules/scene/controls/contextControls/contextControls.tsx diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx index 6b55a26..d078765 100644 --- a/app/src/components/layout/scenes/MainScene.tsx +++ b/app/src/components/layout/scenes/MainScene.tsx @@ -3,6 +3,7 @@ import { useLoadingProgress, useRenameModeStore, useSaveVersion, + useSelectedAssets, useSelectedComment, useSelectedFloorItem, useSocketStore, @@ -58,6 +59,7 @@ function MainScene() { const { setFloatingWidget } = useFloatingWidget(); const { clearComparisonProduct } = useComparisonProduct(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); + const { selectedAssets } = useSelectedAssets(); const { assetStore, productStore } = useSceneContext(); const { products } = productStore(); const { setName } = assetStore(); @@ -97,18 +99,35 @@ function MainScene() { const handleObjectRename = async (newName: string) => { if (!projectId) return - let response = await setAssetsApi({ - modelUuid: selectedFloorItem.userData.modelUuid, - modelName: newName, - projectId - }); - selectedFloorItem.userData = { - ...selectedFloorItem.userData, - modelName: newName - }; - setSelectedFloorItem(selectedFloorItem); - setIsRenameMode(false); - setName(selectedFloorItem.userData.modelUuid, response.modelName); + if (selectedFloorItem) { + setAssetsApi({ + modelUuid: selectedFloorItem.userData.modelUuid, + modelName: newName, + projectId + }).then(() => { + selectedFloorItem.userData = { + ...selectedFloorItem.userData, + modelName: newName + }; + setSelectedFloorItem(selectedFloorItem); + setIsRenameMode(false); + setName(selectedFloorItem.userData.modelUuid, newName); + }) + } else if (selectedAssets.length === 1) { + setAssetsApi({ + modelUuid: selectedAssets[0].userData.modelUuid, + modelName: newName, + projectId + }).then(() => { + selectedAssets[0].userData = { + ...selectedAssets[0].userData, + modelName: newName + }; + setAssetsApi(selectedAssets); + setIsRenameMode(false); + setName(selectedAssets[0].userData.modelUuid, newName); + }) + } } return ( @@ -135,7 +154,7 @@ function MainScene() { {(isPlaying) && activeModule !== "simulation" && } - {isRenameMode && selectedFloorItem?.userData.modelName && } + {isRenameMode && (selectedFloorItem?.userData.modelName || selectedAssets.length === 1) && } {/* remove this later */} {activeModule === "builder" && !toggleThreeD && } @@ -188,7 +207,7 @@ function MainScene() { {activeModule !== "market" && !selectedUser &&
} - {(commentPositionState !== null || selectedComment !== null) && } + {(commentPositionState !== null || selectedComment !== null) && } ); diff --git a/app/src/components/ui/menu/contextMenu.tsx b/app/src/components/ui/menu/contextMenu.tsx new file mode 100644 index 0000000..8996f19 --- /dev/null +++ b/app/src/components/ui/menu/contextMenu.tsx @@ -0,0 +1,207 @@ +import React from "react"; + +type ContextMenuProps = { + visibility: { + rename: boolean; + focus: boolean; + flipX: boolean; + flipZ: boolean; + move: boolean; + rotate: boolean; + duplicate: boolean; + copy: boolean; + paste: boolean; + modifier: boolean; + group: boolean; + array: boolean; + delete: boolean; + }; + onRename: () => void; + onFocus: () => void; + onFlipX: () => void; + onFlipZ: () => void; + onMove: () => void; + onRotate: () => void; + onDuplicate: () => void; + onCopy: () => void; + onPaste: () => void; + onGroup: () => void; + onArray: () => void; + onDelete: () => void; +}; + +const ContextMenu: React.FC = ({ + visibility, + onRename, + onFocus, + onFlipX, + onFlipZ, + onMove, + onRotate, + onDuplicate, + onCopy, + onPaste, + onGroup, + onArray, + onDelete, +}) => { + const styles: { [key: string]: React.CSSProperties } = { + contextMenu: { + position: "absolute", + top: 0, + left: 0, + backgroundColor: "#2c2c2c", + borderRadius: "6px", + padding: "8px", + color: "white", + fontFamily: "sans-serif", + fontSize: "14px", + boxShadow: "0 0 8px rgba(0,0,0,0.4)", + zIndex: 1000, + }, + menuItem: { + margin: "4px 0", + }, + button: { + background: "none", + border: "none", + color: "inherit", + display: "flex", + justifyContent: "space-between", + width: "180px", + padding: "4px 8px", + cursor: "pointer", + }, + submenu: { + marginLeft: "10px", + marginTop: "4px", + backgroundColor: "#3a3a3a", + borderRadius: "4px", + padding: "4px", + }, + shortcut: { + opacity: 0.6, + }, + }; + + return ( +
+
+ {visibility.rename && ( +
+ +
+ )} + {visibility.focus && ( +
+ +
+ )} + {visibility.flipX && ( +
+ +
+ )} + {visibility.flipZ && ( +
+ +
+ )} + {(visibility.move || visibility.rotate) && ( +
+ +
+ {visibility.move && ( +
+ +
+ )} + {visibility.rotate && ( +
+ +
+ )} +
+
+ )} + {visibility.duplicate && ( +
+ +
+ )} + {visibility.copy && ( +
+ +
+ )} + {visibility.paste && ( +
+ +
+ )} + {visibility.modifier && ( +
+ +
+ )} + {(visibility.group || visibility.array) && ( +
+ +
+ {visibility.group && ( +
+ +
+ )} + {visibility.array && ( +
+ +
+ )} +
+
+ )} + {visibility.delete && ( +
+ +
+ )} +
+
+ ); +}; + +export default ContextMenu; diff --git a/app/src/modules/scene/controls/contextControls/contextControls.tsx b/app/src/modules/scene/controls/contextControls/contextControls.tsx new file mode 100644 index 0000000..38219ec --- /dev/null +++ b/app/src/modules/scene/controls/contextControls/contextControls.tsx @@ -0,0 +1,194 @@ +import { useEffect, useState } from 'react'; +import { useThree } from '@react-three/fiber'; +import { CameraControls, Html, ScreenSpace } from '@react-three/drei'; +import { useContextActionStore, useRenameModeStore, useSelectedAssets } from '../../../../store/builder/store'; +import ContextMenu from '../../../../components/ui/menu/contextMenu'; + +function ContextControls() { + const { gl, controls } = useThree(); + const [canRender, setCanRender] = useState(false); + const [visibility, setVisibility] = useState({ rename: true, focus: true, flipX: true, flipZ: true, move: true, rotate: true, duplicate: true, copy: true, paste: true, modifier: false, group: false, array: false, delete: true, }); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + const { selectedAssets } = useSelectedAssets(); + const { setContextAction } = useContextActionStore(); + const { setIsRenameMode } = useRenameModeStore(); + + useEffect(() => { + if (selectedAssets.length === 1) { + setVisibility({ + rename: true, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: false, + array: false, + delete: true, + }); + } else if (selectedAssets.length > 1) { + setVisibility({ + rename: false, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: true, + array: false, + delete: true, + }); + } else { + setVisibility({ + rename: false, + focus: false, + flipX: false, + flipZ: false, + move: false, + rotate: false, + duplicate: false, + copy: false, + paste: false, + modifier: false, + group: false, + array: false, + delete: false, + }); + } + }, [selectedAssets]); + + useEffect(() => { + const canvasElement = gl.domElement; + + const handleContextClick = (event: MouseEvent) => { + event.preventDefault(); + if (selectedAssets.length > 0) { + setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 }); + setCanRender(true); + if (controls) { + (controls as CameraControls).enabled = false; + } + } else { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + } + }; + + if (selectedAssets.length > 0) { + canvasElement.addEventListener('contextmenu', handleContextClick) + } else { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setMenuPosition({ x: 0, y: 0 }); + } + + return () => { + canvasElement.removeEventListener('contextmenu', handleContextClick); + }; + }, [gl, selectedAssets]); + + const handleAssetRename = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("renameAsset"); + setIsRenameMode(true); + } + const handleAssetFocus = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("focusAsset"); + } + const handleAssetMove = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("moveAsset") + } + const handleAssetRotate = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("rotateAsset") + } + const handleAssetCopy = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("copyAsset") + } + const handleAssetPaste = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("pasteAsset") + } + const handleAssetDelete = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("deleteAsset") + } + const handleAssetDuplicate = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("duplicateAsset") + } + + return ( + <> + {canRender && ( + + + handleAssetRename()} + onFocus={() => handleAssetFocus()} + onFlipX={() => console.log("Flip to X")} + onFlipZ={() => console.log("Flip to Z")} + onMove={() => handleAssetMove()} + onRotate={() => handleAssetRotate()} + onDuplicate={() => handleAssetDuplicate()} + onCopy={() => handleAssetCopy()} + onPaste={() => handleAssetPaste()} + onGroup={() => console.log("Group")} + onArray={() => console.log("Array")} + onDelete={() => handleAssetDelete()} + /> + + + )} + + ); +} + +export default ContextControls; diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index 79b9c9f..354d2bd 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -14,6 +14,7 @@ import TransformControl from "./transformControls/transformControls"; import { useParams } from "react-router-dom"; import { getUserData } from "../../../functions/getUserData"; +import ContextControls from "./contextControls/contextControls"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; @@ -146,6 +147,8 @@ export default function Controls() { + + ); } \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index e28da3c..b9056d7 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -2,7 +2,7 @@ import * as THREE from "three"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; -import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; +import { useContextActionStore, 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"; @@ -40,6 +40,7 @@ const CopyPasteControls3D = ({ const [relativePositions, setRelativePositions] = useState([]); const [centerOffset, setCenterOffset] = useState(null); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); + const { contextAction, setContextAction } = useContextActionStore() const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => { if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] }; @@ -57,6 +58,16 @@ const CopyPasteControls3D = ({ return { center, relatives }; }, []); + useEffect(() => { + if (contextAction === "copyAsset") { + setContextAction(null); + copySelection() + } else if (contextAction === "pasteAsset") { + setContextAction(null); + pasteCopiedObjects() + } + }, [contextAction]) + useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index f494025..153ff07 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -2,7 +2,7 @@ import * as THREE from "three"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; -import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; +import { useContextActionStore, 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"; @@ -38,12 +38,20 @@ const DuplicationControls3D = ({ const [initialPositions, setInitialPositions] = useState>({}); const [isDuplicating, setIsDuplicating] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); + const { contextAction, setContextAction } = useContextActionStore() const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); return new THREE.Vector3().subVectors(pointPosition, hitPoint); }, []); + useEffect(() => { + if (contextAction === "duplicateAsset") { + setContextAction(null); + duplicateSelection() + } + }, [contextAction]) + useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 6f65300..2a8146c 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store"; +import { useContextActionStore, 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"; @@ -47,6 +47,7 @@ function MoveControls3D({ const [initialStates, setInitialStates] = useState>({}); const [isMoving, setIsMoving] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); + const { contextAction, setContextAction } = useContextActionStore() const updateBackend = ( productName: string, @@ -63,6 +64,13 @@ function MoveControls3D({ }); }; + useEffect(() => { + if (contextAction === "moveAsset") { + setContextAction(null); + moveAssets() + } + }, [contextAction]) + useEffect(() => { if (!camera || !scene || toggleView) return; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index 0ca114d..7a8f7d4 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; +import { useContextActionStore, 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"; @@ -42,10 +42,8 @@ function RotateControls3D({ const [isRotating, setIsRotating] = useState(false); const prevPointerPosition = useRef(null); const rotationCenter = useRef(null); - const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ - left: false, - right: false, - }); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); + const { contextAction, setContextAction } = useContextActionStore() const updateBackend = useCallback(( productName: string, @@ -62,6 +60,13 @@ function RotateControls3D({ }); }, [selectedVersion]); + useEffect(() => { + if (contextAction === "rotateAsset") { + setContextAction(null); + rotateAssets() + } + }, [contextAction]) + useEffect(() => { if (!camera || !scene || toggleView) return; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index c72da8b..9838189 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -10,7 +10,7 @@ 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 { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; import DuplicationControls3D from "./duplicationControls3D"; import CopyPasteControls3D from "./copyPasteControls3D"; @@ -33,6 +33,7 @@ const SelectionControls3D: React.FC = () => { const { socket } = useSocketStore(); const { assetStore, eventStore, productStore } = useSceneContext(); const { removeAsset } = assetStore(); + const { contextAction, setContextAction } = useContextActionStore() const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); @@ -65,6 +66,13 @@ const SelectionControls3D: React.FC = () => { }); }; + useEffect(() => { + if (contextAction === "deleteAsset") { + setContextAction(null); + deleteSelection() + } + }, [contextAction]) + useEffect(() => { if (!camera || !scene || toggleView) return; @@ -107,7 +115,7 @@ const SelectionControls3D: React.FC = () => { if (event.button === 2 && !event.ctrlKey && !event.shiftKey) { isRightClick.current = false; if (!rightClickMoved.current) { - clearSelection(); + // clearSelection(); } return; } @@ -192,7 +200,7 @@ const SelectionControls3D: React.FC = () => { const onContextMenu = (event: MouseEvent) => { event.preventDefault(); if (!rightClickMoved.current) { - clearSelection(); + // clearSelection(); } rightClickMoved.current = false; }; diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx index d45518c..ecd70e1 100644 --- a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx +++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx @@ -18,7 +18,7 @@ function UndoRedo2DControls() { const { selectedVersion } = selectedVersionStore(); useEffect(() => { - console.log(undoStack, redoStack); + // console.log(undoStack, redoStack); }, [undoStack, redoStack]); useEffect(() => { diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index aa1499b..6c3c12e 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -1,3 +1,4 @@ +import { Object3D } from "three"; import { create } from "zustand"; import { io } from "socket.io-client"; import * as CONSTANTS from "../../types/world/worldConstants"; @@ -166,9 +167,14 @@ export const useNavMesh = create((set: any) => ({ setNavMesh: (x: any) => set({ navMesh: x }), })); -export const useSelectedAssets = create((set: any) => ({ +type SelectedAssetsState = { + selectedAssets: Object3D[]; + setSelectedAssets: (assets: Object3D[]) => void; +}; + +export const useSelectedAssets = create((set) => ({ selectedAssets: [], - setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), + setSelectedAssets: (assets) => set({ selectedAssets: assets }), })); export const useLayers = create((set: any) => ({ @@ -632,3 +638,7 @@ export const useSelectedPath = create((set: any) => ({ selectedPath: "auto", setSelectedPath: (x: any) => set({ selectedPath: x }), })); +export const useContextActionStore = create((set: any) => ({ + contextAction: null, + setContextAction: (x: any) => set({ contextAction: x }), +})); diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index ffd9ddf..f98cd58 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -11,6 +11,7 @@ import useVersionHistoryVisibleStore, { useDfxUpload, useRenameModeStore, useSaveVersion, + useSelectedAssets, useSelectedComment, useSelectedFloorItem, useSelectedWallItem, @@ -50,6 +51,7 @@ const KeyPressListener: React.FC = () => { const { setViewSceneLabels } = useViewSceneStore(); const { isRenameMode, setIsRenameMode } = useRenameModeStore(); const { selectedFloorItem } = useSelectedFloorItem(); + const { selectedAssets } = useSelectedAssets(); const { setCreateNewVersion } = useVersionHistoryStore(); const { setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { setSelectedComment } = useSelectedComment(); @@ -254,7 +256,7 @@ const KeyPressListener: React.FC = () => { setViewSceneLabels((prev) => !prev); } - if (selectedFloorItem && keyCombination === "F2") { + if ((selectedFloorItem || selectedAssets.length === 1) && keyCombination === "F2") { setIsRenameMode(true); } @@ -281,6 +283,7 @@ const KeyPressListener: React.FC = () => { hidePlayer, selectedFloorItem, isRenameMode, + selectedAssets ]); return null; From 3576a65aebea383e9fa76c04ce8356850b5c0e14 Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Tue, 12 Aug 2025 11:05:48 +0530 Subject: [PATCH 25/38] Add context menu icons and styles for improved UI interaction --- app/src/components/icons/ContextMenuIcons.tsx | 150 ++++++++++ app/src/components/ui/menu/contextMenu.tsx | 282 ++++++++---------- app/src/pages/UserAuth.tsx | 6 +- .../components/contextMenu/_contextMenu.scss | 62 ++++ app/src/styles/main.scss | 3 +- 5 files changed, 348 insertions(+), 155 deletions(-) create mode 100644 app/src/styles/components/contextMenu/_contextMenu.scss diff --git a/app/src/components/icons/ContextMenuIcons.tsx b/app/src/components/icons/ContextMenuIcons.tsx index e249bad..210f1b9 100644 --- a/app/src/components/icons/ContextMenuIcons.tsx +++ b/app/src/components/icons/ContextMenuIcons.tsx @@ -160,3 +160,153 @@ export function RenameIcon() { ); } + +export function FocusIcon() { + return ( + + + + + ); +} + +export function TransformIcon() { + return ( + + + + + + + + + + + + ); +} + +export function DublicateIcon() { + return ( + + + + + + + + + + + + + + ); +} + +export function CopyIcon() { + return ( + + + + + + + + + + + + ); +} + +export function PasteIcon() { + return ( + + + + + + + + + + + + ); +} + +export function ModifiersIcon() { + return ( + + + + + ); +} + +export function DeleteIcon() { + return ( + + + + + + + + + + + ); +} + +export function MoveIcon() { + return ( + + + + + + + + + + + ); +} + +export function RotateIcon() { + return ( + + + + ); +} + +export function GroupIcon() { + return ( + + + + ); +} + +export function ArrayIcon() { + return ( + + + + + + ); +} + +export function SubMenuIcon() { + return ( + + + + + ); +} + diff --git a/app/src/components/ui/menu/contextMenu.tsx b/app/src/components/ui/menu/contextMenu.tsx index 8996f19..9069da8 100644 --- a/app/src/components/ui/menu/contextMenu.tsx +++ b/app/src/components/ui/menu/contextMenu.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { ArrayIcon, CopyIcon, DeleteIcon, DublicateIcon, FlipXAxisIcon, FlipZAxisIcon, FocusIcon, GroupIcon, ModifiersIcon, MoveIcon, PasteIcon, RenameIcon, RotateIcon, SubMenuIcon, TransformIcon } from "../../icons/ContextMenuIcons"; type ContextMenuProps = { visibility: { @@ -45,161 +46,140 @@ const ContextMenu: React.FC = ({ onArray, onDelete, }) => { - const styles: { [key: string]: React.CSSProperties } = { - contextMenu: { - position: "absolute", - top: 0, - left: 0, - backgroundColor: "#2c2c2c", - borderRadius: "6px", - padding: "8px", - color: "white", - fontFamily: "sans-serif", - fontSize: "14px", - boxShadow: "0 0 8px rgba(0,0,0,0.4)", - zIndex: 1000, - }, - menuItem: { - margin: "4px 0", - }, - button: { - background: "none", - border: "none", - color: "inherit", - display: "flex", - justifyContent: "space-between", - width: "180px", - padding: "4px 8px", - cursor: "pointer", - }, - submenu: { - marginLeft: "10px", - marginTop: "4px", - backgroundColor: "#3a3a3a", - borderRadius: "4px", - padding: "4px", - }, - shortcut: { - opacity: 0.6, - }, - }; + + return ( -
-
- {visibility.rename && ( -
- +
+ {visibility.rename && ( +
+ + F2 +
+ )} + {visibility.focus && ( +
+ + F +
+ )} + {visibility.flipX && ( +
+ +
+ )} + {visibility.flipZ && ( +
+ +
+ )} + {(visibility.move || visibility.rotate) && ( +
+ +
+
+ {visibility.move && ( +
+ + G +
+ )} + {visibility.rotate && ( +
+ + R +
+ )}
- )} - {visibility.focus && ( -
- +
+ )} + {visibility.duplicate && ( +
+ + Ctrl + D +
+ )} + {visibility.copy && ( +
+ + Ctrl + C +
+ )} + {visibility.paste && ( +
+ + Ctrl + V +
+ )} + {visibility.modifier && ( +
+
+ +
+ )} + {(visibility.group || visibility.array) && ( +
+ +
+ {visibility.group && ( +
+ + Ctrl + G +
+ )} + {visibility.array && ( +
+ +
+ )}
- )} - {visibility.flipX && ( -
- -
- )} - {visibility.flipZ && ( -
- -
- )} - {(visibility.move || visibility.rotate) && ( -
- -
- {visibility.move && ( -
- -
- )} - {visibility.rotate && ( -
- -
- )} -
-
- )} - {visibility.duplicate && ( -
- -
- )} - {visibility.copy && ( -
- -
- )} - {visibility.paste && ( -
- -
- )} - {visibility.modifier && ( -
- -
- )} - {(visibility.group || visibility.array) && ( -
- -
- {visibility.group && ( -
- -
- )} - {visibility.array && ( -
- -
- )} -
-
- )} - {visibility.delete && ( -
- -
- )} -
+
+ )} + {visibility.delete && ( +
+ + X +
+ )}
); }; diff --git a/app/src/pages/UserAuth.tsx b/app/src/pages/UserAuth.tsx index 09e442e..fe65701 100644 --- a/app/src/pages/UserAuth.tsx +++ b/app/src/pages/UserAuth.tsx @@ -202,10 +202,10 @@ const UserAuth: React.FC = () => {
{!isSignIn && (
- -
+ +
+
)}
+ {isOpen && (
- {listType === "default" && } - {listType === "outline" && ( - <> - - - - )} +
)}
diff --git a/app/src/functions/isPointInsidePolygon.ts b/app/src/functions/isPointInsidePolygon.ts new file mode 100644 index 0000000..7fa1846 --- /dev/null +++ b/app/src/functions/isPointInsidePolygon.ts @@ -0,0 +1,20 @@ +export const isPointInsidePolygon = ( + point: [number, number], + polygon: [number, number][] +) => { + let inside = false; + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i][0], + zi = polygon[i][1]; + const xj = polygon[j][0], + zj = polygon[j][1]; + + const intersect = + // eslint-disable-next-line no-mixed-operators + zi > point[1] !== zj > point[1] && + point[0] < ((xj - xi) * (point[1] - zi)) / (zj - zi + 0.000001) + xi; + + if (intersect) inside = !inside; + } + return inside; +}; \ No newline at end of file From 79b11ad2ca3bca0cd8d7bfd985890dd8a6c2581e Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Wed, 13 Aug 2025 17:54:04 +0530 Subject: [PATCH 27/38] Refactor asset handling in MainScene and improve zone data processing in Outline; clean up console logs in ZoneProperties and List components --- app/src/components/layout/scenes/MainScene.tsx | 13 +++++++++---- app/src/components/layout/sidebarLeft/Outline.tsx | 10 +++++----- .../sidebarRight/properties/ZoneProperties.tsx | 8 ++++---- app/src/components/ui/list/List.tsx | 1 - 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx index d078765..e283986 100644 --- a/app/src/components/layout/scenes/MainScene.tsx +++ b/app/src/components/layout/scenes/MainScene.tsx @@ -59,7 +59,7 @@ function MainScene() { const { setFloatingWidget } = useFloatingWidget(); const { clearComparisonProduct } = useComparisonProduct(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); - const { selectedAssets } = useSelectedAssets(); + const { selectedAssets,setSelectedAssets } = useSelectedAssets(); const { assetStore, productStore } = useSceneContext(); const { products } = productStore(); const { setName } = assetStore(); @@ -100,10 +100,14 @@ function MainScene() { const handleObjectRename = async (newName: string) => { if (!projectId) return if (selectedFloorItem) { + console.log('selectedFloorItem.userData.modelUuid: ', selectedFloorItem.userData.modelUuid); + console.log(' newName: ', newName); + console.log('projectId: ', projectId); setAssetsApi({ modelUuid: selectedFloorItem.userData.modelUuid, modelName: newName, - projectId + projectId, + versionId: selectedVersion?.versionId || '' }).then(() => { selectedFloorItem.userData = { ...selectedFloorItem.userData, @@ -117,13 +121,14 @@ function MainScene() { setAssetsApi({ modelUuid: selectedAssets[0].userData.modelUuid, modelName: newName, - projectId + projectId, + versionId: selectedVersion?.versionId || '' }).then(() => { selectedAssets[0].userData = { ...selectedAssets[0].userData, modelName: newName }; - setAssetsApi(selectedAssets); + setSelectedAssets(selectedAssets); setIsRenameMode(false); setName(selectedAssets[0].userData.modelUuid, newName); }) diff --git a/app/src/components/layout/sidebarLeft/Outline.tsx b/app/src/components/layout/sidebarLeft/Outline.tsx index c459925..c23e77c 100644 --- a/app/src/components/layout/sidebarLeft/Outline.tsx +++ b/app/src/components/layout/sidebarLeft/Outline.tsx @@ -24,12 +24,11 @@ const Outline: React.FC = () => { useEffect(() => { const updatedZoneList: ZoneData[] = zones?.map((zone: any) => { - const polygon2D = zone.points.map((p: [number, number, number]) => [p[0], p[2],]); - + const polygon2D = zone.points.map((p: any) => [p.position[0], p.position[2]]); const assetsInZone = assets.filter((item: any) => { - const [x, , z] = item.position; - return isPointInsidePolygon([x, z], polygon2D as [number, number][]); - }) + const [x, , z] = item.position; + return isPointInsidePolygon([x, z], polygon2D as [number, number][]); + }) .map((item: any) => ({ id: item.modelUuid, name: item.modelName, @@ -37,6 +36,7 @@ const Outline: React.FC = () => { rotation: item.rotation, })); + return { id: zone.zoneUuid, name: zone.zoneName, diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index 4247316..32bec03 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -42,11 +42,11 @@ const ZoneProperties: React.FC = () => { let response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); - // console.log('response: ', response); + // if (response.message === "zone updated") { setEdit(false); } else { - // console.log(response); + // } } catch (error) { echo.error("Failed to set zone view"); @@ -75,7 +75,7 @@ const ZoneProperties: React.FC = () => { // ) // ); } else { - // console.log(response?.message); + // } } function handleVectorChange( @@ -85,7 +85,7 @@ const ZoneProperties: React.FC = () => { setSelectedZone((prev) => ({ ...prev, [key]: newValue })); } const checkZoneNameDuplicate = (name: string) => { - console.log('zones: ', zones); + return zones.some( (zone: any) => zone.zoneName?.trim().toLowerCase() === name?.trim().toLowerCase() && diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index fc0f5ec..3831e54 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -136,7 +136,6 @@ const List: React.FC = ({ items = [], remove }) => { } async function handleZoneAssetName(newName: string) { - if (zoneAssetId?.id) { let response = await setAssetsApi({ modelUuid: zoneAssetId.id, From b898c51927f867c8d0e427c808e426ac4006fdfe Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 13 Aug 2025 18:19:17 +0530 Subject: [PATCH 28/38] added crane interialtion with other assets --- .../mechanics/humanMechanics.tsx | 10 +- .../actionHandler/usePickAndDropHandler.ts | 2 +- .../actionHandler/useRetrieveHandler.ts | 45 +++- .../eventManager/useCraneEventManager.ts | 95 +++++++-- .../instances/animator/materialAnimator.tsx | 57 +++++ .../instances/animator/pillarJibAnimator.tsx | 104 ++++++--- .../instances/helper/pillarJibHelper.tsx | 120 +++++++---- .../instances/instance/pillarJibInstance.tsx | 65 +++++- .../eventManager/useHumanEventManager.ts | 12 +- .../instances/animator/operatorAnimator.tsx | 182 ++++++++++++++++ .../instance/actions/operatorInstance.tsx | 168 +++++++++++++++ .../instances/instance/humanInstance.tsx | 4 + .../modules/simulation/products/products.tsx | 2 +- .../checkActiveRoboticArmsInSubsequence.ts | 3 +- .../determineExecutionMachineSequences.ts | 3 +- .../functions/determineExecutionOrder.ts | 3 +- .../functions/determineExecutionSequences.ts | 3 +- .../getConveyorSequencesInProduct.ts | 3 +- .../triggerHandler/useTriggerHandler.ts | 201 +++++++++++++++++- .../instances/animator/materialAnimator.tsx | 31 ++- .../instances/animator/vehicleAnimator.tsx | 2 +- .../instances/instance/vehicleInstance.tsx | 102 +++++++-- app/src/store/simulation/useCraneStore.ts | 19 +- app/src/store/simulation/useVehicleStore.ts | 11 + app/src/types/simulationTypes.d.ts | 8 +- 25 files changed, 1107 insertions(+), 148 deletions(-) create mode 100644 app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx create mode 100644 app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx create mode 100644 app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 9d8e393..4b29f8c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -17,7 +17,7 @@ import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; import { useParams } from "react-router-dom"; function HumanMechanics() { - const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker"); + const [activeOption, setActiveOption] = useState<"worker" | "assembly" | "operator">("worker"); const [speed, setSpeed] = useState("0.5"); const [loadCount, setLoadCount] = useState(0); const [assemblyCount, setAssemblyCount] = useState(0); @@ -78,7 +78,7 @@ function HumanMechanics() { const newCurrentAction = getActionByUuid(selectedProduct.productUuid, actionUuid); - if (newCurrentAction && (newCurrentAction.actionType === 'assembly' || newCurrentAction?.actionType === 'worker')) { + if (newCurrentAction && (newCurrentAction.actionType === 'assembly' || newCurrentAction?.actionType === 'worker' || newCurrentAction?.actionType === "operator")) { if (!selectedAction.actionId) { setSelectedAction(newCurrentAction.actionUuid, newCurrentAction.actionName); } @@ -117,7 +117,7 @@ function HumanMechanics() { const handleSelectActionType = (actionType: string) => { if (!selectedAction.actionId || !currentAction || !selectedPointData) return; - const updatedAction = { ...currentAction, actionType: actionType as "worker" | "assembly" }; + const updatedAction = { ...currentAction, actionType: actionType as "worker" | "assembly" | "operator" }; const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); const updatedPoint = { ...selectedPointData, actions: updatedActions }; @@ -397,12 +397,12 @@ function HumanMechanics() { - {currentAction.actionType === 'worker' && + {(currentAction.actionType === 'worker' || currentAction.actionType === "operator") && >(new Map()); const retrievalCountRef = useRef>(new Map()); const monitoredHumansRef = useRef>(new Set()); + const cranePickupLockRef = useRef>(new Map()); const [initialDelayComplete, setInitialDelayComplete] = useState(false); const delayTimerRef = useRef(null); @@ -450,7 +452,48 @@ export function useRetrieveHandler() { } } } + } else if (triggeredModel?.type === 'crane') { + const crane = getCraneById(triggeredModel.modelUuid); + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + const currentCount = retrievalCountRef.current.get(actionUuid) ?? 0; + + if (!crane) return; + + const hasLock = cranePickupLockRef.current.get(crane.modelUuid) || false; + + if (action && action.actionType === 'pickAndDrop' && !hasLock && !crane.isCarrying && !crane.isActive && crane.currentLoad < (action?.maxPickUpCount || 0)) { + const material = getLastMaterial(storageUnit.modelUuid); + if (material) { + incrementCraneLoad(crane.modelUuid, 1); + addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId); + addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId); + + cranePickupLockRef.current.set(crane.modelUuid, true); + } + } else if (crane.isCarrying && crane.currentPhase === 'pickup-drop' && hasLock) { + cranePickupLockRef.current.set(crane.modelUuid, false); + + const lastMaterial = getLastMaterial(storageUnit.modelUuid); + if (lastMaterial) { + const material = createNewMaterial( + lastMaterial.materialId, + lastMaterial.materialType, + storageUnit.point.action + ); + if (material) { + removeLastMaterial(storageUnit.modelUuid); + updateCurrentLoad(storageUnit.modelUuid, -1); + retrievalCountRef.current.set(actionUuid, currentCount + 1); + } + } + } else if (!action) { + const action = getActionByUuid(selectedProduct.productUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid || ''); + if (action) { + addCurrentActionToCrane(crane.modelUuid, action.actionUuid, null, null); + } + } } + }); if (hasChanges || completedActions.length > 0) { diff --git a/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts index f8d40d9..dd8af03 100644 --- a/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts +++ b/app/src/modules/simulation/crane/eventManager/useCraneEventManager.ts @@ -5,10 +5,13 @@ import { useSceneContext } from '../../../scene/sceneContext'; import { useProductContext } from '../../products/productContext'; export function useCraneEventManager() { - const { craneStore, productStore, assetStore, craneEventManagerRef } = useSceneContext(); + const { craneStore, productStore, assetStore, vehicleStore, armBotStore, machineStore, craneEventManagerRef } = useSceneContext(); const { getCraneById, setCurrentPhase, removeCurrentAction } = craneStore(); const { getAssetById } = assetStore(); - const { getActionByUuid } = productStore(); + const { getVehicleById } = vehicleStore(); + const { getArmBotById } = armBotStore(); + const { getMachineById } = machineStore(); + const { getActionByUuid, getEventByModelUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); @@ -78,17 +81,85 @@ export function useCraneEventManager() { if (crane.isActive || crane.state !== "idle") continue; if (currentCraneAction.actionType === 'pickAndDrop' && crane.currentLoad < currentCraneAction.maxPickUpCount) { - if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { - setCurrentPhase(crane.modelUuid, 'init'); - removeCurrentAction(crane.modelUuid); + const humanAction = getActionByUuid(selectedProduct.productUuid, currentCraneAction.triggers[0].triggeredAsset?.triggeredAction?.actionUuid || ''); + if (humanAction) { + const nextEvent = getEventByModelUuid(selectedProduct.productUuid, humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid || '') + if (nextEvent) { + if (nextEvent.type === 'transfer') { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } else if (nextEvent.type === 'vehicle') { + const vehicle = getVehicleById(nextEvent.modelUuid); + + if (vehicle && !vehicle.isActive && vehicle.currentPhase === 'picking') { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } else if (nextEvent.type === 'roboticArm') { + const armBot = getArmBotById(nextEvent.modelUuid); + + if (armBot && !armBot.isActive) { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } else if (nextEvent.type === 'machine') { + const machine = getMachineById(nextEvent.modelUuid); + + if (machine && !machine.isActive) { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } else { + if (currentAction.actionUuid !== crane.currentAction?.actionUuid) { + setCurrentPhase(crane.modelUuid, 'init'); + removeCurrentAction(crane.modelUuid); + } + + craneState.isProcessing = false; + currentAction.callback(); + + setTimeout(() => { + completeCurrentAction(craneState); + }, 1000); + } + } } - - craneState.isProcessing = false; - currentAction.callback(); - - setTimeout(() => { - completeCurrentAction(craneState); - }, 1000); } } }, 0); diff --git a/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx new file mode 100644 index 0000000..796a897 --- /dev/null +++ b/app/src/modules/simulation/crane/instances/animator/materialAnimator.tsx @@ -0,0 +1,57 @@ +import { useFrame, useThree } from '@react-three/fiber'; +import { useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { MaterialModel } from '../../../materials/instances/material/materialModel'; + +type MaterialAnimatorProps = { + crane: CraneStatus; +}; + +export default function MaterialAnimator({ crane }: Readonly) { + const materialRef = useRef(null); + const { scene } = useThree(); + const [isRendered, setIsRendered] = useState(false); + + useEffect(() => { + if (crane.isCarrying) { + setIsRendered(true); + } else { + setIsRendered(false); + } + }, [crane.isCarrying]); + + useFrame(() => { + const craneModel = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!materialRef.current || !craneModel) return; + + const base = craneModel.getObjectByName('hook'); + if (!base) return; + + if (crane.isCarrying) { + const boneWorldPos = new THREE.Vector3(); + base.getWorldPosition(boneWorldPos); + + const yOffset = -0.65; + boneWorldPos.y += yOffset; + + materialRef.current.position.copy(boneWorldPos); + + materialRef.current.up.set(0, 1, 0); + materialRef.current.lookAt( + materialRef.current.position.clone().add(new THREE.Vector3(0, 0, 1)) + ); + } + }); + + return ( + <> + {isRendered && ( + + )} + + ); +} diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx index 1b1332d..96455b2 100644 --- a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -3,6 +3,8 @@ import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; +import { dragAction } from '@use-gesture/react'; function PillarJibAnimator({ crane, @@ -20,8 +22,12 @@ function PillarJibAnimator({ onAnimationComplete: (action: string) => void; }) { const { scene } = useThree(); - const { assetStore } = useSceneContext(); + const { assetStore, productStore, materialStore } = useSceneContext(); + const { getActionByUuid, getPointByUuid } = productStore(); const { resetAsset } = assetStore(); + const { setIsVisible } = materialStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); const { isPaused } = usePauseButtonStore(); const { isPlaying } = usePlayButtonStore(); const { isReset } = useResetButtonStore(); @@ -49,7 +55,9 @@ function PillarJibAnimator({ if (crane.currentPhase === 'init-pickup') { if (crane.currentMaterials.length > 0) { - const material = scene.getObjectByProperty('uuid', crane.currentMaterials[0].materialId); + const materials = scene.getObjectsByProperty('uuid', crane.currentMaterials[0].materialId); + console.log('materials: ', materials); + const material = materials.find((material) => material.visible === true); if (material) { const materialWorld = new THREE.Vector3(); material.getWorldPosition(materialWorld); @@ -62,12 +70,34 @@ function PillarJibAnimator({ ); } } + } else if (crane.currentPhase === 'pickup-drop') { + if (crane.currentMaterials.length > 0) { + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + const humanAction = getActionByUuid(selectedProduct.productUuid, action?.triggers[0].triggeredAsset?.triggeredAction?.actionUuid || ''); + + if (humanAction) { + const point = getPointByUuid(selectedProduct.productUuid, humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid || '', humanAction.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid || ''); + const eventModel = scene.getObjectByProperty('uuid', humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid); + if (point && eventModel) { + const pointLocal = new THREE.Vector3(...point.position); + const pointWorld = pointLocal.clone().applyMatrix4(eventModel.matrixWorld); + + const startPoint = new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z); + const endPoint = new THREE.Vector3(pointWorld.x, pointWorld.y + 0.5, pointWorld.z); + + setIsVisible(crane.currentMaterials[0].materialId, false); + + setAnimationPhase('init-hook-adjust'); + setPoints([startPoint, endPoint]); + } + } + } } }, [crane.currentPhase]) useEffect(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); - if (!model) return; + if (!model || !model.userData.fieldData) return; const base = model.getObjectByName('base'); const trolley = model.getObjectByName('trolley'); @@ -84,10 +114,10 @@ function PillarJibAnimator({ const hookWorld = new THREE.Vector3(); hook.getWorldPosition(hookWorld); - const trolleyMinOffset = -1; - const trolleyMaxOffset = 1.75; - const hookMinOffset = 0.25; - const hookMaxOffset = -1.5; + const trolleyMinOffset = model.userData.trolleyMinOffset || -1; + const trolleyMaxOffset = model.userData.trolleyMaxOffset || 1.75; + const hookMinOffset = model.userData.hookMinOffset || 0.25; + const hookMaxOffset = model.userData.hookMaxOffset || -1.5; const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05); @@ -159,18 +189,27 @@ function PillarJibAnimator({ } const { animationData } = model.userData; - const hookSpeed = 0.01 * speed; - const rotationSpeed = 0.005 * speed; - const trolleySpeed = 0.01 * speed; + const hookSpeed = (model.userData.hookSpeed || 0.01) * speed; + const rotationSpeed = (model.userData.rotationSpeed || 0.005) * speed; + const trolleySpeed = (model.userData.trolleySpeed || 0.01) * speed; + + const threshold = Math.max(0.01, 0.05 / speed); switch (animationPhase) { case 'init-hook-adjust': { const hookWorld = new THREE.Vector3(); hook.getWorldPosition(hookWorld); - const direction = Math.sign((clampedPoints[0].y - baseWorld.y) - hookWorld.y); - hook.position.y += direction * hookSpeed; + const targetY = clampedPoints[0].y; + const direction = Math.sign(targetY - hookWorld.y); - if (parseFloat(Math.abs(hookWorld.y - clampedPoints[0].y).toFixed(2)) < 0.05) { + hook.position.y = THREE.MathUtils.lerp( + hook.position.y, + hook.position.y + direction * 0.1, + Math.min(hookSpeed, 0.9) + ); + + if (Math.abs(hookWorld.y - targetY) < threshold) { + hook.position.y = targetY - baseWorld.y; setAnimationPhase('init-rotate-base'); } break; @@ -182,7 +221,6 @@ function PillarJibAnimator({ baseForward.sub(baseWorld); const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize(); - const targetWorld = clampedPoints[0]; const targetDir = new THREE.Vector2( targetWorld.x - baseWorld.x, @@ -192,13 +230,16 @@ function PillarJibAnimator({ const currentAngle = Math.atan2(currentDir.y, currentDir.x); const targetAngle = Math.atan2(targetDir.y, targetDir.x); let angleDiff = currentAngle - targetAngle; - angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff)); - if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) { - base.rotation.y += Math.sign(angleDiff) * rotationSpeed; + if (Math.abs(angleDiff) > threshold) { + base.rotation.y = THREE.MathUtils.lerp( + base.rotation.y, + base.rotation.y - angleDiff, + Math.min(rotationSpeed, 0.9) + ); } else { - base.rotation.y += angleDiff; + base.rotation.y -= angleDiff; const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone()); animationData.targetTrolleyX = localTarget?.x; setAnimationPhase('init-move-trolley'); @@ -208,25 +249,32 @@ function PillarJibAnimator({ case 'init-move-trolley': { const dx = animationData.targetTrolleyX - trolley.position.x; - const direction = Math.sign(dx); - trolley.position.x += direction * trolleySpeed; - if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) { + trolley.position.x = THREE.MathUtils.lerp( + trolley.position.x, + animationData.targetTrolleyX, + Math.min(trolleySpeed, 0.9) + ); + + if (Math.abs(dx) < threshold) { trolley.position.x = animationData.targetTrolleyX; - - animationData.finalHookTargetY = hook.position.y; + animationData.finalHookTargetY = clampedPoints[0].y - baseWorld.y; setAnimationPhase('init-final-hook-adjust'); } break; } case 'init-final-hook-adjust': { - const dy = animationData.finalHookTargetY - hook.position.y; - const direction = Math.sign(dy); - hook.position.y += direction * hookSpeed; + const targetY = clampedPoints[0].y - baseWorld.y; - if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) { - hook.position.y = animationData.finalHookTargetY; + hook.position.y = THREE.MathUtils.lerp( + hook.position.y, + targetY, + Math.min(hookSpeed, 0.9) + ); + + if (Math.abs(hook.position.y - targetY) < threshold) { + hook.position.y = targetY; model.userData.animationData = { originalHookY: hook.position.y, diff --git a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx index 8998865..40ac35a 100644 --- a/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx +++ b/app/src/modules/simulation/crane/instances/helper/pillarJibHelper.tsx @@ -1,22 +1,51 @@ -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; import { useThree } from "@react-three/fiber"; import { Box, Sphere } from "@react-three/drei"; function PillarJibHelper({ crane, - points + points, + isHelperNeeded }: { crane: CraneStatus, points: [THREE.Vector3, THREE.Vector3] | null; + isHelperNeeded: boolean; }) { const { scene } = useThree(); const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>(); const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]); + const baseWorldRef = useRef(null); + const trolleyWorldRef = useRef(null); + const hookWorldRef = useRef(null); + + useEffect(() => { + const model = scene.getObjectByProperty("uuid", crane.modelUuid); + if (!model) return; + + const base = model.getObjectByName("base"); + const trolley = model.getObjectByName("trolley"); + const hook = model.getObjectByName("hook"); + + if (!base || !trolley || !hook) return; + + const bw = new THREE.Vector3(); + const tw = new THREE.Vector3(); + const hw = new THREE.Vector3(); + + base.getWorldPosition(bw); + trolley.getWorldPosition(tw); + hook.getWorldPosition(hw); + + baseWorldRef.current = bw; + trolleyWorldRef.current = tw; + hookWorldRef.current = hw; + }, [scene, crane.modelUuid]); + const { geometry, position } = useMemo(() => { const model = scene.getObjectByProperty('uuid', crane.modelUuid); - if (!model) return { geometry: null, position: null }; + if (!model || !baseWorldRef.current || !trolleyWorldRef.current || !hookWorldRef.current) return { geometry: null, position: null }; const base = model.getObjectByName('base'); const trolley = model.getObjectByName('trolley'); @@ -24,19 +53,16 @@ function PillarJibHelper({ if (!base || !trolley || !hook || !points) return { geometry: null, position: null }; - const baseWorld = new THREE.Vector3(); - base.getWorldPosition(baseWorld); + const baseWorld = baseWorldRef.current; - const trolleyWorld = new THREE.Vector3(); - trolley.getWorldPosition(trolleyWorld); + const trolleyWorld = trolleyWorldRef.current; - const hookWorld = new THREE.Vector3(); - hook.getWorldPosition(hookWorld); + const hookWorld = hookWorldRef.current; - const trolleyMinOffset = -1; - const trolleyMaxOffset = 1.75; - const hookMinOffset = 0.25; - const hookMaxOffset = -1.5; + const trolleyMinOffset = model.userData.trolleyMinOffset || -1; + const trolleyMaxOffset = model.userData.trolleyMaxOffset || 1.75; + const hookMinOffset = model.userData.hookMinOffset || 0.25; + const hookMaxOffset = model.userData.hookMaxOffset || -1.5; const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); const outerRadius = distFromBase + trolleyMaxOffset; @@ -100,40 +126,44 @@ function PillarJibHelper({ return ( <> - - - + {isHelperNeeded && + <> + + + - {points && points.map((point, i) => ( - - - - ))} + {points && points.map((point, i) => ( + + + + ))} - {clampedPoints && clampedPoints.map((point, i) => ( - - - - ))} + {clampedPoints && clampedPoints.map((point, i) => ( + + + + ))} + + } ); } diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx index c95fb95..11c06a4 100644 --- a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -3,36 +3,86 @@ import * as THREE from 'three' import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; +import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import PillarJibAnimator from '../animator/pillarJibAnimator' import PillarJibHelper from '../helper/pillarJibHelper' +import MaterialAnimator from '../animator/materialAnimator'; function PillarJibInstance({ crane }: { crane: CraneStatus }) { const { isPlaying } = usePlayButtonStore(); - const { craneStore, productStore } = useSceneContext(); - const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); - const { getCraneById, setCurrentPhase } = craneStore(); + const { craneStore, productStore, humanStore, assetStore } = useSceneContext(); + const { triggerPointActions } = useTriggerHandler(); + const { getActionByUuid } = productStore(); + const { setCurrentPhase, setCraneActive, setIsCaryying, removeCurrentAction, removeLastMaterial, decrementCraneLoad } = craneStore(); + const { setCurrentPhase: setCurrentPhaseHuman, setHumanActive, setHumanState, getHumanById } = humanStore(); + const { setCurrentAnimation, getAssetById } = assetStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const [animationPhase, setAnimationPhase] = useState('idle'); const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null); + const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); + const actionTriggers = action?.triggers || []; + const humanId = actionTriggers?.[0]?.triggeredAsset?.triggeredModel?.modelUuid ?? null; + const humanAsset = getAssetById(humanId || ''); + const humanAction = getActionByUuid(selectedProduct.productUuid, actionTriggers?.[0]?.triggeredAsset?.triggeredAction?.actionUuid ?? ''); useEffect(() => { if (isPlaying) { - const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || ''); - if (!action || action.actionType !== 'pickAndDrop') return; + const human = getHumanById(humanId || ''); + if (!human || !humanAsset || !humanId || !action || action.actionType !== 'pickAndDrop') return; if (!crane.isActive && crane.currentPhase === 'init' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) { setCurrentPhase(crane.modelUuid, 'init-pickup'); + } else if (crane.currentPhase === 'picking' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && !crane.isCarrying) { + if (action.triggers.length > 0) { + if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted && humanId && humanAction && humanAction.actionType === 'operator') { + setCurrentAnimation(humanId, 'idle', true, true, true); + setIsCaryying(crane.modelUuid, true); + setCurrentPhase(crane.modelUuid, 'pickup-drop'); + } else { + setCurrentPhaseHuman(humanId, 'hooking'); + setHumanActive(humanId, true); + setHumanState(humanId, 'running'); + setCurrentAnimation(humanId, 'working_standing', true, false, false); + } + } + } else if (crane.currentPhase === 'dropping' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && crane.isCarrying && human.currentPhase === 'hooking') { + setCurrentPhaseHuman(humanId, 'loadPoint-unloadPoint'); + } else if (human.state === 'running' && human.currentPhase === 'unhooking') { + if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted) { + setCurrentPhase(crane.modelUuid, 'init'); + setCraneActive(crane.modelUuid, false); + setCurrentAnimation(humanId, 'idle', true, true, true); + setCurrentPhaseHuman(humanId, 'init'); + setHumanActive(humanId, false); + setHumanState(humanId, 'idle'); + handleMaterialDrop(); + } } } - }, [crane]) + }, [crane, humanAsset?.animationState?.isCompleted]) + + const handleMaterialDrop = () => { + if (humanAction && humanAction.actionType === 'operator') { + setIsCaryying(crane.modelUuid, false); + removeCurrentAction(crane.modelUuid); + const removedMaterial = removeLastMaterial(crane.modelUuid); + decrementCraneLoad(crane.modelUuid, 1); + + if (removedMaterial && humanAction.triggers[0].triggeredAsset?.triggeredAction?.actionUuid) { + triggerPointActions(humanAction, removedMaterial.materialId); + } + } + } const handleAnimationComplete = (action: string) => { if (action === 'starting') { setAnimationPhase('first-hook-adjust'); } else if (action === 'picking') { setCurrentPhase(crane.modelUuid, 'picking'); + } else if (action === 'dropping') { + setCurrentPhase(crane.modelUuid, 'dropping'); } } @@ -49,9 +99,12 @@ function PillarJibInstance({ crane }: { crane: CraneStatus }) { onAnimationComplete={handleAnimationComplete} /> + + diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index f023852..1dec0d9 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -25,7 +25,7 @@ export function useHumanEventManager() { const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { const human = getHumanById(humanId); const action = getActionByUuid(selectedProduct.productUuid, actionUuid); - if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker') || !humanEventManagerRef.current) return; + if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker' && action.actionType !== 'operator') || !humanEventManagerRef.current) return; let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId); if (!state) { @@ -36,7 +36,7 @@ export function useHumanEventManager() { const existingAction = state.actionQueue.find(a => a.actionUuid === actionUuid); if (existingAction) { const currentCount = existingAction.count ?? 0; - if (existingAction.actionType === 'worker') { + if (existingAction.actionType === 'worker' || existingAction.actionType === 'operator') { if (currentCount < existingAction.maxLoadCount) { existingAction.callback = callback; existingAction.isMonitored = true; @@ -98,8 +98,8 @@ export function useHumanEventManager() { let conditionMet = false; - if (currentAction.actionType === 'worker') { - if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + if (currentAction.actionType === 'worker' || currentAction.actionType === 'operator') { + if ((action.actionType === 'worker' || action.actionType === 'operator') && human.currentLoad < currentAction.loadCapacity) { conditionMet = true; } else if (action.actionType === 'assembly') { conditionMet = true; @@ -107,7 +107,7 @@ export function useHumanEventManager() { } else if (currentAction.actionType === 'assembly') { if (action.actionType === 'assembly') { conditionMet = true; - } else if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + } else if ((action.actionType === 'worker' || action.actionType === 'operator') && human.currentLoad < currentAction.loadCapacity) { conditionMet = true; } } @@ -121,7 +121,7 @@ export function useHumanEventManager() { action.callback(); action.count = (action.count ?? 0) + 1; action.isMonitored = false; - if ((action.actionType === 'worker' && action.count >= action.maxLoadCount) || + if (((action.actionType === 'worker' || action.actionType === 'operator') && action.count >= action.maxLoadCount) || (action.actionType === 'assembly' && action.count >= action.maxAssemblyCount)) { action.isCompleted = true; } diff --git a/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx new file mode 100644 index 0000000..14934df --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/operatorAnimator.tsx @@ -0,0 +1,182 @@ +import { useEffect, useRef, useState } from 'react'; +import { useFrame, useThree } from '@react-three/fiber'; +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 WorkerAnimatorProps { + path: [number, number, number][]; + handleCallBack: () => void; + reset: () => void; + human: HumanStatus; +} + +function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly) { + const { humanStore, assetStore, productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { getHumanById } = humanStore(); + const { setCurrentAnimation } = assetStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset, setReset } = useResetButtonStore(); + const progressRef = useRef(0); + const movingForward = useRef(true); + const completedRef = useRef(false); + 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 { scene } = useThree(); + + useEffect(() => { + if (!human.currentAction?.actionUuid) return; + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + if (human.currentPhase === 'init-loadPoint' && path.length > 0) { + setCurrentPath(path); + setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null); + } else if (human.currentPhase === 'loadPoint-unloadPoint' && path.length > 0) { + setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null); + setCurrentPath(path); + } else if (human.currentPhase === 'unloadPoint-loadPoint' && path.length > 0) { + setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null); + setCurrentPath(path); + } + }, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); + + useEffect(() => { + completedRef.current = false; + }, [currentPath]); + + useEffect(() => { + if (isReset || !isPlaying) { + reset(); + setCurrentPath([]); + completedRef.current = false; + movingForward.current = true; + progressRef.current = 0; + setReset(false); + setRestingRotation(true); + const object = scene.getObjectByProperty('uuid', human.modelUuid); + const humanData = getHumanById(human.modelUuid); + if (object && humanData) { + object.position.set(humanData.position[0], humanData.position[1], humanData.position[2]); + object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]); + } + } + }, [isReset, isPlaying]); + + const lastTimeRef = useRef(performance.now()); + + useFrame(() => { + const now = performance.now(); + const delta = (now - lastTimeRef.current) / 1000; + lastTimeRef.current = now; + + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (!object || currentPath.length < 2) return; + if (isPaused || !isPlaying) return; + + let totalDistance = 0; + const distances = []; + let accumulatedDistance = 0; + let index = 0; + const rotationSpeed = 1.5; + + for (let i = 0; i < currentPath.length - 1; i++) { + const start = new THREE.Vector3(...currentPath[i]); + const end = new THREE.Vector3(...currentPath[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(...currentPath[index]); + const end = new THREE.Vector3(...currentPath[index + 1]); + const segmentDistance = distances[index]; + + const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix( + new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)) + ); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + targetQuaternion.multiply(y180); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + const isAligned = angle < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * human.speed); + const t = (progressRef.current - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation && objectRotation) { + const targetEuler = new THREE.Euler(0, objectRotation[1], 0); + const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + const targetQuaternion = baseQuaternion.multiply(y180); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + setRestingRotation(false); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + return; + } + } + + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + } + }); + + return ( + <> + {currentPath.length > 0 && ( + + + {currentPath.map((point, index) => ( + + + + + ))} + + )} + + ); +} + +export default OperatorAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx new file mode 100644 index 0000000..d938353 --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/actions/operatorInstance.tsx @@ -0,0 +1,168 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { useThree } from '@react-three/fiber'; +import { NavMeshQuery } from '@recast-navigation/core'; +import { useNavMesh } 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 OperatorAnimator from '../../animator/operatorAnimator'; + +function OperatorInstance({ human }: { human: HumanStatus }) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { scene } = useThree(); + const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, craneStore, productStore } = useSceneContext(); + const { removeMaterial, setEndTime, setIsVisible } = materialStore(); + const { getStorageUnitById } = storageUnitStore(); + const { getArmBotById } = armBotStore(); + const { getConveyorById } = conveyorStore(); + const { getVehicleById } = vehicleStore(); + const { getMachineById } = machineStore(); + const { getCraneById } = craneStore(); + const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); + const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime, setCurrentPhase } = humanStore(); + + const [path, setPath] = useState<[number, number, number][]>([]); + const pauseTimeRef = useRef(null); + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const isPausedRef = useRef(false); + const isSpeedRef = useRef(0); + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const humanAsset = getAssetById(human.modelUuid); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + 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]) || []; + } + } catch { + console.error("Failed to compute path"); + return []; + } + }, [navMesh]); + + function humanStatus(modelId: string, status: string) { + // console.log(`${modelId} , ${status}`); + } + + function reset() { + setCurrentPhase(human.modelUuid, 'init'); + setHumanActive(human.modelUuid, false); + setHumanState(human.modelUuid, 'idle'); + setHumanScheduled(human.modelUuid, false); + setHumanLoad(human.modelUuid, 0); + resetAnimation(human.modelUuid); + setPath([]); + isPausedRef.current = false; + pauseTimeRef.current = 0; + resetTime(human.modelUuid) + activeTimeRef.current = 0 + idleTimeRef.current = 0 + previousTimeRef.current = null + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current) + animationFrameIdRef.current = null + } + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (object && human) { + object.position.set(human.position[0], human.position[1], human.position[2]); + object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]); + } + } + + useEffect(() => { + if (isPlaying) { + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + if (!action || action.actionType !== 'operator' || !action.pickUpPoint || !action.dropPoint) 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?.pickUpPoint?.position || [0, 0, 0]); + + setPath(toPickupPath); + setCurrentPhase(human.modelUuid, 'init-loadPoint'); + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from init, heading to loadPoint'); + } else if (human.isActive && human.currentPhase === 'loadPoint-unloadPoint') { + if (action.pickUpPoint && action.dropPoint && humanAsset?.animationState?.current === 'idle') { + const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]); + setPath(toDrop); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from loadPoint, heading to unloadPoint'); + } + } else if (human.state === 'idle' && human.currentPhase === 'unhooking') { + setHumanState(human.modelUuid, 'running'); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'working_standing', true, false, false); + } + } else { + reset() + } + }, [human, human.currentAction, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + + function handleCallBack() { + if (human.currentPhase === 'init-loadPoint') { + setCurrentPhase(human.modelUuid, 'waiting'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached loadPoint point, waiting for material'); + setPath([]); + } else if (human.currentPhase === 'loadPoint-unloadPoint') { + setCurrentPhase(human.modelUuid, 'unhooking'); + setHumanState(human.modelUuid, 'idle'); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached loadPoint point, waiting for material'); + setPath([]); + } + } + + return ( + <> + + + ) +} + +export default OperatorInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 8d1c51f..1532e5c 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -6,6 +6,7 @@ import { useProductContext } from '../../../products/productContext'; import MaterialAnimator from '../animator/materialAnimator'; import AssemblerInstance from './actions/assemberInstance'; import WorkerInstance from './actions/workerInstance'; +import OperatorInstance from './actions/operatorInstance'; function HumanInstance({ human }: { human: HumanStatus }) { const { isPlaying } = usePlayButtonStore(); @@ -86,6 +87,9 @@ function HumanInstance({ human }: { human: HumanStatus }) { {action && action.actionType === 'assembly' && } + {action && action.actionType === 'operator' && + + } diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index 32c1162..f65e33f 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -183,7 +183,7 @@ function Products() { addCrane(selectedProduct.productUuid, events); if (events.point.actions.length > 0) { - addCurrentActionCrane(events.modelUuid, events.point.actions[0].actionUuid); + addCurrentActionCrane(events.modelUuid, events.point.actions[0].actionUuid, null, null); } } }); diff --git a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts index 58925f3..4d3f3de 100644 --- a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts +++ b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts @@ -125,7 +125,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts index a87fe6a..089f461 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts @@ -17,7 +17,8 @@ export async function determineExecutionMachineSequences(products: productsSchem event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts index d17e2d7..09be2f2 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts @@ -20,7 +20,8 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { pointMap.set(event.point.uuid, event.point); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts index 3ba5eb2..ebc26ac 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts @@ -17,7 +17,8 @@ export async function determineExecutionSequences(products: productsSchema): Pro event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { pointMap.set(event.point.uuid, event.point); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts b/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts index 8f812bf..76f2854 100644 --- a/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts +++ b/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts @@ -92,7 +92,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'machine' || event.type === 'storageUnit' || event.type === 'roboticArm' || - event.type === 'human' + event.type === 'human' || + event.type === 'crane' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index fbc4dde..9660a6d 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -25,7 +25,7 @@ export function useTriggerHandler() { const { addCraneToMonitor } = useCraneEventManager(); const { getVehicleById } = vehicleStore(); const { getHumanById, setHumanScheduled } = humanStore(); - const { getCraneById, setCraneScheduled } = craneStore(); + const { getCraneById, setCraneScheduled, addCurrentAction } = craneStore(); const { getMachineById, setMachineActive } = machineStore(); const { getStorageUnitById } = storageUnitStore(); const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore(); @@ -427,20 +427,21 @@ export function useTriggerHandler() { action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (model?.type === 'transfer') { + if (model?.type === 'human') { const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid); if (crane) { setIsPaused(materialId, true); setCraneScheduled(crane.modelUuid, true); - const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (conveyor) { - addConveyorToMonitor(conveyor.modelUuid, () => { + const human = getHumanById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (human) { + addHumanToMonitor(human.modelUuid, () => { + addCurrentAction(crane.modelUuid, action.actionUuid, null, null); addCraneToMonitor(crane.modelUuid, () => { setIsPaused(materialId, true); handleAction(action, materialId); }, action.actionUuid) - }) + }, action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid) } } } @@ -620,6 +621,66 @@ export function useTriggerHandler() { } } } + } else if (toEvent?.type === 'crane') { + // Vehicle to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + // Handle current action of the material + handleAction(action, materialId); + + if (material.next) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: material.next.modelUuid, + pointUuid: material.next.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + if (action) { + + if (crane) { + 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 === 'human') { + const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (crane) { + setIsPaused(materialId, true); + setCraneScheduled(crane.modelUuid, true); + const human = getHumanById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (human) { + addHumanToMonitor(human.modelUuid, () => { + addCurrentAction(crane.modelUuid, action.actionUuid, null, null); + addCraneToMonitor(crane.modelUuid, () => { + setIsPaused(materialId, true); + + handleAction(action, materialId); + }, action.actionUuid) + }, action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid) + } + } + } + } + } + } + } + } + } } } else if (fromEvent?.type === 'machine') { if (toEvent?.type === 'transfer') { @@ -1408,6 +1469,134 @@ export function useTriggerHandler() { setIsPaused(material.materialId, false); } } + } else if (material && action.actionType === 'operator') { + + 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, + }); + + 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') { + + handleAction(action, material.materialId); + + } else if (model?.type === 'vehicle') { + const nextAction = getActionByUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredAction.actionUuid); + + if (action) { + handleAction(action, material.materialId); + + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + + 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); + + if (nextAction) { + + if (vehicle) { + + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '', + actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '', + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + setIsPaused(materialId, false); + + // Handle current action from vehicle + handleAction(nextAction, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, () => { + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '', + actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '', + }); + + setNextLocation(material.materialId, null); + + setIsPaused(materialId, false); + setIsVisible(materialId, false); + handleAction(nextAction, materialId); + }) + } + } + } + } + + } else if (model?.type === 'transfer') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (conveyor) { + setNextLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid, + }) + setIsPaused(material.materialId, false); + setIsVisible(material.materialId, true); + handleAction(action, material.materialId); + } + } else { + setNextLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid, + }) + + handleAction(action, material.materialId); + } + } else if (action) { + setNextLocation(material.materialId, null) + + handleAction(action, material.materialId); + } + } } diff --git a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx index 8b943d9..b4c9b90 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx @@ -13,30 +13,44 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { useEffect(() => { const loadState = agvDetail.currentLoad > 0; setHasLoad(loadState); - + if (!loadState) { setIsAttached(false); - if (meshRef.current?.parent) { - meshRef.current.parent.remove(meshRef.current); + const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; + if (agvModel) { + const material = agvModel.getObjectByName('Sample-Material'); + if (material) { + agvModel.remove(material); + } } } }, [agvDetail.currentLoad]); useFrame(() => { + // if (agvDetail.currentMaterials.length === 0 || agvDetail.currentLoad === 0) { + // const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; + // if (agvModel) { + // const material = agvModel.getObjectByName('Sample-Material'); + // if (material) { + // agvModel.remove(material); + // } + // } + // } if (!hasLoad || !meshRef.current || isAttached) return; const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D; if (agvModel && !isAttached) { - if (meshRef.current.parent) { - meshRef.current.parent.remove(meshRef.current); + const material = agvModel.getObjectByName('Sample-Material'); + if (material) { + agvModel.remove(material); } - + agvModel.add(meshRef.current); - + meshRef.current.position.copy(offset); meshRef.current.rotation.set(0, 0, 0); meshRef.current.scale.set(1, 1, 1); - + setIsAttached(true); } }); @@ -45,6 +59,7 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { <> {hasLoad && agvDetail.currentMaterials.length > 0 && ( {selectedPath === "auto" && - + {currentPath.map((pos, i) => { if (i < currentPath.length - 1) { return ( diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 5de64cd..0466ba2 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -12,24 +12,26 @@ import MaterialAnimator from '../animator/materialAnimator'; import VehicleAnimator from '../animator/vehicleAnimator'; import { useHumanEventManager } from '../../../human/eventManager/useHumanEventManager'; +import { useCraneEventManager } from '../../../crane/eventManager/useCraneEventManager'; function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore, assetStore } = useSceneContext(); + const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, productStore, assetStore } = useSceneContext(); const { removeMaterial, setEndTime, setIsVisible } = materialStore(); const { getStorageUnitById } = storageUnitStore(); const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad } = humanStore(); + const { getCraneById, addCurrentAction: addCraneAction, addCurrentMaterial: addCraneMaterial, incrementCraneLoad } = craneStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { triggerPointActions } = useTriggerHandler(); const { setCurrentAnimation, getAssetById } = assetStore(); const { addHumanToMonitor } = useHumanEventManager(); + const { addCraneToMonitor } = useCraneEventManager(); 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 { vehicles, setCurrentPhase, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = vehicleStore(); const [path, setPath] = useState<[number, number, number][]>([]); const pauseTimeRef = useRef(null); const idleTimeRef = useRef(0); @@ -80,7 +82,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) // Function to reset everything function reset() { - setCurrentPhase('stationed'); + setCurrentPhase(agvDetail.modelUuid, 'stationed'); setVehicleActive(agvDetail.modelUuid, false); setVehiclePicking(agvDetail.modelUuid, false); setVehicleState(agvDetail.modelUuid, 'idle'); @@ -103,20 +105,20 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) if (isPlaying && selectedPath === "auto") { if (!agvDetail.point.action.unLoadPoint || !agvDetail.point.action.pickUpPoint) return; - if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') { + if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'stationed') { const toPickupPath = computePath( new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), agvDetail?.point?.action?.pickUpPoint?.position ); setPath(toPickupPath); - setCurrentPhase('stationed-pickup'); + setCurrentPhase(agvDetail.modelUuid, 'stationed-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup'); return; - } else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'picking') { + } else if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'picking') { if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity && agvDetail.currentMaterials.length > 0) { if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) { const toDrop = computePath( @@ -124,21 +126,21 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) agvDetail.point.action.unLoadPoint.position ); setPath(toDrop); - setCurrentPhase('pickup-drop'); + setCurrentPhase(agvDetail.modelUuid, 'pickup-drop'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point'); } } - } else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'dropping' && agvDetail.currentLoad === 0) { + } else if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'dropping' && agvDetail.currentLoad === 0) { if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) { const dropToPickup = computePath( agvDetail.point.action.unLoadPoint.position, agvDetail.point.action.pickUpPoint.position ); setPath(dropToPickup); - setCurrentPhase('drop-pickup'); + setCurrentPhase(agvDetail.modelUuid, 'drop-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); @@ -149,7 +151,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) else { reset() } - }, [vehicles, currentPhase, path, isPlaying, selectedPath]); + }, [vehicles, agvDetail.currentPhase, path, isPlaying, selectedPath]); function animate(currentTime: number) { if (previousTimeRef.current === null) { @@ -198,22 +200,22 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) }, [agvDetail, isPlaying]); function handleCallBack() { - if (currentPhase === 'stationed-pickup') { - setCurrentPhase('picking'); + if (agvDetail.currentPhase === 'stationed-pickup') { + setCurrentPhase(agvDetail.modelUuid, 'picking'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, true); setVehicleActive(agvDetail.modelUuid, false); vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material'); setPath([]); - } else if (currentPhase === 'pickup-drop') { - setCurrentPhase('dropping'); + } else if (agvDetail.currentPhase === 'pickup-drop') { + setCurrentPhase(agvDetail.modelUuid, 'dropping'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, false); vehicleStatus(agvDetail.modelUuid, 'Reached drop point'); setPath([]); - } else if (currentPhase === 'drop-pickup') { - setCurrentPhase('picking'); + } else if (agvDetail.currentPhase === 'drop-pickup') { + setCurrentPhase(agvDetail.modelUuid, 'picking'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, true); setVehicleActive(agvDetail.modelUuid, false); @@ -252,6 +254,12 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) if (action && (triggeredAction?.actionType === 'assembly' || triggeredAction?.actionType === 'worker')) { handleMaterialDropToHuman(model, triggeredAction); } + } else if (model.type === 'crane') { + const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid); + if (action && (triggeredAction?.actionType === 'pickAndDrop')) { + handleMaterialDropToCrane(model, triggeredAction); + addCraneAction(model.modelUuid, triggeredAction.actionUuid, null, null); + } } } else { const droppedMaterial = agvDetail.currentLoad; @@ -265,6 +273,64 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } } + function handleMaterialDropToCrane(model: CraneEventSchema, action: CraneAction) { + + if (model) { + if (action.actionType === 'pickAndDrop') { + addCraneToMonitor(model.modelUuid, () => { + loopMaterialDropToCrane( + agvDetail, + model.modelUuid, + action.actionUuid + ); + }, action.actionUuid || '') + } + } + } + + function loopMaterialDropToCrane( + vehicle: VehicleStatus, + craneId: string, + craneActionId: string + ) { + let currentVehicleLoad = vehicle.currentLoad; + + const unloadLoop = () => { + const crane = getCraneById(craneId); + const craneaction = crane?.point.actions.find((action) => action.actionUuid === craneActionId); + + if (!crane || crane.currentAction?.actionUuid !== craneaction?.actionUuid) return; + if (crane.isCarrying) { + decrementVehicleLoad(vehicle.modelUuid, 1); + currentVehicleLoad -= 1; + const material = removeLastMaterial(vehicle.modelUuid); + if (material) { + setIsVisible(material.materialId, false); + } + return; + } else if (!crane.isCarrying && !crane.isActive && crane.currentLoad < (craneaction?.maxPickUpCount || 0) && craneaction?.actionType === 'pickAndDrop') { + const material = getLastMaterial(vehicle.modelUuid); + if (material) { + incrementCraneLoad(craneId, 1); + addCraneAction(craneId, craneActionId, material.materialType, material.materialId); + addCraneMaterial(craneId, material.materialType, material.materialId); + } + } + setTimeout(() => { + requestAnimationFrame(unloadLoop); + }, 500) + }; + + const crane = getCraneById(craneId); + const craneaction = crane?.point.actions.find((action) => action.actionUuid === craneActionId); + if (crane && crane.currentLoad < (craneaction?.maxPickUpCount || 0)) { + setTimeout(() => { + unloadLoop(); + }, 500) + } + } + + function handleMaterialDropToHuman(model: HumanEventSchema, action: HumanAction) { if (model) { @@ -576,7 +642,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) void; - addCurrentAction: (modelUuid: string, actionUuid: string) => void; + addCurrentAction: (modelUuid: string, actionUuid: string, materialType: string | null, materialId: string | null) => void; removeCurrentAction: (modelUuid: string) => void; setCraneActive: (modelUuid: string, isActive: boolean) => void; setCraneScheduled: (modelUuid: string, isScheduled: boolean) => void; + setIsCaryying: (modelUuid: string, isCarrying: boolean) => void; setCraneLoad: (modelUuid: string, load: number) => void; setCraneState: (modelUuid: string, newState: CraneStatus["state"]) => void; incrementCraneLoad: (modelUuid: string, incrementBy: number) => void; @@ -54,6 +55,7 @@ export const createCraneStore = () => { currentPhase: 'init', isActive: false, isScheduled: false, + isCarrying: false, idleTime: 0, activeTime: 0, currentLoad: 0, @@ -93,7 +95,7 @@ export const createCraneStore = () => { }); }, - addCurrentAction: (modelUuid, actionUuid) => { + addCurrentAction: (modelUuid, actionUuid, materialType, materialId) => { set((state) => { const crane = state.cranes.find(c => c.modelUuid === modelUuid); if (crane) { @@ -101,7 +103,9 @@ export const createCraneStore = () => { if (action) { crane.currentAction = { actionUuid: action.actionUuid, - actionName: action.actionName + actionName: action.actionName, + materialType: materialType, + materialId: materialId }; } } @@ -135,6 +139,15 @@ export const createCraneStore = () => { }); }, + setIsCaryying: (modelUuid, isCarrying) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.isCarrying = isCarrying; + } + }); + }, + setCraneLoad: (modelUuid, load) => { set((state) => { const crane = state.cranes.find(c => c.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index 1e558f0..e01bbce 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -15,6 +15,7 @@ interface VehiclesStore { deletePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string) => VehicleAction["paths"]; clearVehicles: () => void; + setCurrentPhase: (modelUuid: string, phase: string) => void; setVehicleActive: (modelUuid: string, isActive: boolean) => void; setVehiclePicking: (modelUuid: string, isPicking: boolean) => void; updateSteeringAngle: (modelUuid: string, steeringAngle: number) => void; @@ -52,6 +53,7 @@ export const createVehicleStore = () => { state.vehicles.push({ ...event, productUuid, + currentPhase: 'stationed', isActive: false, isPicking: false, idleTime: 0, @@ -130,6 +132,15 @@ export const createVehicleStore = () => { }); }, + setCurrentPhase: (modelUuid, phase) => { + set((state) => { + const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); + if (vehicle) { + vehicle.currentPhase = phase; + } + }); + }, + setVehicleActive: (modelUuid, isActive) => { set((state) => { const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 1e90fe8..ae6c55a 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -98,7 +98,7 @@ interface StorageAction { interface HumanAction { actionUuid: string; actionName: string; - actionType: "worker" | "assembly"; + actionType: "worker" | "assembly" | "operator"; processTime: number; swapMaterial?: string; assemblyPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } @@ -264,6 +264,7 @@ interface ArmBotStatus extends RoboticArmEventSchema { interface VehicleStatus extends VehicleEventSchema { productUuid: string; + currentPhase: string; isActive: boolean; isPicking: boolean; idleTime: number; @@ -303,6 +304,7 @@ interface CraneStatus extends CraneEventSchema { currentPhase: string; isActive: boolean; isScheduled: boolean; + isCarrying: boolean; idleTime: number; activeTime: number; currentLoad: number; @@ -310,6 +312,8 @@ interface CraneStatus extends CraneEventSchema { currentAction?: { actionUuid: string; actionName: string; + materialType: string | null; + materialId: string | null; }; } @@ -319,7 +323,7 @@ interface CraneStatus extends CraneEventSchema { type HumanEventState = { humanId: string; actionQueue: { - actionType: 'worker' | 'assembly'; + actionType: 'worker' | 'assembly' | 'operator'; actionUuid: string; actionName: string; maxLoadCount: number; From d0b7aa9a9a3449bccd7cd3185ec4e4572773e944 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 13 Aug 2025 18:21:35 +0530 Subject: [PATCH 29/38] file name capitialization --- ...assemblyAction.tsx => AssemblyAction1.tsx} | 2 +- .../{DefaultAction.tsx => DefaultAction1.tsx} | 14 +- .../{DelayAction.tsx => DelayAction1.tsx} | 0 .../{DespawnAction.tsx => DespawnAction1.tsx} | 20 +-- ...laceAction.tsx => PickAndPlaceAction1.tsx} | 58 +++---- ...llarJibAction.tsx => PillarJibAction1.tsx} | 0 .../{ProcessAction.tsx => ProcessAction1.tsx} | 96 ++++++------ .../{SpawnAction.tsx => SpawnAction1.tsx} | 144 +++++++++--------- .../{StorageAction.tsx => StorageAction1.tsx} | 114 +++++++------- .../{SwapAction.tsx => SwapAction1.tsx} | 50 +++--- .../{TravelAction.tsx => TravelAction1.tsx} | 140 ++++++++--------- .../{workerAction.tsx => WorkerAction1.tsx} | 0 .../mechanics/conveyorMechanics.tsx | 10 +- .../mechanics/humanMechanics.tsx | 4 +- .../mechanics/machineMechanics.tsx | 2 +- .../mechanics/roboticArmMechanics.tsx | 2 +- .../mechanics/storageMechanics.tsx | 2 +- .../mechanics/vehicleMechanics.tsx | 2 +- 18 files changed, 330 insertions(+), 330 deletions(-) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{assemblyAction.tsx => AssemblyAction1.tsx} (98%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{DefaultAction.tsx => DefaultAction1.tsx} (94%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{DelayAction.tsx => DelayAction1.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{DespawnAction.tsx => DespawnAction1.tsx} (93%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{PickAndPlaceAction.tsx => PickAndPlaceAction1.tsx} (95%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{pillarJibAction.tsx => PillarJibAction1.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{ProcessAction.tsx => ProcessAction1.tsx} (92%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{SpawnAction.tsx => SpawnAction1.tsx} (96%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{StorageAction.tsx => StorageAction1.tsx} (97%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{SwapAction.tsx => SwapAction1.tsx} (95%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{TravelAction.tsx => TravelAction1.tsx} (96%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{workerAction.tsx => WorkerAction1.tsx} (100%) diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction1.tsx similarity index 98% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction1.tsx index e371f6f..f141c83 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction1.tsx @@ -1,7 +1,7 @@ import React from "react"; import InputRange from "../../../../../ui/inputs/InputRange"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import SwapAction from "./SwapAction"; +import SwapAction from "./SwapAction1"; interface AssemblyActionProps { processTime: { diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction1.tsx similarity index 94% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction1.tsx index 88f515e..2b0a43c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction1.tsx @@ -1,7 +1,7 @@ -import React from "react"; - -const DefaultAction:React.FC = () => { - return <>; -}; - -export default DefaultAction; +import React from "react"; + +const DefaultAction:React.FC = () => { + return <>; +}; + +export default DefaultAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction1.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction1.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction1.tsx similarity index 93% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction1.tsx index 88cbd2e..13d71af 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction1.tsx @@ -1,10 +1,10 @@ -import React from "react"; - -const DespawnAction: React.FC = () => { - return ( - <> - - ); -}; - -export default DespawnAction; +import React from "react"; + +const DespawnAction: React.FC = () => { + return ( + <> + + ); +}; + +export default DespawnAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction1.tsx similarity index 95% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction1.tsx index 08c3d60..750b1aa 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction1.tsx @@ -1,29 +1,29 @@ -import React from "react"; - -interface PickAndPlaceActionProps { - clearPoints: () => void; -} - -const PickAndPlaceAction: React.FC = ({ - clearPoints, -}) => { - return ( -
-
-
Reset
- -
-
- ); -}; - -export default PickAndPlaceAction; +import React from "react"; + +interface PickAndPlaceActionProps { + clearPoints: () => void; +} + +const PickAndPlaceAction: React.FC = ({ + clearPoints, +}) => { + return ( +
+
+
Reset
+ +
+
+ ); +}; + +export default PickAndPlaceAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PillarJibAction1.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/pillarJibAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/PillarJibAction1.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction1.tsx similarity index 92% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction1.tsx index 331cf1b..5974233 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction1.tsx @@ -1,48 +1,48 @@ -import React from "react"; -import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import SwapAction from "./SwapAction"; - -interface ProcessActionProps { - value: string; - min: number; - max: number; - defaultValue: string; - onChange: (value: string) => void; - swapOptions: string[]; - swapDefaultOption: string; - onSwapSelect: (value: string) => void; -} - -const ProcessAction: React.FC = ({ - value, - min, - max, - defaultValue, - onChange, - swapOptions, - swapDefaultOption, - onSwapSelect, -}) => { - return ( - <> - { }} - onChange={onChange} - /> - - - ); -}; - -export default ProcessAction; +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import SwapAction from "./SwapAction1"; + +interface ProcessActionProps { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + swapOptions: string[]; + swapDefaultOption: string; + onSwapSelect: (value: string) => void; +} + +const ProcessAction: React.FC = ({ + value, + min, + max, + defaultValue, + onChange, + swapOptions, + swapDefaultOption, + onSwapSelect, +}) => { + return ( + <> + { }} + onChange={onChange} + /> + + + ); +}; + +export default ProcessAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction1.tsx similarity index 96% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction1.tsx index 7d8002e..f82be50 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction1.tsx @@ -1,72 +1,72 @@ -import React from "react"; -import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; -import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; - -interface SpawnActionProps { - onChangeInterval: (value: string) => void; - onChangeCount: (value: string) => void; - defaultOption: string; - options: string[]; - onSelect: (option: string) => void; - intervalValue: string; - countValue: string; - intervalMin: number; - intervalMax: number; - intervalDefaultValue: string; - countMin: number; - countMax: number; - countDefaultValue: string; -} - -const SpawnAction: React.FC = ({ - onChangeInterval, - onChangeCount, - defaultOption, - options, - onSelect, - intervalValue, - countValue, - intervalMin, - intervalMax, - intervalDefaultValue, - countMin, - countMax, - countDefaultValue, -}) => { - return ( - <> - { }} - onChange={onChangeInterval} - /> - { }} - onChange={onChangeCount} - /> - {/* */} - - - ); -}; - -export default SpawnAction; +import React from "react"; +import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; + +interface SpawnActionProps { + onChangeInterval: (value: string) => void; + onChangeCount: (value: string) => void; + defaultOption: string; + options: string[]; + onSelect: (option: string) => void; + intervalValue: string; + countValue: string; + intervalMin: number; + intervalMax: number; + intervalDefaultValue: string; + countMin: number; + countMax: number; + countDefaultValue: string; +} + +const SpawnAction: React.FC = ({ + onChangeInterval, + onChangeCount, + defaultOption, + options, + onSelect, + intervalValue, + countValue, + intervalMin, + intervalMax, + intervalDefaultValue, + countMin, + countMax, + countDefaultValue, +}) => { + return ( + <> + { }} + onChange={onChangeInterval} + /> + { }} + onChange={onChangeCount} + /> + {/* */} + + + ); +}; + +export default SpawnAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction1.tsx similarity index 97% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction1.tsx index 5fd62df..4d280d2 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction1.tsx @@ -1,57 +1,57 @@ -import React from "react"; -import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; - -interface StorageActionProps { - type: "store" | "spawn" | "default"; - value: string; - min: number; - max?: number; - defaultValue: string; - currentMaterialType: string; - handleCapacityChange: (value: string) => void; - handleMaterialTypeChange: (value: string) => void; -} - -const StorageAction: React.FC = ({ type, value, min, max, defaultValue, currentMaterialType, handleCapacityChange, handleMaterialTypeChange }) => { - return ( - <> - {type === 'store' && - { }} - onChange={handleCapacityChange} - /> - } - {type === 'spawn' && - <> - { }} - onChange={handleCapacityChange} - /> - - - } - - ); -}; - -export default StorageAction; +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; + +interface StorageActionProps { + type: "store" | "spawn" | "default"; + value: string; + min: number; + max?: number; + defaultValue: string; + currentMaterialType: string; + handleCapacityChange: (value: string) => void; + handleMaterialTypeChange: (value: string) => void; +} + +const StorageAction: React.FC = ({ type, value, min, max, defaultValue, currentMaterialType, handleCapacityChange, handleMaterialTypeChange }) => { + return ( + <> + {type === 'store' && + { }} + onChange={handleCapacityChange} + /> + } + {type === 'spawn' && + <> + { }} + onChange={handleCapacityChange} + /> + + + } + + ); +}; + +export default StorageAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction1.tsx similarity index 95% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction1.tsx index 5eaf991..14a7c18 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction1.tsx @@ -1,25 +1,25 @@ -import React from "react"; -import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; - -interface SwapActionProps { - onSelect: (option: string) => void; - defaultOption: string; - options: string[]; -} - -const SwapAction: React.FC = ({ - onSelect, - defaultOption, - options, -}) => { - return ( - - ); -}; - -export default SwapAction; +import React from "react"; +import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; + +interface SwapActionProps { + onSelect: (option: string) => void; + defaultOption: string; + options: string[]; +} + +const SwapAction: React.FC = ({ + onSelect, + defaultOption, + options, +}) => { + return ( + + ); +}; + +export default SwapAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction1.tsx similarity index 96% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction1.tsx index db11181..5ae5d76 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction1.tsx @@ -1,70 +1,70 @@ -import React from "react"; -import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; - -interface TravelActionProps { - loadCapacity: { - value: string; - min: number; - max: number; - defaultValue: string; - onChange: (value: string) => void; - }; - unloadDuration: { - value: string; - min: number; - max: number; - defaultValue: string; - onChange: (value: string) => void; - }; - clearPoints: () => void; -} - -const TravelAction: React.FC = ({ - loadCapacity, - unloadDuration, - clearPoints, -}) => { - return ( - <> - { }} - onChange={loadCapacity.onChange} - /> - { }} - onChange={unloadDuration.onChange} - /> -
-
-
Reset
- -
-
- - ); -}; - -export default TravelAction; +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; + +interface TravelActionProps { + loadCapacity: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + unloadDuration: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + clearPoints: () => void; +} + +const TravelAction: React.FC = ({ + loadCapacity, + unloadDuration, + clearPoints, +}) => { + return ( + <> + { }} + onChange={loadCapacity.onChange} + /> + { }} + onChange={unloadDuration.onChange} + /> +
+
+
Reset
+ +
+
+ + ); +}; + +export default TravelAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/WorkerAction1.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/WorkerAction1.tsx 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 990d046..b3df5d6 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -1,12 +1,12 @@ import { useEffect, useState } from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import DelayAction from "../actions/DelayAction"; +import DelayAction from "../actions/DelayAction1"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; -import DespawnAction from "../actions/DespawnAction"; -import SwapAction from "../actions/SwapAction"; -import SpawnAction from "../actions/SpawnAction"; -import DefaultAction from "../actions/DefaultAction"; +import DespawnAction from "../actions/DespawnAction1"; +import SwapAction from "../actions/SwapAction1"; +import SpawnAction from "../actions/SpawnAction1"; +import DefaultAction from "../actions/DefaultAction1"; import Trigger from "../trigger/Trigger"; import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; import ActionsList from "../components/ActionsList"; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 4b29f8c..ebd9d90 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -6,8 +6,8 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import ActionsList from "../components/ActionsList"; -import WorkerAction from "../actions/WorkerAction"; -import AssemblyAction from "../actions/AssemblyAction"; +import WorkerAction from "../actions/WorkerAction1"; +import AssemblyAction from "../actions/AssemblyAction1"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; 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 22b3277..fb76932 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -3,7 +3,7 @@ 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 ProcessAction from "../actions/ProcessAction"; +import ProcessAction from "../actions/ProcessAction1"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; 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 0c7c6e3..f72cb36 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -5,7 +5,7 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; -import PickAndPlaceAction from "../actions/PickAndPlaceAction"; +import PickAndPlaceAction from "../actions/PickAndPlaceAction1"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; 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 796dbc5..02a0fc6 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; -import StorageAction from "../actions/StorageAction"; +import StorageAction from "../actions/StorageAction1"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; 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 fada871..4794e9e 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -4,7 +4,7 @@ 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 TravelAction from "../actions/TravelAction"; +import TravelAction from "../actions/TravelAction1"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; From d12ead7d3533f378fce220a1b2eb2731103c8d8d Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 13 Aug 2025 18:23:40 +0530 Subject: [PATCH 30/38] file name capitalized --- .../{AssemblyAction1.tsx => AssemblyAction.tsx} | 2 +- .../actions/{DefaultAction1.tsx => DefaultAction.tsx} | 0 .../actions/{DelayAction1.tsx => DelayAction.tsx} | 0 .../actions/{DespawnAction1.tsx => DespawnAction.tsx} | 0 ...{PickAndPlaceAction1.tsx => PickAndPlaceAction.tsx} | 0 .../{PillarJibAction1.tsx => PillarJibAction.tsx} | 0 .../actions/{ProcessAction1.tsx => ProcessAction.tsx} | 2 +- .../actions/{SpawnAction1.tsx => SpawnAction.tsx} | 0 .../actions/{StorageAction1.tsx => StorageAction.tsx} | 0 .../actions/{SwapAction1.tsx => SwapAction.tsx} | 0 .../actions/{TravelAction1.tsx => TravelAction.tsx} | 0 .../actions/{WorkerAction1.tsx => WorkerAction.tsx} | 0 .../eventProperties/mechanics/conveyorMechanics.tsx | 10 +++++----- .../eventProperties/mechanics/humanMechanics.tsx | 4 ++-- .../eventProperties/mechanics/machineMechanics.tsx | 2 +- .../eventProperties/mechanics/roboticArmMechanics.tsx | 2 +- .../eventProperties/mechanics/storageMechanics.tsx | 2 +- .../eventProperties/mechanics/vehicleMechanics.tsx | 2 +- 18 files changed, 13 insertions(+), 13 deletions(-) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{AssemblyAction1.tsx => AssemblyAction.tsx} (98%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{DefaultAction1.tsx => DefaultAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{DelayAction1.tsx => DelayAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{DespawnAction1.tsx => DespawnAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{PickAndPlaceAction1.tsx => PickAndPlaceAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{PillarJibAction1.tsx => PillarJibAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{ProcessAction1.tsx => ProcessAction.tsx} (96%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{SpawnAction1.tsx => SpawnAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{StorageAction1.tsx => StorageAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{SwapAction1.tsx => SwapAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{TravelAction1.tsx => TravelAction.tsx} (100%) rename app/src/components/layout/sidebarRight/properties/eventProperties/actions/{WorkerAction1.tsx => WorkerAction.tsx} (100%) diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction.tsx similarity index 98% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction.tsx index f141c83..e371f6f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction1.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/AssemblyAction.tsx @@ -1,7 +1,7 @@ import React from "react"; import InputRange from "../../../../../ui/inputs/InputRange"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import SwapAction from "./SwapAction1"; +import SwapAction from "./SwapAction"; interface AssemblyActionProps { processTime: { diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/DefaultAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PillarJibAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PillarJibAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/PillarJibAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/PillarJibAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx similarity index 96% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx index 5974233..6b1825b 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction1.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx @@ -1,6 +1,6 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import SwapAction from "./SwapAction1"; +import SwapAction from "./SwapAction"; interface ProcessActionProps { value: string; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/WorkerAction1.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/WorkerAction.tsx similarity index 100% rename from app/src/components/layout/sidebarRight/properties/eventProperties/actions/WorkerAction1.tsx rename to app/src/components/layout/sidebarRight/properties/eventProperties/actions/WorkerAction.tsx 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 b3df5d6..990d046 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -1,12 +1,12 @@ import { useEffect, useState } from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import DelayAction from "../actions/DelayAction1"; +import DelayAction from "../actions/DelayAction"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; -import DespawnAction from "../actions/DespawnAction1"; -import SwapAction from "../actions/SwapAction1"; -import SpawnAction from "../actions/SpawnAction1"; -import DefaultAction from "../actions/DefaultAction1"; +import DespawnAction from "../actions/DespawnAction"; +import SwapAction from "../actions/SwapAction"; +import SpawnAction from "../actions/SpawnAction"; +import DefaultAction from "../actions/DefaultAction"; import Trigger from "../trigger/Trigger"; import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; import ActionsList from "../components/ActionsList"; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index ebd9d90..4b29f8c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -6,8 +6,8 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import ActionsList from "../components/ActionsList"; -import WorkerAction from "../actions/WorkerAction1"; -import AssemblyAction from "../actions/AssemblyAction1"; +import WorkerAction from "../actions/WorkerAction"; +import AssemblyAction from "../actions/AssemblyAction"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; 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 fb76932..22b3277 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -3,7 +3,7 @@ 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 ProcessAction from "../actions/ProcessAction1"; +import ProcessAction from "../actions/ProcessAction"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; 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 f72cb36..0c7c6e3 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -5,7 +5,7 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; -import PickAndPlaceAction from "../actions/PickAndPlaceAction1"; +import PickAndPlaceAction from "../actions/PickAndPlaceAction"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; 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 02a0fc6..796dbc5 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; -import StorageAction from "../actions/StorageAction1"; +import StorageAction from "../actions/StorageAction"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; 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 4794e9e..fada871 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -4,7 +4,7 @@ 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 TravelAction from "../actions/TravelAction1"; +import TravelAction from "../actions/TravelAction"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; From 75f77c4204f4c2a382ad131bf1f94d60f09154f4 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 14 Aug 2025 13:59:24 +0530 Subject: [PATCH 31/38] added direction movement to asset during move and duplication --- .../selection3D/duplicationControls3D.tsx | 53 ++++++++++++++++++- .../selection3D/moveControls3D.tsx | 41 +++++++++++++- .../utils/shortcutkeys/handleShortcutKeys.ts | 5 +- 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 344ae4e..e5ca7ab 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -35,6 +35,7 @@ const DuplicationControls3D = ({ const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [isDuplicating, setIsDuplicating] = useState(false); @@ -82,6 +83,19 @@ const DuplicationControls3D = ({ const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); + if (isDuplicating && duplicatedObjects.length > 0) { + if (event.key.toLowerCase() === 'x') { + setAxisConstraint(prev => prev === 'x' ? null : 'x'); + event.preventDefault(); + return; + } + if (event.key.toLowerCase() === 'z') { + setAxisConstraint(prev => prev === 'z' ? null : 'z'); + event.preventDefault(); + return; + } + } + if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) { duplicateSelection(); } @@ -127,7 +141,28 @@ const DuplicationControls3D = ({ } if (dragOffset) { - const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + let adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + + if (axisConstraint) { + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + if (model) { + + const currentBasePosition = model.position.clone(); + if (axisConstraint === 'x') { + adjustedHit = new THREE.Vector3( + adjustedHit.x, + currentBasePosition.y, + currentBasePosition.z + ); + } else if (axisConstraint === 'z') { + adjustedHit = new THREE.Vector3( + currentBasePosition.x, + currentBasePosition.y, + adjustedHit.z + ); + } + } + } duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => { if (duplicatedObject.userData.modelUuid) { @@ -154,6 +189,21 @@ const DuplicationControls3D = ({ } }); + useEffect(() => { + if (duplicatedObjects.length > 0) { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + if (model) { + const newOffset = calculateDragOffset(model, intersectionPoint); + setDragOffset(newOffset); + } + } + } + }, [axisConstraint, camera, duplicatedObjects]) + const duplicateSelection = useCallback(() => { if (selectedAssets.length > 0 && duplicatedObjects.length === 0) { const positions: Record = {}; @@ -585,6 +635,7 @@ const DuplicationControls3D = ({ setSelectedAssets([]); setIsDuplicating(false); setDragOffset(null); + setAxisConstraint(null); }; return null; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 4916886..865deea 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -43,6 +43,7 @@ function MoveControls3D({ const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [initialStates, setInitialStates] = useState>({}); @@ -114,6 +115,19 @@ function MoveControls3D({ if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; + if (isMoving && movedObjects.length > 0) { + if (event.key.toLowerCase() === 'x') { + setAxisConstraint(prev => prev === 'x' ? null : 'x'); + event.preventDefault(); + return; + } + if (event.key.toLowerCase() === 'z') { + setAxisConstraint(prev => prev === 'z' ? null : 'z'); + event.preventDefault(); + return; + } + } + if (keyCombination !== keyEvent) { if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { setKeyEvent(keyCombination); @@ -184,9 +198,22 @@ function MoveControls3D({ } } }); - }, 0) + setAxisConstraint(null); + }, 100) }, [movedObjects, initialStates, updateAsset]); + useEffect(() => { + if (movedObjects.length > 0) { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint); + setDragOffset(newOffset); + } + } + }, [axisConstraint, camera, movedObjects]) + useFrame(() => { if (!isMoving || movedObjects.length === 0) return; @@ -204,7 +231,16 @@ function MoveControls3D({ } if (dragOffset) { - const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + let rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + + if (axisConstraint) { + const currentBasePosition = movedObjects[0].position.clone(); + if (axisConstraint === 'x') { + rawBasePosition = new THREE.Vector3(rawBasePosition.x, currentBasePosition.y, currentBasePosition.z); + } else if (axisConstraint === 'z') { + rawBasePosition = new THREE.Vector3(currentBasePosition.x, currentBasePosition.y, rawBasePosition.z); + } + } let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1; @@ -422,6 +458,7 @@ function MoveControls3D({ echo.success("Object moved!"); setIsMoving(false); clearSelection(); + setAxisConstraint(null); }; const clearSelection = () => { diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 7abbe2f..2a0d437 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -11,6 +11,7 @@ import useVersionHistoryVisibleStore, { useDfxUpload, useRenameModeStore, useSaveVersion, + useSelectedAssets, useSelectedComment, useSelectedFloorItem, useSelectedWallItem, @@ -40,6 +41,7 @@ const KeyPressListener: React.FC = () => { const { toggleView, setToggleView } = useToggleView(); const { setAddAction } = useAddAction(); const { setSelectedWallItem } = useSelectedWallItem(); + const { selectedAssets } = useSelectedAssets(); const { setActiveTool } = useActiveTool(); const { clearSelectedZone } = useSelectedZoneStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore(); @@ -82,7 +84,7 @@ const KeyPressListener: React.FC = () => { H: "free-hand", }; const tool = toolMap[key]; - if (tool) { + if (tool && selectedAssets.length === 0) { setActiveTool(tool); setActiveSubTool(tool); } @@ -278,6 +280,7 @@ const KeyPressListener: React.FC = () => { hidePlayer, selectedFloorItem, isRenameMode, + selectedAssets ]); return null; From 594445ac2017127a2d1922b514a707bf9627cbae Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 14 Aug 2025 14:43:05 +0530 Subject: [PATCH 32/38] move controls snapping and slow movement added --- .../selection3D/moveControls3D.tsx | 46 +++++----- app/src/utils/handleSnap.ts | 87 +++++++++++++++---- 2 files changed, 90 insertions(+), 43 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 865deea..d578bc1 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -5,7 +5,7 @@ import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../. 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 { getSnappedBasePosition } from "../../../../../utils/handleSnap"; import DistanceFindingControls from "./distanceFindingControls"; import { useParams } from "react-router-dom"; import { useProductContext } from "../../../../simulation/products/productContext"; @@ -49,6 +49,10 @@ function MoveControls3D({ const [initialStates, setInitialStates] = useState>({}); const [isMoving, setIsMoving] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); + // Add a ref to track fine-move base + const fineMoveBaseRef = useRef(null); + const lastPointerPositionRef = useRef(null); + const wasShiftHeldRef = useRef(false); const updateBackend = ( productName: string, @@ -78,10 +82,21 @@ function MoveControls3D({ }; const onKeyUp = (event: KeyboardEvent) => { - const isModifierKey = (!event.shiftKey && !event.ctrlKey); + const keyCombination = detectModifierKeys(event); - if (isModifierKey && keyEvent !== "") { + if (keyCombination === "") { setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + if (movedObjects[0]) { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint); + setDragOffset(newOffset); + } } }; @@ -231,30 +246,9 @@ function MoveControls3D({ } if (dragOffset) { - let rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - if (axisConstraint) { - const currentBasePosition = movedObjects[0].position.clone(); - if (axisConstraint === 'x') { - rawBasePosition = new THREE.Vector3(rawBasePosition.x, currentBasePosition.y, currentBasePosition.z); - } else if (axisConstraint === 'z') { - rawBasePosition = new THREE.Vector3(currentBasePosition.x, currentBasePosition.y, rawBasePosition.z); - } - } - - let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1; - - const initialBasePosition = initialPositions[movedObjects[0].uuid]; - const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition); - - let adjustedDifference = positionDifference.multiplyScalar(moveDistance); - - const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference); - - if (keyEvent.includes("Ctrl")) { - baseNewPosition.x = snapControls(baseNewPosition.x, keyEvent); - baseNewPosition.z = snapControls(baseNewPosition.z, keyEvent); - } + const baseNewPosition = getSnappedBasePosition({ rawBasePosition, intersectionPoint, movedObjects, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); movedObjects.forEach((movedAsset: THREE.Object3D) => { if (movedAsset.userData.modelUuid) { diff --git a/app/src/utils/handleSnap.ts b/app/src/utils/handleSnap.ts index bd6a74d..d96bf14 100644 --- a/app/src/utils/handleSnap.ts +++ b/app/src/utils/handleSnap.ts @@ -1,22 +1,75 @@ -export function snapControls(value: number, event: string): number { - const CTRL_DISTANCE = 1; // Snap to whole numbers when Ctrl is pressed - const SHIFT_DISTANCE = 0.01; // Snap to half-step increments when Shift is pressed - const CTRL_SHIFT_DISTANCE = 0.1; // Snap to fine increments when both Ctrl and Shift are pressed +import * as THREE from "three"; - switch (event) { - case "Ctrl": - return Math.round(value / CTRL_DISTANCE) * CTRL_DISTANCE; +export function getSnappedBasePosition({ + rawBasePosition, + intersectionPoint, + movedObjects, + axisConstraint, + keyEvent, + fineMoveBaseRef, + lastPointerPositionRef, + wasShiftHeldRef +}: { + rawBasePosition: THREE.Vector3; + intersectionPoint: THREE.Vector3; + movedObjects: THREE.Object3D[]; + axisConstraint: "x" | "z" | null; + keyEvent: string; + fineMoveBaseRef: React.MutableRefObject; + lastPointerPositionRef: React.MutableRefObject; + wasShiftHeldRef: React.MutableRefObject; +}): THREE.Vector3 { + const CTRL_DISTANCE = 0.5; + const SHIFT_DISTANCE = 0.05; + const CTRL_SHIFT_DISTANCE = 0.05; - case "Shift": - return Math.round(value / SHIFT_DISTANCE) * SHIFT_DISTANCE; + const isShiftHeld = keyEvent.includes("Shift"); - case "Ctrl+Shift": - const base = Math.floor(value / CTRL_DISTANCE) * CTRL_DISTANCE; - const offset = - Math.round((value - base) / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE; - return base + offset; + // Handle Shift toggle state + if (isShiftHeld !== wasShiftHeldRef.current) { + if (isShiftHeld) { + fineMoveBaseRef.current = movedObjects[0].position.clone(); + lastPointerPositionRef.current = intersectionPoint.clone(); + } else { + fineMoveBaseRef.current = null; + } + wasShiftHeldRef.current = isShiftHeld; + } - default: - return value; // No snapping if no modifier key is pressed - } + // Start from raw + let baseNewPosition = rawBasePosition.clone(); + + // Apply snapping / fine move + if (keyEvent === "Ctrl") { + baseNewPosition.set( + Math.round(baseNewPosition.x / CTRL_DISTANCE) * CTRL_DISTANCE, + baseNewPosition.y, + Math.round(baseNewPosition.z / CTRL_DISTANCE) * CTRL_DISTANCE + ); + } else if (keyEvent === "Ctrl+Shift") { + if (isShiftHeld && fineMoveBaseRef.current && lastPointerPositionRef.current) { + const pointerDelta = new THREE.Vector3().subVectors(intersectionPoint, lastPointerPositionRef.current); + baseNewPosition = fineMoveBaseRef.current.clone().add(pointerDelta.multiplyScalar(SHIFT_DISTANCE)); + } + baseNewPosition.set( + Math.round(baseNewPosition.x / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE, + baseNewPosition.y, + Math.round(baseNewPosition.z / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE + ); + } else if (isShiftHeld && fineMoveBaseRef.current && lastPointerPositionRef.current) { + const pointerDelta = new THREE.Vector3().subVectors(intersectionPoint, lastPointerPositionRef.current); + baseNewPosition = fineMoveBaseRef.current.clone().add(pointerDelta.multiplyScalar(SHIFT_DISTANCE)); + } + + // Apply axis constraint last + if (axisConstraint) { + const currentBasePosition = movedObjects[0].position.clone(); + if (axisConstraint === 'x') { + baseNewPosition.set(baseNewPosition.x, currentBasePosition.y, currentBasePosition.z); + } else if (axisConstraint === 'z') { + baseNewPosition.set(currentBasePosition.x, currentBasePosition.y, baseNewPosition.z); + } + } + + return baseNewPosition; } From ab3eb84277f0a662ffbd98a52ba8f3048bf81f30 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 14 Aug 2025 15:42:51 +0530 Subject: [PATCH 33/38] duplication asset snapping movement and slow movement added --- .../selection3D/duplicationControls3D.tsx | 67 ++++++++++++------- .../functions/handleAssetPositionSnap.ts} | 16 ++--- .../selection3D/moveControls3D.tsx | 10 ++- 3 files changed, 55 insertions(+), 38 deletions(-) rename app/src/{utils/handleSnap.ts => modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts} (89%) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index e5ca7ab..fcac600 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -9,6 +9,7 @@ import { useParams } from "react-router-dom"; import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap"; // import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; @@ -35,11 +36,15 @@ const DuplicationControls3D = ({ const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [isDuplicating, setIsDuplicating] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); + const fineMoveBaseRef = useRef(null); + const lastPointerPositionRef = useRef(null); + const wasShiftHeldRef = useRef(false); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); @@ -78,6 +83,7 @@ const DuplicationControls3D = ({ removeAsset(obj.userData.modelUuid); }); } + setKeyEvent(""); }; const onKeyDown = (event: KeyboardEvent) => { @@ -96,6 +102,14 @@ const DuplicationControls3D = ({ } } + if (keyCombination !== keyEvent) { + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } else { + setKeyEvent(""); + } + } + if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) { duplicateSelection(); } @@ -108,11 +122,34 @@ const DuplicationControls3D = ({ } }; + const onKeyUp = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === "") { + setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + if (duplicatedObjects[0] && keyEvent !== "" && keyCombination === '') { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + if (model) { + const newOffset = calculateDragOffset(model, intersectionPoint); + setDragOffset(newOffset); + } + } + } + }; + if (!toggleView) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); + canvasElement.addEventListener("keyup", onKeyUp); } return () => { @@ -120,8 +157,9 @@ const DuplicationControls3D = ({ canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); + canvasElement.addEventListener("keyup", onKeyUp); }; - }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]); + }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects, keyEvent]); useFrame(() => { if (!isDuplicating || duplicatedObjects.length === 0) return; @@ -141,28 +179,9 @@ const DuplicationControls3D = ({ } if (dragOffset) { - let adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - - if (axisConstraint) { - const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); - if (model) { - - const currentBasePosition = model.position.clone(); - if (axisConstraint === 'x') { - adjustedHit = new THREE.Vector3( - adjustedHit.x, - currentBasePosition.y, - currentBasePosition.z - ); - } else if (axisConstraint === 'z') { - adjustedHit = new THREE.Vector3( - currentBasePosition.x, - currentBasePosition.y, - adjustedHit.z - ); - } - } - } + const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => { if (duplicatedObject.userData.modelUuid) { @@ -176,7 +195,7 @@ const DuplicationControls3D = ({ ); const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid); - const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset); + const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; if (model) { diff --git a/app/src/utils/handleSnap.ts b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts similarity index 89% rename from app/src/utils/handleSnap.ts rename to app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts index d96bf14..00b6924 100644 --- a/app/src/utils/handleSnap.ts +++ b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts @@ -1,9 +1,9 @@ import * as THREE from "three"; -export function getSnappedBasePosition({ +export function handleAssetPositionSnap({ rawBasePosition, intersectionPoint, - movedObjects, + model, axisConstraint, keyEvent, fineMoveBaseRef, @@ -12,7 +12,7 @@ export function getSnappedBasePosition({ }: { rawBasePosition: THREE.Vector3; intersectionPoint: THREE.Vector3; - movedObjects: THREE.Object3D[]; + model: THREE.Object3D | undefined; axisConstraint: "x" | "z" | null; keyEvent: string; fineMoveBaseRef: React.MutableRefObject; @@ -26,9 +26,9 @@ export function getSnappedBasePosition({ const isShiftHeld = keyEvent.includes("Shift"); // Handle Shift toggle state - if (isShiftHeld !== wasShiftHeldRef.current) { + if (isShiftHeld !== wasShiftHeldRef.current && model) { if (isShiftHeld) { - fineMoveBaseRef.current = movedObjects[0].position.clone(); + fineMoveBaseRef.current = model.position.clone(); lastPointerPositionRef.current = intersectionPoint.clone(); } else { fineMoveBaseRef.current = null; @@ -62,8 +62,8 @@ export function getSnappedBasePosition({ } // Apply axis constraint last - if (axisConstraint) { - const currentBasePosition = movedObjects[0].position.clone(); + if (axisConstraint && model) { + const currentBasePosition = model.position.clone(); if (axisConstraint === 'x') { baseNewPosition.set(baseNewPosition.x, currentBasePosition.y, currentBasePosition.z); } else if (axisConstraint === 'z') { @@ -72,4 +72,4 @@ export function getSnappedBasePosition({ } return baseNewPosition; -} +} \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index d578bc1..8fc9c63 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -5,7 +5,7 @@ import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../. import * as Types from "../../../../../types/world/worldTypes"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; -import { getSnappedBasePosition } from "../../../../../utils/handleSnap"; +import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap"; import DistanceFindingControls from "./distanceFindingControls"; import { useParams } from "react-router-dom"; import { useProductContext } from "../../../../simulation/products/productContext"; @@ -34,7 +34,6 @@ function MoveControls3D({ const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { socket } = useSocketStore(); - const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const { userId, organization } = getUserData(); const { projectId } = useParams(); const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); @@ -43,13 +42,13 @@ function MoveControls3D({ const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [initialStates, setInitialStates] = useState>({}); const [isMoving, setIsMoving] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); - // Add a ref to track fine-move base const fineMoveBaseRef = useRef(null); const lastPointerPositionRef = useRef(null); const wasShiftHeldRef = useRef(false); @@ -121,7 +120,6 @@ function MoveControls3D({ clearSelection(); setMovedObjects([]); } - setKeyEvent(""); }; const onKeyDown = (event: KeyboardEvent) => { @@ -247,8 +245,8 @@ function MoveControls3D({ if (dragOffset) { const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - - const baseNewPosition = getSnappedBasePosition({ rawBasePosition, intersectionPoint, movedObjects, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); + const model = movedObjects[0]; + const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); movedObjects.forEach((movedAsset: THREE.Object3D) => { if (movedAsset.userData.modelUuid) { From 922085ec6c31e61761ccda099f4b36b973b5ce25 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 14 Aug 2025 15:55:31 +0530 Subject: [PATCH 34/38] storage to human bug fix --- .../actionHandler/useRetrieveHandler.ts | 21 +++++++++++++------ .../eventManager/useHumanEventManager.ts | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index f48312a..18e8bfd 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -6,7 +6,7 @@ import { useProductContext } from "../../../products/productContext"; import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager"; export function useRetrieveHandler() { - const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore } = useSceneContext(); + const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore, humanEventManagerRef } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { addMaterial } = materialStore(); const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); @@ -464,11 +464,20 @@ export function useRetrieveHandler() { if (action && action.actionType === 'pickAndDrop' && !hasLock && !crane.isCarrying && !crane.isActive && crane.currentLoad < (action?.maxPickUpCount || 0)) { const material = getLastMaterial(storageUnit.modelUuid); if (material) { - incrementCraneLoad(crane.modelUuid, 1); - addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId); - addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId); - - cranePickupLockRef.current.set(crane.modelUuid, true); + if (action.triggers[0].triggeredAsset?.triggeredModel.modelUuid && action.triggers[0].triggeredAsset.triggeredAction?.actionUuid) { + const human = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid); + if (human && human.type === 'human') { + if (!monitoredHumansRef.current.has(human.modelUuid)) { + addHumanToMonitor(human.modelUuid, () => { + incrementCraneLoad(crane.modelUuid, 1); + addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId); + addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId); + cranePickupLockRef.current.set(crane.modelUuid, true); + }, action.triggers[0].triggeredAsset.triggeredAction?.actionUuid) + } + monitoredHumansRef.current.add(human.modelUuid); + } + } } } else if (crane.isCarrying && crane.currentPhase === 'pickup-drop' && hasLock) { cranePickupLockRef.current.set(crane.modelUuid, false); diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index 1dec0d9..8a5b634 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -23,6 +23,7 @@ export function useHumanEventManager() { }, [isReset, isPlaying]); const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { + console.log('humanId: ', humanId); const human = getHumanById(humanId); const action = getActionByUuid(selectedProduct.productUuid, actionUuid); if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker' && action.actionType !== 'operator') || !humanEventManagerRef.current) return; From 24cc15c2b91f5ac9d03c3cca68ac731018da734e Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 14 Aug 2025 17:07:43 +0530 Subject: [PATCH 35/38] bug fix --- app/src/modules/scene/setup/setup.tsx | 4 ++-- app/src/utils/shortcutkeys/handleShortcutKeys.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/modules/scene/setup/setup.tsx b/app/src/modules/scene/setup/setup.tsx index 71dcce7..1887015 100644 --- a/app/src/modules/scene/setup/setup.tsx +++ b/app/src/modules/scene/setup/setup.tsx @@ -5,7 +5,7 @@ import Controls from '../controls/controls'; import { AdaptiveDpr, AdaptiveEvents, Environment } from '@react-three/drei' import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr"; -// import { Perf } from 'r3f-perf'; +import { Perf } from 'r3f-perf'; function Setup() { return ( @@ -18,7 +18,7 @@ function Setup() { - {/* */} + {/* */} diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index b38292f..507e31b 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -41,7 +41,6 @@ const KeyPressListener: React.FC = () => { const { toggleView, setToggleView } = useToggleView(); const { setAddAction } = useAddAction(); const { setSelectedWallItem } = useSelectedWallItem(); - const { selectedAssets } = useSelectedAssets(); const { setActiveTool } = useActiveTool(); const { clearSelectedZone } = useSelectedZoneStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore(); From 592452068ebe80b8f9be5360806612aeb4f0b2fe Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 14 Aug 2025 17:23:05 +0530 Subject: [PATCH 36/38] refactor: comment out unused performance stats component and add styles for stats positioning --- app/src/modules/scene/setup/setup.tsx | 4 ++-- app/src/styles/scene/scene.scss | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/modules/scene/setup/setup.tsx b/app/src/modules/scene/setup/setup.tsx index 1887015..71dcce7 100644 --- a/app/src/modules/scene/setup/setup.tsx +++ b/app/src/modules/scene/setup/setup.tsx @@ -5,7 +5,7 @@ import Controls from '../controls/controls'; import { AdaptiveDpr, AdaptiveEvents, Environment } from '@react-three/drei' import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr"; -import { Perf } from 'r3f-perf'; +// import { Perf } from 'r3f-perf'; function Setup() { return ( @@ -18,7 +18,7 @@ function Setup() { - + {/* */} {/* */} diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 4db9e8b..6358c81 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -134,3 +134,11 @@ align-items: center; } } + +.stats{ + top: auto !important; + bottom: 36px !important; + left: 12px !important; + border-radius: 6px; + overflow: hidden; +} From a8c6f8d80acd9eecbd0476f4b188df47ea77672b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 14 Aug 2025 18:13:28 +0530 Subject: [PATCH 37/38] rotational snap added --- .../functions/handleAssetRotationSnap.ts | 67 ++++++++++++++++ .../selection3D/rotateControls3D.tsx | 76 ++++++++++++------- .../eventManager/useHumanEventManager.ts | 1 - 3 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts new file mode 100644 index 0000000..89fb36b --- /dev/null +++ b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts @@ -0,0 +1,67 @@ +import * as THREE from "three"; + +function normalizeDegrees(deg: number): number { + // Wrap into [-180, 180] + deg = (deg + 180) % 360; + if (deg < 0) deg += 360; + return deg - 180; +} + +export function handleAssetRotationSnap({ + currentRotation, + rotationDelta, + keyEvent, + snapBaseRef, + prevRotationRef, + wasShiftHeldRef +}: { + currentRotation: THREE.Euler; + rotationDelta: number; + keyEvent: string; + snapBaseRef: React.MutableRefObject; + prevRotationRef: React.MutableRefObject; + wasShiftHeldRef: React.MutableRefObject; +}): number { + const SHIFT_SPEED = 0.5; // Fine rotation speed + const NORMAL_SPEED = 5; // Normal rotation speed + const CTRL_SNAP_DEG = 15; // 15 degrees + const CTRL_SHIFT_SNAP_DEG = 5; // 5 degrees + + const isShiftHeld = keyEvent.includes("Shift"); + const isCtrlHeld = keyEvent.includes("Ctrl"); + + const speedFactor = isShiftHeld ? SHIFT_SPEED : NORMAL_SPEED; + let newRotationDeltaDeg = THREE.MathUtils.radToDeg(rotationDelta) * speedFactor; + + const modifierChanged = isShiftHeld !== wasShiftHeldRef.current; + if (modifierChanged) { + wasShiftHeldRef.current = isShiftHeld; + } + + if (isCtrlHeld) { + const snapDeg = isShiftHeld ? CTRL_SHIFT_SNAP_DEG : CTRL_SNAP_DEG; + + // Store base rotation in degrees when Ctrl first pressed + if (snapBaseRef.current === null) { + snapBaseRef.current = normalizeDegrees(THREE.MathUtils.radToDeg(currentRotation.y)); + } else { + snapBaseRef.current = normalizeDegrees(snapBaseRef.current + newRotationDeltaDeg); + } + + // Snap to nearest increment + const snappedDeg = Math.round(snapBaseRef.current / snapDeg) * snapDeg; + + // Normalize so it never goes beyond [-180, 180] + const normalizedSnappedDeg = normalizeDegrees(snappedDeg); + + // Convert back to radians for returning delta + newRotationDeltaDeg = normalizedSnappedDeg - THREE.MathUtils.radToDeg(currentRotation.y); + + } else { + snapBaseRef.current = null; + } + + prevRotationRef.current = currentRotation.y; + + return THREE.MathUtils.degToRad(newRotationDeltaDeg); +} diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index bd5e254..f82db88 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -9,6 +9,8 @@ import { useProductContext } from "../../../../simulation/products/productContex import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; +import { handleAssetRotationSnap } from "./functions/handleAssetRotationSnap"; // import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; @@ -23,7 +25,6 @@ function RotateControls3D({ setDuplicatedObjects }: any) { const { camera, gl, scene, pointer, raycaster } = useThree(); - const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); @@ -38,15 +39,16 @@ function RotateControls3D({ const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const [initialRotations, setInitialRotations] = useState>({}); const [initialPositions, setInitialPositions] = useState>({}); const [isRotating, setIsRotating] = useState(false); const prevPointerPosition = useRef(null); const rotationCenter = useRef(null); - const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ - left: false, - right: false, - }); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); + const snapBaseRef = useRef(null); + const prevRotationRef = useRef(null); + const wasShiftHeldRef = useRef(false); const updateBackend = useCallback(( productName: string, @@ -98,6 +100,8 @@ function RotateControls3D({ }; const onKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return; if (event.key.toLowerCase() === "r") { @@ -105,6 +109,15 @@ function RotateControls3D({ rotateAssets(); } } + + if (keyCombination !== keyEvent) { + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } else { + setKeyEvent(""); + } + } + if (event.key.toLowerCase() === "escape") { event.preventDefault(); resetToInitialRotations(); @@ -113,11 +126,24 @@ function RotateControls3D({ } }; + const onKeyUp = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === "") { + setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + const currentPointer = new THREE.Vector2(pointer.x, pointer.y); + prevPointerPosition.current = currentPointer.clone(); + }; + if (!toggleView) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); + canvasElement?.addEventListener("keyup", onKeyUp); } return () => { @@ -125,8 +151,9 @@ function RotateControls3D({ canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); + canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]); + }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]); const resetToInitialRotations = useCallback(() => { rotatedObjects.forEach((obj: THREE.Object3D) => { @@ -154,26 +181,27 @@ function RotateControls3D({ useFrame(() => { if (!isRotating || rotatedObjects.length === 0) return; - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + const currentPointer = new THREE.Vector2(pointer.x, pointer.y); if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { - if (point) { - prevPointerPosition.current = new THREE.Vector2(point.x, point.z); - } + prevPointerPosition.current = currentPointer.clone(); return; } - if (point && prevPointerPosition.current && rotationCenter.current) { + if (prevPointerPosition.current && rotationCenter.current) { const center = rotationCenter.current; + const deltaX = currentPointer.x - prevPointerPosition.current.x; - const currentAngle = Math.atan2(point.z - center.z, point.x - center.x); - const prevAngle = Math.atan2( - prevPointerPosition.current.y - center.z, - prevPointerPosition.current.x - center.x - ); - const angleDelta = prevAngle - currentAngle; + const rawAngleDelta = deltaX; + + const angleDelta = handleAssetRotationSnap({ + currentRotation: rotatedObjects[0].rotation, + rotationDelta: rawAngleDelta, + keyEvent, + snapBaseRef, + prevRotationRef, + wasShiftHeldRef + }); const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); @@ -186,7 +214,7 @@ function RotateControls3D({ } }); - prevPointerPosition.current = new THREE.Vector2(point.x, point.z); + prevPointerPosition.current = currentPointer.clone(); } }); @@ -208,17 +236,13 @@ function RotateControls3D({ setInitialRotations(rotations); setInitialPositions(positions); - const intersectionPoint = new THREE.Vector3(); raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - prevPointerPosition.current = new THREE.Vector2(point.x, point.z); - } + prevPointerPosition.current = new THREE.Vector2(pointer.x, pointer.y); setRotatedObjects(selectedAssets); setIsRotating(true); - }, [selectedAssets, camera, pointer, raycaster, plane]); + }, [selectedAssets, camera, pointer, raycaster]); const placeRotatedAssets = useCallback(() => { if (rotatedObjects.length === 0) return; diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index 8a5b634..1dec0d9 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -23,7 +23,6 @@ export function useHumanEventManager() { }, [isReset, isPlaying]); const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { - console.log('humanId: ', humanId); const human = getHumanById(humanId); const action = getActionByUuid(selectedProduct.productUuid, actionUuid); if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker' && action.actionType !== 'operator') || !humanEventManagerRef.current) return; From 64aadbb53da796723752dd22ad27f81be0482956 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 20 Aug 2025 10:39:29 +0530 Subject: [PATCH 38/38] rotationControls snapping bug fixed --- .../functions/handleAssetRotationSnap.ts | 59 ++++++++++-------- .../selection3D/rotateControls3D.tsx | 62 ++++++++++--------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts index 89fb36b..ca16720 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts +++ b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts @@ -1,22 +1,15 @@ import * as THREE from "three"; -function normalizeDegrees(deg: number): number { - // Wrap into [-180, 180] - deg = (deg + 180) % 360; - if (deg < 0) deg += 360; - return deg - 180; -} - export function handleAssetRotationSnap({ - currentRotation, - rotationDelta, + object, + pointerDeltaX, keyEvent, snapBaseRef, prevRotationRef, wasShiftHeldRef }: { - currentRotation: THREE.Euler; - rotationDelta: number; + object: THREE.Object3D; + pointerDeltaX: number; keyEvent: string; snapBaseRef: React.MutableRefObject; prevRotationRef: React.MutableRefObject; @@ -31,37 +24,51 @@ export function handleAssetRotationSnap({ const isCtrlHeld = keyEvent.includes("Ctrl"); const speedFactor = isShiftHeld ? SHIFT_SPEED : NORMAL_SPEED; - let newRotationDeltaDeg = THREE.MathUtils.radToDeg(rotationDelta) * speedFactor; + let deltaAngle = pointerDeltaX * speedFactor; + // Track if modifier changed const modifierChanged = isShiftHeld !== wasShiftHeldRef.current; if (modifierChanged) { wasShiftHeldRef.current = isShiftHeld; + if (isCtrlHeld) snapBaseRef.current = null; } if (isCtrlHeld) { const snapDeg = isShiftHeld ? CTRL_SHIFT_SNAP_DEG : CTRL_SNAP_DEG; + const snapRad = THREE.MathUtils.degToRad(snapDeg); - // Store base rotation in degrees when Ctrl first pressed + // Get current Y rotation from object's quaternion + const euler = new THREE.Euler().setFromQuaternion(object.quaternion, 'YXZ'); + let currentAngle = euler.y; + + // Initialize snap base on first frame if (snapBaseRef.current === null) { - snapBaseRef.current = normalizeDegrees(THREE.MathUtils.radToDeg(currentRotation.y)); - } else { - snapBaseRef.current = normalizeDegrees(snapBaseRef.current + newRotationDeltaDeg); + snapBaseRef.current = currentAngle; + prevRotationRef.current = currentAngle; } + // Accumulate the total rotation from the base + const totalRotation = snapBaseRef.current + deltaAngle; + // Snap to nearest increment - const snappedDeg = Math.round(snapBaseRef.current / snapDeg) * snapDeg; + const snappedAngle = Math.round(totalRotation / snapRad) * snapRad; - // Normalize so it never goes beyond [-180, 180] - const normalizedSnappedDeg = normalizeDegrees(snappedDeg); + // Calculate the delta needed to reach the snapped angle from current rotation + let targetDelta = snappedAngle - currentAngle; - // Convert back to radians for returning delta - newRotationDeltaDeg = normalizedSnappedDeg - THREE.MathUtils.radToDeg(currentRotation.y); + // Handle wrapping around 360 degrees + if (Math.abs(targetDelta) > Math.PI) { + targetDelta = targetDelta - Math.sign(targetDelta) * 2 * Math.PI; + } + // Update snap base for next frame + snapBaseRef.current = totalRotation; + + return targetDelta; } else { + // Reset snapping when Ctrl is not held snapBaseRef.current = null; + prevRotationRef.current = null; + return deltaAngle; } - - prevRotationRef.current = currentRotation.y; - - return THREE.MathUtils.degToRad(newRotationDeltaDeg); -} +} \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index f82db88..255f8b0 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -156,26 +156,28 @@ function RotateControls3D({ }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]); const resetToInitialRotations = useCallback(() => { - rotatedObjects.forEach((obj: THREE.Object3D) => { - const uuid = obj.uuid; - if (obj.userData.modelUuid) { - const initialRotation = initialRotations[uuid]; - const initialPosition = initialPositions[uuid]; + setTimeout(() => { + rotatedObjects.forEach((obj: THREE.Object3D) => { + const uuid = obj.uuid; + if (obj.userData.modelUuid) { + const initialRotation = initialRotations[uuid]; + const initialPosition = initialPositions[uuid]; - if (initialRotation && initialPosition) { - const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,]; - const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,]; + if (initialRotation && initialPosition) { + const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,]; + const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,]; - updateAsset(obj.userData.modelUuid, { - rotation: rotationArray, - position: positionArray, - }); + updateAsset(obj.userData.modelUuid, { + rotation: rotationArray, + position: positionArray, + }); - obj.rotation.copy(initialRotation); - obj.position.copy(initialPosition); + obj.rotation.copy(initialRotation); + obj.position.copy(initialPosition); + } } - } - }); + }); + }, 100) }, [rotatedObjects, initialRotations, initialPositions, updateAsset]); useFrame(() => { @@ -192,25 +194,25 @@ function RotateControls3D({ const center = rotationCenter.current; const deltaX = currentPointer.x - prevPointerPosition.current.x; - const rawAngleDelta = deltaX; - - const angleDelta = handleAssetRotationSnap({ - currentRotation: rotatedObjects[0].rotation, - rotationDelta: rawAngleDelta, - keyEvent, - snapBaseRef, - prevRotationRef, - wasShiftHeldRef - }); - - const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); - rotatedObjects.forEach((obj: THREE.Object3D) => { if (obj.userData.modelUuid) { + const angleDelta = handleAssetRotationSnap({ + object: obj, + pointerDeltaX: deltaX, + keyEvent, + snapBaseRef, + prevRotationRef, + wasShiftHeldRef + }); + const relativePosition = new THREE.Vector3().subVectors(obj.position, center); + const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); relativePosition.applyMatrix4(rotationMatrix); obj.position.copy(center).add(relativePosition); - obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta); + + const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta); + obj.quaternion.multiply(rotationQuat); + obj.rotation.setFromQuaternion(obj.quaternion); } });