From 0f10a8421583760c66168a21494efd01e20d95c0 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Fri, 5 Sep 2025 18:09:43 +0530 Subject: [PATCH] Refactor version management: Replace useSaveVersion with useIsComparing across components - Updated MainScene, SideBarLeft, SideBarRight, Simulations, and CompareLayOut to utilize isComparing state instead of isVersionSaved. - Adjusted conditional rendering and logic to reflect the new comparison state. - Introduced SyncCam component to synchronize camera state during comparisons. - Created useSceneStore to manage camera state with position and target vectors. - Cleaned up imports and ensured consistent formatting across affected files. --- app/src/app.tsx | 35 ++-- .../layout/scenes/ComparisonScene.tsx | 35 +--- .../components/layout/scenes/MainScene.tsx | 14 +- .../layout/sidebarLeft/SideBarLeft.tsx | 8 +- .../layout/sidebarRight/SideBarRight.tsx | 12 +- .../sidebarRight/simulation/Simulations.tsx | 6 +- .../ui/compareVersion/CompareLayOut.tsx | 52 +++++- .../builder/asset/models/model/model.tsx | 155 +++++++++--------- .../modules/builder/asset/models/models.tsx | 18 +- app/src/modules/scene/camera/syncCam.tsx | 32 ++++ app/src/modules/scene/controls/controls.tsx | 38 ++--- app/src/modules/scene/environment/shadow.tsx | 34 +--- app/src/store/builder/store.ts | 107 +++++------- app/src/store/scene/useSceneStore.ts | 27 +++ .../utils/shortcutkeys/handleShortcutKeys.ts | 6 +- 15 files changed, 288 insertions(+), 291 deletions(-) create mode 100644 app/src/modules/scene/camera/syncCam.tsx create mode 100644 app/src/store/scene/useSceneStore.ts diff --git a/app/src/app.tsx b/app/src/app.tsx index bbd5984..2b2a25d 100644 --- a/app/src/app.tsx +++ b/app/src/app.tsx @@ -10,25 +10,24 @@ import ForgotPassword from "./pages/ForgotPassword"; import PageNotFound from "./pages/PageNotFound"; const App: React.FC = () => { + useEffect(() => { + Cache.clear(); + Cache.enabled = true; + }, []); - useEffect(() => { - Cache.clear(); - Cache.enabled = true; - }, []); - - return ( - - - - } /> - } /> - } /> - } /> - } /> - - - - ); + return ( + + + + } /> + } /> + } /> + } /> + } /> + + + + ); }; export default App; diff --git a/app/src/components/layout/scenes/ComparisonScene.tsx b/app/src/components/layout/scenes/ComparisonScene.tsx index b09e53e..18fe34f 100644 --- a/app/src/components/layout/scenes/ComparisonScene.tsx +++ b/app/src/components/layout/scenes/ComparisonScene.tsx @@ -1,5 +1,5 @@ import { useParams } from "react-router-dom"; -import { useCompareProductDataStore, useLoadingProgress, useSaveVersion } from "../../../store/builder/store"; +import { useCompareProductDataStore, useLoadingProgress, useIsComparing } from "../../../store/builder/store"; import { useComparisonProduct, useMainProduct } from "../../../store/simulation/useSimulationStore"; import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; import { useEffect, useState } from "react"; @@ -9,21 +9,18 @@ import CompareLayOut from "../../ui/compareVersion/CompareLayOut"; import ComparisonResult from "../../ui/compareVersion/ComparisonResult"; import RegularDropDown from "../../ui/inputs/RegularDropDown"; -import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi"; - function ComparisonScene() { const { isPlaying } = usePlayButtonStore(); const { productStore, versionStore } = useSceneContext(); - const { versionHistory, selectedVersion, setSelectedVersion, setVersions } = versionStore(); + const { versionHistory, selectedVersion, setSelectedVersion } = versionStore(); const { products, selectedProduct } = productStore(); - const { isVersionSaved } = useSaveVersion(); + const { isComparing } = useIsComparing(); const { activeModule } = useModuleStore(); const { comparisonProduct, setComparisonProduct } = useComparisonProduct(); const { mainProduct } = useMainProduct(); const { loadingProgress } = useLoadingProgress(); const { compareProductsData } = useCompareProductDataStore(); const [shouldShowComparisonResult, setShouldShowComparisonResult] = useState(false); - const { projectId } = useParams(); const handleSelectVersion = (option: string) => { const version = versionHistory.find((version) => version.versionName === option); @@ -39,30 +36,6 @@ function ComparisonScene() { } }; - useEffect(() => { - if (!projectId) return; - - getVersionHistoryApi(projectId) - .then((data) => { - const versions: VersionHistory = []; - data.versions.forEach((version: any) => { - versions.push({ - version: version.version, - versionId: version.versionId, - versionName: version.versionName, - versionDescription: version.description, - timeStamp: version.createdAt, - createdBy: version.createdBy.userName, - }); - }); - setVersions(versions); - }) - .catch(() => { - console.error("Error fetching version history"); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [projectId]); - useEffect(() => { if (mainProduct && comparisonProduct && compareProductsData.length > 1) { const hasMain = compareProductsData.some((val) => val.productUuid === mainProduct.productUuid); @@ -79,7 +52,7 @@ function ComparisonScene() { return ( <> - {isVersionSaved && activeModule === "simulation" && selectedProduct && ( + {isComparing && activeModule === "simulation" && selectedProduct && ( <> {selectedVersion && !isPlaying && (
diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx index 5db82a5..82b237f 100644 --- a/app/src/components/layout/scenes/MainScene.tsx +++ b/app/src/components/layout/scenes/MainScene.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useParams } from "react-router-dom"; -import { useLoadingProgress, useRenameModeStore, useSaveVersion, useSelectedComment, useSocketStore, useWidgetSubOption } from "../../../store/builder/store"; +import { useLoadingProgress, useRenameModeStore, useIsComparing, useSelectedComment, useSocketStore, useWidgetSubOption } from "../../../store/builder/store"; import useModuleStore, { useThreeDStore } from "../../../store/ui/useModuleStore"; import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore"; @@ -36,7 +36,7 @@ import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionCo function MainScene() { const { setMainProduct } = useMainProduct(); - const { isVersionSaved, setIsVersionSaved } = useSaveVersion(); + const { isComparing, setIsComparing } = useIsComparing(); const { activeModule } = useModuleStore(); const { selectedUser } = useSelectedUserStore(); const { loadingProgress } = useLoadingProgress(); @@ -67,9 +67,9 @@ function MainScene() { useEffect(() => { if (activeModule !== "simulation") { clearComparisonProduct(); - setIsVersionSaved(false); + setIsComparing(false); } - }, [activeModule, clearComparisonProduct, setIsVersionSaved]); + }, [activeModule, clearComparisonProduct, setIsComparing]); useEffect(() => { if (!projectId) return; @@ -162,14 +162,14 @@ function MainScene() { {loadingProgress > 0 && } {!isPlaying && ( <> - {toggleThreeD && !isVersionSaved && } + {toggleThreeD && !isComparing && } )} {activeModule === "market" && } - {activeModule !== "market" && !isPlaying && !isVersionSaved && } + {activeModule !== "market" && !isPlaying && !isComparing && } {isPlaying && activeModule === "simulation" && loadingProgress === 0 && } {isPlaying && activeModule !== "simulation" && } @@ -204,7 +204,7 @@ function MainScene() {
- {selectedProduct && selectedVersion && isVersionSaved && !isPlaying && activeModule === "simulation" && ( + {selectedProduct && selectedVersion && isComparing && !isPlaying && activeModule === "simulation" && (
{ const [activeOption, setActiveOption] = useState("Widgets"); @@ -16,7 +16,7 @@ const SideBarLeft: React.FC = () => { const { toggleUILeft } = useToggleStore(); const { activeModule } = useModuleStore(); - const { isVersionSaved } = useSaveVersion(); + const { isComparing } = useIsComparing(); // Reset activeOption whenever activeModule changes useEffect(() => { @@ -35,7 +35,7 @@ const SideBarLeft: React.FC = () => { return (
@@ -74,7 +74,7 @@ const SideBarLeft: React.FC = () => { } else { return ( <> - {!isVersionSaved && ( + {!isComparing && ( <> { const { selectedEventData } = useSelectedEventData(); const { selectedEventSphere } = useSelectedEventSphere(); const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore(); - const { isVersionSaved } = useSaveVersion(); + const { isComparing } = useIsComparing(); const [displayComponent, setDisplayComponent] = useState("none"); @@ -80,7 +80,7 @@ const SideBarRight: React.FC = () => { return; } - if (!isVersionSaved && activeModule === "simulation") { + if (!isComparing && activeModule === "simulation") { if (subModule === "simulations") { setDisplayComponent("simulations"); return; @@ -155,7 +155,7 @@ const SideBarRight: React.FC = () => { } setDisplayComponent("none"); - }, [viewVersionHistory, activeModule, subModule, isVersionSaved, selectedFloorAsset, selectedWall, selectedFloor, selectedAisle, toolMode, selectedDecal]); + }, [viewVersionHistory, activeModule, subModule, isComparing, selectedFloorAsset, selectedWall, selectedFloor, selectedAisle, toolMode, selectedDecal]); const renderComponent = () => { switch (displayComponent) { @@ -197,11 +197,11 @@ const SideBarRight: React.FC = () => { }; return ( -
e.stopPropagation()}> +
e.stopPropagation()}>
{toggleUIRight && ( <> - {(!isVersionSaved || activeModule !== "simulation") && ( + {(!isComparing || activeModule !== "simulation") && (
{activeModule !== "simulation" && ( <> diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index e7b24ce..5ae7c47 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -13,7 +13,7 @@ import { deleteProductApi } from "../../../../services/simulation/products/delet import { renameProductApi } from "../../../../services/simulation/products/renameProductApi"; import { determineExecutionMachineSequences } from "../../../../modules/simulation/simulator/functions/determineExecutionMachineSequences"; import ComparePopUp from "../../../ui/compareVersion/Compare"; -import { useCompareStore, useSaveVersion } from "../../../../store/builder/store"; +import { useCompareStore, useIsComparing } from "../../../../store/builder/store"; import { useToggleStore } from "../../../../store/ui/useUIToggleStore"; import { useParams } from "react-router-dom"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; @@ -36,10 +36,10 @@ const Simulations: React.FC = () => { const { setMainProduct } = useMainProduct(); const { selectedVersion } = versionStore(); const { comparePopUp, setComparePopUp } = useCompareStore(); - const { setIsVersionSaved } = useSaveVersion(); + const { setIsComparing } = useIsComparing(); const handleSaveVersion = () => { - setIsVersionSaved(true); + setIsComparing(true); setComparePopUp(false); setToggleUI(false, false); }; diff --git a/app/src/components/ui/compareVersion/CompareLayOut.tsx b/app/src/components/ui/compareVersion/CompareLayOut.tsx index 57b0f73..3c98b6f 100644 --- a/app/src/components/ui/compareVersion/CompareLayOut.tsx +++ b/app/src/components/ui/compareVersion/CompareLayOut.tsx @@ -1,19 +1,22 @@ +import { useParams } from "react-router-dom"; import React, { useState, useRef, useEffect, Suspense } from "react"; import { CompareLayoutIcon, LayoutIcon, ResizerIcon } from "../../icons/SimulationIcons"; -import { useLoadingProgress, useSaveVersion } from "../../../store/builder/store"; -import Search from "../inputs/Search"; -import OuterClick from "../../../utils/outerClick"; -import Scene from "../../../modules/scene/scene"; +import { useLoadingProgress, useIsComparing } from "../../../store/builder/store"; import { useComparisonProduct } from "../../../store/simulation/useSimulationStore"; import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; import { useSceneContext } from "../../../modules/scene/sceneContext"; import { getAllProductsApi } from "../../../services/simulation/products/getallProductsApi"; -import { useParams } from "react-router-dom"; +import Search from "../inputs/Search"; +import OuterClick from "../../../utils/outerClick"; +import Scene from "../../../modules/scene/scene"; +import useRestStates from "../../../hooks/useResetStates"; + +import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi"; const CompareLayOut = () => { const { clearComparisonProduct, comparisonProduct, setComparisonProduct } = useComparisonProduct(); const { versionStore } = useSceneContext(); - const { versionHistory, selectedVersion, setSelectedVersion, clearSelectedVersion } = versionStore(); + const { versionHistory, selectedVersion, setSelectedVersion, clearSelectedVersion, setVersions } = versionStore(); const { setLoadingProgress } = useLoadingProgress(); const [width, setWidth] = useState("50vw"); const [isResizing, setIsResizing] = useState(false); @@ -21,10 +24,43 @@ const CompareLayOut = () => { const wrapperRef = useRef(null); const startWidthRef = useRef(0); const startXRef = useRef(0); - const { setIsVersionSaved } = useSaveVersion(); + const { setIsComparing } = useIsComparing(); const { loadingProgress } = useLoadingProgress(); const { setIsPlaying } = usePlayButtonStore(); const { projectId } = useParams(); + const { resetStates } = useRestStates(); + + useEffect(() => { + return () => { + if (selectedVersion?.versionId) { + resetStates(); + } + }; + }, [selectedVersion?.versionId]); + + useEffect(() => { + if (!projectId) return; + + getVersionHistoryApi(projectId) + .then((data) => { + const versions: VersionHistory = []; + data.versions.forEach((version: any) => { + versions.push({ + version: version.version, + versionId: version.versionId, + versionName: version.versionName, + versionDescription: version.description, + timeStamp: version.createdAt, + createdBy: version.createdBy.userName, + }); + }); + setVersions(versions); + }) + .catch(() => { + console.error("Error fetching version history"); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projectId]); useEffect(() => { if (!comparisonProduct) { @@ -71,7 +107,7 @@ const CompareLayOut = () => { if (finalWidthVw <= 10) { setWidth("0px"); - setIsVersionSaved(false); + setIsComparing(false); clearComparisonProduct(); setIsPlaying(false); } else { diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 9788062..666595d 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -1,17 +1,17 @@ -import * as THREE from 'three'; -import { useEffect, useRef, useState } from 'react'; -import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; -import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; -import { useToggleView, useToolMode } from '../../../../../store/builder/store'; -import { AssetBoundingBox } from '../../functions/assetBoundingBox'; -import { useBuilderStore } from '../../../../../store/builder/useBuilderStore'; -import useModuleStore from '../../../../../store/ui/useModuleStore'; -import { useSceneContext } from '../../../../scene/sceneContext'; -import { SkeletonUtils } from 'three-stdlib'; +import * as THREE from "three"; +import { useEffect, useRef, useState } from "react"; +import { retrieveGLTF, storeGLTF } from "../../../../../utils/indexDB/idbUtils"; +import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +import { useToggleView, useToolMode } from "../../../../../store/builder/store"; +import { AssetBoundingBox } from "../../functions/assetBoundingBox"; +import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; +import useModuleStore from "../../../../../store/ui/useModuleStore"; +import { useSceneContext } from "../../../../scene/sceneContext"; +import { SkeletonUtils } from "three-stdlib"; -import { getAssetFieldApi } from '../../../../../services/factoryBuilder/asset/floorAsset/getAssetField'; -import { ModelAnimator } from './animator/modelAnimator'; -import { useModelEventHandlers } from './eventHandlers/useModelEventHandlers'; +import { getAssetFieldApi } from "../../../../../services/factoryBuilder/asset/floorAsset/getAssetField"; +import { ModelAnimator } from "./animator/modelAnimator"; +import { useModelEventHandlers } from "./eventHandlers/useModelEventHandlers"; function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendered: boolean; loader: GLTFLoader }>) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -31,33 +31,33 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere useEffect(() => { if (!fieldData && asset.eventData) { getAssetFieldApi(asset.assetId).then((data) => { - if (data.type === 'ArmBot') { + if (data.type === "ArmBot") { if (data.data) { const fieldData: IK[] = data.data; setFieldData(fieldData); } - } else if (data.type === 'Conveyor' || data.type === 'Crane') { + } else if (data.type === "Conveyor" || data.type === "Crane") { if (data.data) { const fieldData = data.data; setFieldData(fieldData); } } - }) + }); } - }, [asset.modelUuid, fieldData]) + }, [asset.modelUuid, fieldData]); useEffect(() => { setDeletableFloorAsset(null); if (selectedFloorAsset === null || selectedFloorAsset.userData.modelUuid !== asset.modelUuid) { resetAnimation(asset.modelUuid); } - }, [activeModule, toolMode, selectedFloorAsset]) + }, [activeModule, toolMode, selectedFloorAsset]); useEffect(() => { if (selectedFloorAsset && selectedFloorAsset.userData.modelUuid === asset.modelUuid) { setSelectedFloorAsset(groupRef.current); } - }, [isRendered, selectedFloorAsset]) + }, [isRendered, selectedFloorAsset]); useEffect(() => { if (selectedAssets.length > 0) { @@ -69,7 +69,7 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere } else { setIsSelected(false); } - }, [selectedAssets]) + }, [selectedAssets]); useEffect(() => { if (gltfScene) { @@ -78,13 +78,13 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere child.castShadow = true; child.receiveShadow = true; } - }) + }); } }, [gltfScene]); const logModelStatus = (modelId: string, status: string) => { // console.log(modelId, status); - } + }; useEffect(() => { // Calculate Bounding Box @@ -101,62 +101,61 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere clone.animations = cachedModel.animations || []; setGltfScene(clone); calculateBoundingBox(clone); - logModelStatus(assetId, 'cache-loaded'); + logModelStatus(assetId, "cache-loaded"); 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); - logModelStatus(assetId, 'indexedDB-loaded'); - }, - 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(() => { + 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); - logModelStatus(assetId, 'backend-loaded'); - }) - .catch((error) => { - console.error( - `[Backend] Error storing/loading ${asset.modelName}:`, - error - ); - }); - }, - undefined, - (error) => { - echo.error(`[Backend] Error loading ${asset.modelName}:`); + logModelStatus(assetId, "indexedDB-loaded"); + }, + undefined, + () => { + echo.error(`[IndexedDB] Error loading ${asset.modelName}:`); + URL.revokeObjectURL(blobUrl); + } + ); + return; } - ); - }).catch((err) => { - console.error("Failed to load model:", asset.assetId, err); - }); + + // 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); + logModelStatus(assetId, "backend-loaded"); + }) + .catch((error) => { + console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error); + }); + }, + undefined, + () => { + 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, asset }); @@ -164,7 +163,7 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere return ( {isRendered ? ( <> - - ) : ( - <> - {!isSelected && - - } - + <>{!isSelected && } )} - {isSelected && - - } + {isSelected && } )} ); } -export default Model; \ No newline at end of file +export default Model; diff --git a/app/src/modules/builder/asset/models/models.tsx b/app/src/modules/builder/asset/models/models.tsx index 78eacab..61be1b2 100644 --- a/app/src/modules/builder/asset/models/models.tsx +++ b/app/src/modules/builder/asset/models/models.tsx @@ -1,13 +1,13 @@ import { useEffect, useRef, useState } from "react"; import { useThree, useFrame } from "@react-three/fiber"; import { Group, Vector3 } from "three"; -import { CameraControls } from '@react-three/drei'; -import { useLimitDistance, useRenderDistance } from '../../../../store/builder/store'; -import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore'; -import { useSceneContext } from '../../../scene/sceneContext'; +import { CameraControls } from "@react-three/drei"; +import { useLimitDistance, useRenderDistance } from "../../../../store/builder/store"; +import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; +import { useSceneContext } from "../../../scene/sceneContext"; import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; -import Model from './model/model'; +import Model from "./model/model"; import { GLTFLoader } from "three/examples/jsm/Addons"; const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); @@ -15,7 +15,7 @@ const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/w function Models({ loader }: { readonly loader: GLTFLoader }) { const { controls, camera } = useThree(); const assetGroupRef = useRef(null); - const { assetStore } = useSceneContext(); + const { assetStore, layout } = useSceneContext(); const { assets } = assetStore(); const { selectedFloorAsset, setSelectedFloorAsset } = useBuilderStore(); const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); @@ -40,13 +40,13 @@ function Models({ loader }: { readonly loader: GLTFLoader }) { camera.getWorldPosition(cameraPos.current); for (const asset of assets) { const isRendered = renderMap[asset.modelUuid] ?? false; - distanceWorker.postMessage({ modelUuid: asset.modelUuid, assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2], }, cameraPosition: cameraPos.current, limitDistance, renderDistance, isRendered, }); + distanceWorker.postMessage({ modelUuid: asset.modelUuid, assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2] }, cameraPosition: cameraPos.current, limitDistance, renderDistance, isRendered }); } }); return ( { e.stopPropagation(); @@ -67,4 +67,4 @@ function Models({ loader }: { readonly loader: GLTFLoader }) { ); } -export default Models; \ No newline at end of file +export default Models; diff --git a/app/src/modules/scene/camera/syncCam.tsx b/app/src/modules/scene/camera/syncCam.tsx new file mode 100644 index 0000000..647ef28 --- /dev/null +++ b/app/src/modules/scene/camera/syncCam.tsx @@ -0,0 +1,32 @@ +import { useFrame, useThree } from "@react-three/fiber"; +import { useSceneContext } from "../sceneContext"; +import { CameraControls } from "@react-three/drei"; +import { useIsComparing } from "../../../store/builder/store"; +import useModuleStore from "../../../store/ui/useModuleStore"; +import { useComparisonProduct } from "../../../store/simulation/useSimulationStore"; +import { useSceneStore } from "../../../store/scene/useSceneStore"; +import { Vector3 } from "three"; + +function SyncCam() { + const { layout } = useSceneContext(); + const { controls } = useThree(); + const { isComparing } = useIsComparing(); + const { activeModule } = useModuleStore(); + const { comparisonProduct } = useComparisonProduct(); + const { setCamera, camState } = useSceneStore(); + + useFrame(() => { + if (layout === "Comparison Layout" && controls && camState) { + (controls as CameraControls).setLookAt(camState.position.x, camState.position.y, camState.position.z, camState.target.x, camState.target.y, camState.target.z, true); + } + if (layout === "Main Layout" && controls && isComparing && activeModule === "simulation" && comparisonProduct) { + const position = (controls as CameraControls).getPosition(new Vector3()); + const target = (controls as CameraControls).getTarget(new Vector3()); + setCamera(position, target); + } + }); + + return <>; +} + +export default SyncCam; diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index 3e34faf..942b7f1 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -2,7 +2,7 @@ import { CameraControls } from "@react-three/drei"; import { useRef, useEffect } from "react"; import { useThree } from "@react-three/fiber"; import * as THREE from "three"; -import * as CONSTANTS from '../../../types/world/worldConstants'; +import * as CONSTANTS from "../../../types/world/worldConstants"; import { useSocketStore, useToggleView, useResetCamera } from "../../../store/builder/store"; import CamMode from "../camera/camMode"; @@ -20,6 +20,7 @@ import { getUserData } from "../../../functions/getUserData"; import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi"; import { setCameraApi } from "../../../services/factoryBuilder/camera/setCameraApi"; import updateCamPosition from "../camera/functions/updateCameraPosition"; +import SyncCam from "../camera/syncCam"; export default function Controls() { const controlsRef = useRef(null); @@ -38,15 +39,17 @@ export default function Controls() { } if (!projectId) return; - getCameraApi(projectId).then((data) => { - if (data?.position && data?.target) { - controlsRef.current?.setPosition(data.position.x, data.position.y, data.position.z); - controlsRef.current?.setTarget(data.target.x, data.target.y, data.target.z); - } else { - controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition); - controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget); - } - }).catch((error) => console.error("Failed to fetch camera data:", error)); + getCameraApi(projectId) + .then((data) => { + if (data?.position && data?.target) { + controlsRef.current?.setPosition(data.position.x, data.position.y, data.position.z); + controlsRef.current?.setTarget(data.target.x, data.target.y, data.target.z); + } else { + controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition); + controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget); + } + }) + .catch((error) => console.error("Failed to fetch camera data:", error)); }, [projectId]); useEffect(() => { @@ -56,12 +59,7 @@ export default function Controls() { controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth); if (!socket?.connected) { - setCameraApi( - projectId, - new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition), - new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget), - new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation), - ) + setCameraApi(projectId, new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition), new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget), new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation)); } else { const camData = { organization, @@ -70,9 +68,9 @@ export default function Controls() { target: new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget), rotation: new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation), socketId: socket.id, - projectId + projectId, }; - socket.emit('v1:Camera:set', camData) + socket.emit("v1:Camera:set", camData); } setResetCamera(false); @@ -152,6 +150,7 @@ export default function Controls() { + @@ -165,7 +164,6 @@ export default function Controls() { - ); -} \ No newline at end of file +} diff --git a/app/src/modules/scene/environment/shadow.tsx b/app/src/modules/scene/environment/shadow.tsx index 03cf942..77df5ba 100644 --- a/app/src/modules/scene/environment/shadow.tsx +++ b/app/src/modules/scene/environment/shadow.tsx @@ -1,13 +1,7 @@ import { useRef, useEffect } from "react"; import { useThree } from "@react-three/fiber"; import * as THREE from "three"; -import { - useAzimuth, - useElevation, - useShadows, - useSunPosition, - useTileDistance, -} from "../../../store/builder/store"; +import { useAzimuth, useElevation, useShadows, useSunPosition, useTileDistance } from "../../../store/builder/store"; import * as CONSTANTS from "../../../types/world/worldConstants"; const shadowWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/shadowWorker", import.meta.url)); @@ -67,31 +61,11 @@ export default function Shadows() { {/* {(lightRef.current?.shadow) && } */} - + - + - + ); diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 461fd77..e9eff60 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -8,12 +8,7 @@ type SocketStore = { dashBoardSocket?: ReturnType | null; projectSocket?: ReturnType | null; threadSocket?: ReturnType | null; - initializeSocket: ( - email?: string, - organization?: string, - token?: string, - refreshToken?: string - ) => void; + initializeSocket: (email?: string, organization?: string, token?: string, refreshToken?: string) => void; disconnectSocket: () => void; }; @@ -23,54 +18,34 @@ export const useSocketStore = create((set, get) => ({ dashBoardSocket: null, projectSocket: null, threadSocket: null, - initializeSocket: ( - email?: string, - organization?: string, - token?: string, - refreshToken?: string - ) => { + 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, @@ -154,8 +129,7 @@ export const useShadows = create((set: any) => ({ export const useSunPosition = create((set: any) => ({ sunPosition: { x: undefined, y: undefined, z: undefined }, - setSunPosition: (newSuntPosition: any) => - set({ sunPosition: newSuntPosition }), + setSunPosition: (newSuntPosition: any) => set({ sunPosition: newSuntPosition }), })); export const useProjectName = create((set: any) => ({ @@ -215,14 +189,12 @@ export const useRenameModeStore = create((set: any) => ({ export const useObjectPosition = create((set: any) => ({ objectPosition: { x: undefined, y: undefined, z: undefined }, - setObjectPosition: (newObjectPosition: any) => - set({ objectPosition: newObjectPosition }), + 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 }), + setObjectRotation: (newObjectRotation: any) => set({ objectRotation: newObjectRotation }), })); export const useDrieTemp = create((set: any) => ({ @@ -234,16 +206,14 @@ 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: typeof callback === "function" ? callback(state.activeUsers) : callback, })), })); export const useDrieUIValue = create((set: any) => ({ 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) => ({ @@ -367,8 +337,7 @@ interface ShortcutStore { export const useShortcutStore = create((set) => ({ showShortcuts: false, setShowShortcuts: (value) => set({ showShortcuts: value }), - toggleShortcuts: () => - set((state) => ({ showShortcuts: !state.showShortcuts })), + toggleShortcuts: () => set((state) => ({ showShortcuts: !state.showShortcuts })), })); export const useMachineCount = create((set: any) => ({ @@ -470,19 +439,18 @@ interface CompareStore { export const useCompareStore = create((set) => ({ comparePopUp: false, setComparePopUp: (value) => set({ comparePopUp: value }), - toggleComparePopUp: () => - set((state) => ({ comparePopUp: !state.comparePopUp })), + toggleComparePopUp: () => set((state) => ({ comparePopUp: !state.comparePopUp })), })); // Save state store -interface SaveVersionStore { - isVersionSaved: boolean; - setIsVersionSaved: (value: boolean) => void; +interface IsComparingStore { + isComparing: boolean; + setIsComparing: (value: boolean) => void; } -export const useSaveVersion = create((set) => ({ - isVersionSaved: false, - setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }), +export const useIsComparing = create((set) => ({ + isComparing: false, + setIsComparing: (value: boolean) => set({ isComparing: value }), })); interface ViewSceneState { @@ -494,8 +462,7 @@ export const useViewSceneStore = create((set) => ({ viewSceneLabels: getInitialViewSceneLabels(), setViewSceneLabels: (value) => { set((state) => { - const newValue = - typeof value === "function" ? value(state.viewSceneLabels) : value; + const newValue = typeof value === "function" ? value(state.viewSceneLabels) : value; // Store in localStorage manually localStorage.setItem("viewSceneLabels", JSON.stringify(newValue)); @@ -558,4 +525,4 @@ export const useSelectedPath = create((set: any) => ({ export const useContextActionStore = create((set: any) => ({ contextAction: null, setContextAction: (x: any) => set({ contextAction: x }), -})); \ No newline at end of file +})); diff --git a/app/src/store/scene/useSceneStore.ts b/app/src/store/scene/useSceneStore.ts new file mode 100644 index 0000000..f0c452a --- /dev/null +++ b/app/src/store/scene/useSceneStore.ts @@ -0,0 +1,27 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; +import * as THREE from "three"; + +type SceneStore = { + camState: { + position: THREE.Vector3; + target: THREE.Vector3; + }; + setCamera: (pos: THREE.Vector3, target: THREE.Vector3) => void; +}; + +export const useSceneStore = create()( + immer((set) => ({ + camState: { + position: new THREE.Vector3(0, 5, 10), + target: new THREE.Vector3(0, 0, 0), + }, + + setCamera: (pos, target) => + set((state) => { + state.camState.position.copy(pos); + + state.camState.target.copy(target); + }), + })) +); diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 6c00911..21d69eb 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/ui/useModuleStore"; import { usePlayerStore, useToggleStore } from "../../store/ui/useUIToggleStore"; -import useVersionHistoryVisibleStore, { useActiveSubTool, useActiveTool, useAddAction, useDfxUpload, useRenameModeStore, useSaveVersion, useSelectedComment, useShortcutStore, useToggleView, useToolMode, useViewSceneStore } from "../../store/builder/store"; +import useVersionHistoryVisibleStore, { useActiveSubTool, useActiveTool, useAddAction, useDfxUpload, useRenameModeStore, useIsComparing, useSelectedComment, useShortcutStore, useToggleView, useToolMode, useViewSceneStore } from "../../store/builder/store"; import useCameraModeStore, { usePlayButtonStore } from "../../store/ui/usePlayButtonStore"; import { detectModifierKeys } from "./detectModifierKeys"; import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; @@ -27,7 +27,7 @@ const KeyPressListener: React.FC = () => { const { clearSelectedZone } = useSelectedZoneStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore(); const { setWalkMode } = useCameraModeStore(); - const { setIsVersionSaved } = useSaveVersion(); + const { setIsComparing } = useIsComparing(); const { isLogListVisible, setIsLogListVisible } = useLogger(); const { hidePlayer, setHidePlayer } = usePlayerStore(); const { setViewSceneLabels } = useViewSceneStore(); @@ -177,7 +177,7 @@ const KeyPressListener: React.FC = () => { setIsPlaying(false); clearSelectedZone(); setShowShortcuts(false); - setIsVersionSaved(false); + setIsComparing(false); clearComparisonProduct(); setIsLogListVisible(false); setIsRenameMode(false);