From 67b942ac16f802f7eba92fd29845c4a40b1f83d6 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 23 Aug 2025 17:52:15 +0530 Subject: [PATCH 01/19] bug fix wallAsset --- app/src/modules/builder/wall/Instances/instance/wall.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index 9d68d73..8798b01 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -121,7 +121,7 @@ function Wall({ wall }: { readonly wall: Wall }) { castShadow receiveShadow ref={meshRef} - geometry={geometry} + geometry={visible ? geometry : new THREE.BoxGeometry(0, 0)} position={[centerX, centerY, centerZ]} rotation={[0, -angle, 0]} userData={wall} From b4b412ce144125ccf06671e77cb3bcced9155284 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 12:02:46 +0530 Subject: [PATCH 02/19] added move and rotate tool using tranformControls --- .../properties/AssetProperties.tsx | 10 +- app/src/components/ui/Tools.tsx | 6 + .../model/eventHandlers/useEventHandlers.ts | 7 +- .../contextControls/contextControls.tsx | 425 +++++++++--------- .../selection3D/moveControls3D.tsx | 16 +- .../selection3D/rotateControls3D.tsx | 18 +- .../selection3D/selectionControls3D.tsx | 11 +- .../selection3D/transformControls3D.tsx | 380 ++++++++++++++++ .../transformControls/transformControls.tsx | 16 +- .../undoRedo3D/undoRedo3DControls.tsx | 2 +- app/src/store/builder/useUndoRedo3DStore.ts | 25 +- 11 files changed, 671 insertions(+), 245 deletions(-) create mode 100644 app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index a62abda..9c8b979 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -32,9 +32,9 @@ const AssetProperties: React.FC = () => { setUserData([]); }; - const handleUserDataChange = (id: number, newValue: string) => {}; + const handleUserDataChange = (id: number, newValue: string) => { }; - const handleRemoveUserData = (id: number) => {}; + const handleRemoveUserData = (id: number) => { }; const handleAnimationClick = (animation: string) => { if (selectedFloorItem) { @@ -57,14 +57,14 @@ const AssetProperties: React.FC = () => {
{objectPosition && ( {}} + onChange={() => { }} value1={parseFloat(objectPosition.x.toFixed(5))} value2={parseFloat(objectPosition.z.toFixed(5))} /> )} {objectRotation && ( {}} + onChange={() => { }} value={parseFloat(objectRotation.y.toFixed(5))} /> )} @@ -107,7 +107,7 @@ const AssetProperties: React.FC = () => { if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations) return ( i === 0 && ( -
+
Looks like there are no preset animations yet. Stay tuned for future additions!
diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index d5d49e4..b89f2ea 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -146,6 +146,12 @@ const Tools: React.FC = () => { case "draw-floor": is2D && setToolMode("Floor"); break; + case "move": + if (!is2D) setToolMode("Move-Asset"); + break; + case "rotate": + if (!is2D) setToolMode("Rotate-Asset"); + break; case "measure": setToolMode("MeasurementScale"); break; 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 360c513..85b5b4b 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -34,7 +34,7 @@ export function useModelEventHandlers({ const { socket } = useSocketStore(); const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext(); const { push3D } = undoRedo3DStore(); - const { getAssetById, removeAsset } = assetStore(); + const { removeAsset } = assetStore(); const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); const { resourceManagementId, setResourceManagementId } = useResourceManagementId(); const { removeEvent, getEventByModelUuid } = eventStore(); @@ -77,11 +77,12 @@ export function useModelEventHandlers({ } }, [zoneAssetId]) + useEffect(() => { if (!resourceManagementId) return if (resourceManagementId === asset.modelUuid) { - - + + handleDblClick(asset); } diff --git a/app/src/modules/scene/controls/contextControls/contextControls.tsx b/app/src/modules/scene/controls/contextControls/contextControls.tsx index 142f8d1..46801c1 100644 --- a/app/src/modules/scene/controls/contextControls/contextControls.tsx +++ b/app/src/modules/scene/controls/contextControls/contextControls.tsx @@ -1,41 +1,14 @@ import { useEffect, useRef, 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 { useContextActionStore, useRenameModeStore, useSelectedAssets, useToggleView, useToolMode, } from "../../../../store/builder/store"; import ContextMenu from "../../../../components/ui/menu/contextMenu"; +import useModuleStore from "../../../../store/useModuleStore"; 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(); - const rightDrag = useRef(false); - const isRightMouseDown = useRef(false); - - useEffect(() => { - if (selectedAssets.length === 1) { - setVisibility({ + const { gl, controls } = useThree(); + const [canRender, setCanRender] = useState(false); + const [visibility, setVisibility] = useState({ rename: true, focus: true, flipX: true, @@ -49,195 +22,219 @@ function ContextControls() { 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]); + }); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); + const { toolMode } = useToolMode(); + const { selectedAssets } = useSelectedAssets(); + const { setContextAction } = useContextActionStore(); + const { setIsRenameMode } = useRenameModeStore(); + const rightDrag = useRef(false); + const isRightMouseDown = useRef(false); - useEffect(() => { - const canvasElement = gl.domElement; - - const onPointerDown = (evt: any) => { - if (evt.button === 2) { - isRightMouseDown.current = true; - rightDrag.current = false; - } - }; - - const onPointerMove = () => { - if (isRightMouseDown.current) { - rightDrag.current = true; - } - }; - - const onPointerUp = (evt: any) => { - if (evt.button === 2) { - isRightMouseDown.current = false; - } - }; - - const handleContextClick = (event: MouseEvent) => { - event.preventDefault(); - if (rightDrag.current) return; - 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; + 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, + }); } - } else { + }, [selectedAssets]); + + useEffect(() => { + const canvasElement = gl.domElement; + + const onPointerDown = (evt: any) => { + if (evt.button === 2) { + isRightMouseDown.current = true; + rightDrag.current = false; + } + }; + + const onPointerMove = () => { + if (isRightMouseDown.current) { + rightDrag.current = true; + } + }; + + const onPointerUp = (evt: any) => { + if (evt.button === 2) { + isRightMouseDown.current = false; + } + }; + + const handleContextClick = (event: MouseEvent) => { + event.preventDefault(); + if (rightDrag.current) return; + 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 && !toggleView && activeModule === "builder" && toolMode === 'cursor') { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + canvasElement.addEventListener("contextmenu", handleContextClick); + } else { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setMenuPosition({ x: 0, y: 0 }); + } + + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + canvasElement.removeEventListener("contextmenu", handleContextClick); + }; + }, [controls, gl, selectedAssets, toggleView, activeModule, toolMode]); + + const handleAssetRename = () => { setCanRender(false); if (controls) { - (controls as CameraControls).enabled = true; + (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"); }; - if (selectedAssets.length > 0) { - canvasElement.addEventListener("pointerdown", onPointerDown); - canvasElement.addEventListener("pointermove", onPointerMove); - canvasElement.addEventListener("pointerup", onPointerUp); - canvasElement.addEventListener("contextmenu", handleContextClick); - } else { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setMenuPosition({ x: 0, y: 0 }); - } - - return () => { - canvasElement.removeEventListener("pointerdown", onPointerDown); - canvasElement.removeEventListener("pointermove", onPointerMove); - canvasElement.removeEventListener("pointerup", onPointerUp); - canvasElement.removeEventListener("contextmenu", handleContextClick); - }; - }, [controls, 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()} - /> - - - )} - - ); + 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/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index e0940c9..248ec9d 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 { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store"; +import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; import * as Types from "../../../../../types/world/worldTypes"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; @@ -12,6 +12,7 @@ import { useProductContext } from "../../../../simulation/products/productContex import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +import useModuleStore from "../../../../../store/useModuleStore"; // import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; @@ -20,6 +21,8 @@ function MoveControls3D({ boundingBoxRef }: any) { const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { toggleView } = useToggleView(); + const { toolMode } = useToolMode(); + const { activeModule } = useModuleStore(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); @@ -162,7 +165,7 @@ function MoveControls3D({ boundingBoxRef }: any) { } }; - if (!toggleView) { + if (!toggleView && toolMode === 'cursor') { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); @@ -178,7 +181,14 @@ function MoveControls3D({ boundingBoxRef }: any) { canvasElement?.removeEventListener("keyup", onKeyUp); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]); + }, [camera, controls, scene, toggleView, toolMode, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]); + + useEffect(() => { + if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) { + resetToInitialPositions(); + setMovedObjects([]); + } + }, [activeModule, toolMode, toggleView]); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index 5af7a3e..66904b2 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 { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; +import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode } from "../../../../../store/builder/store"; import * as Types from "../../../../../types/world/worldTypes"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useParams } from "react-router-dom"; @@ -11,6 +11,7 @@ import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { handleAssetRotationSnap } from "./functions/handleAssetRotationSnap"; +import useModuleStore from "../../../../../store/useModuleStore"; // import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; @@ -18,6 +19,8 @@ function RotateControls3D() { const { camera, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); + const { toolMode } = useToolMode(); + const { activeModule } = useModuleStore(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); @@ -137,7 +140,7 @@ function RotateControls3D() { prevPointerPosition.current = currentPointer.clone(); }; - if (!toggleView) { + if (!toggleView && toolMode === 'cursor') { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); @@ -152,7 +155,14 @@ function RotateControls3D() { canvasElement.removeEventListener("keydown", onKeyDown); canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations]); + }, [camera, scene, toggleView, toolMode, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations]); + + useEffect(() => { + if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) { + resetToInitialRotations(); + setRotatedObjects([]); + } + }, [activeModule, toolMode, toggleView]); const resetToInitialRotations = useCallback(() => { setTimeout(() => { @@ -385,7 +395,7 @@ function RotateControls3D() { setIsRotating(false); clearSelection(); - }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]); + }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId, initialPositions, initialRotations]); const clearSelection = () => { setPastedObjects([]); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index ed458c2..512b80f 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -16,22 +16,23 @@ import DuplicationControls3D from "./duplicationControls3D"; import CopyPasteControls3D from "./copyPasteControls3D"; import MoveControls3D from "./moveControls3D"; import RotateControls3D from "./rotateControls3D"; +import TransformControls3D from "./transformControls3D"; // import { deleteFloorItem } from '../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi'; const SelectionControls3D: React.FC = () => { const { camera, controls, gl, scene, raycaster, pointer } = useThree(); const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); + const { toolMode } = useToolMode(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const boundingBoxRef = useRef(); - const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); const { contextAction, setContextAction } = useContextActionStore() const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); const { push3D } = undoRedo3DStore(); const { removeAsset, getAssetById, movedObjects, rotatedObjects, copiedObjects, pastedObjects, duplicatedObjects, setPastedObjects, setDuplicatedObjects } = assetStore(); const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); - const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { selectedProductStore } = useProductContext(); @@ -202,7 +203,7 @@ const SelectionControls3D: React.FC = () => { rightClickMoved.current = false; }; - if (!toggleView && activeModule === "builder" && toolMode === 'cursor') { + if (!toggleView && activeModule === "builder" && (toolMode === 'cursor' || toolMode === 'Move-Asset' || toolMode === 'Rotate-Asset')) { helper.enabled = true; canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); @@ -227,7 +228,7 @@ const SelectionControls3D: React.FC = () => { }, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, activeModule, toolMode]); useEffect(() => { - if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) { + if (activeModule !== "builder" || (toolMode !== 'cursor' && toolMode !== 'Move-Asset' && toolMode !== 'Rotate-Asset') || toggleView) { clearSelection(); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -381,6 +382,8 @@ const SelectionControls3D: React.FC = () => { + + ); }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx new file mode 100644 index 0000000..a6aa2bb --- /dev/null +++ b/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx @@ -0,0 +1,380 @@ +import * as THREE from 'three'; +import { useRef, useEffect, useState, useCallback } from 'react'; +import { useThree } from '@react-three/fiber'; +import { TransformControls } from '@react-three/drei'; +import { useSelectedAssets, useSocketStore, useToolMode } from '../../../../../store/builder/store'; +import { useProductContext } from '../../../../simulation/products/productContext'; +import { useVersionContext } from '../../../../builder/version/versionContext'; +import { useSceneContext } from '../../../sceneContext'; +import { useParams } from 'react-router-dom'; + +import { getUserData } from '../../../../../functions/getUserData'; +import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; +import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys'; +// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; + +function TransformControls3D() { + const { selectedAssets, setSelectedAssets } = useSelectedAssets(); + const { toolMode } = useToolMode(); + const { camera, scene, gl } = useThree(); + const transformControlsRef = useRef(null); + const [visible, setVisible] = useState(false); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); + const { socket } = useSocketStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); + const { push3D, subscribeUndoRedo } = undoRedo3DStore(); + const { updateAsset, getAssetById } = assetStore(); + const { userId, organization } = getUserData(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + const pivotPointRef = useRef(new THREE.Vector3()); + const initialPositionsRef = useRef>(new Map()); + const initialRotationsRef = useRef>(new Map()); + const initialPivotPositionRef = useRef(new THREE.Vector3()); + const initialQuaternionsRef = useRef>(new Map()); + const tempObjectRef = useRef(new THREE.Object3D()); + + const updateBackend = useCallback(( + productName: string, + productUuid: string, + projectId: string, + eventData: any + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }, [selectedVersion]); + + const recalcGizmo = useCallback(() => { + if (selectedAssets.length === 0) return; + + const bbox = new THREE.Box3(); + selectedAssets.forEach(obj => bbox.expandByObject(obj)); + bbox.getCenter(pivotPointRef.current); + initialPivotPositionRef.current.copy(pivotPointRef.current); + + tempObjectRef.current.position.copy(pivotPointRef.current); + tempObjectRef.current.rotation.set(0, 0, 0); + tempObjectRef.current.scale.set(1, 1, 1); + + initialPositionsRef.current.clear(); + initialRotationsRef.current.clear(); + initialQuaternionsRef.current.clear(); + + selectedAssets.forEach(obj => { + initialPositionsRef.current.set(obj.uuid, obj.position.clone()); + initialRotationsRef.current.set(obj.uuid, obj.rotation.clone()); + initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone()); + }); + }, [selectedAssets]); + + useEffect(() => { + const unsubscribe = subscribeUndoRedo(() => { + setTimeout(() => { + recalcGizmo(); + }, 10); + }); + return unsubscribe; + }, [subscribeUndoRedo, recalcGizmo]); + + const handleTransformationComplete = useCallback(() => { + if (selectedAssets.length === 0) return; + + const updatedAssets = [...selectedAssets]; + setSelectedAssets(updatedAssets); + + selectedAssets.forEach(obj => { + const asset = getAssetById(obj.uuid); + if (!asset) return; + + updateAsset(asset.modelUuid, { + position: [obj.position.x, obj.position.y, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + }); + + if (asset.eventData) { + const eventData = eventStore.getState().getEventByModelUuid(asset.modelUuid); + const productData = productStore.getState().getEventByModelUuid( + selectedProduct.productUuid, + asset.modelUuid + ); + + if (eventData) { + eventStore.getState().updateEvent(asset.modelUuid, { + position: [obj.position.x, obj.position.y, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + }); + } + + if (productData) { + const event = productStore + .getState() + .updateEvent( + selectedProduct.productUuid, + asset.modelUuid, + { + position: [obj.position.x, obj.position.y, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + } + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + } + } + + //REST + + // setAssetsApi( + // organization, + // asset.modelUuid, + // asset.modelName, + // [obj.position.x, 0, obj.position.z], + // { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + // asset.assetId, + // false, + // true, + // ); + + //SOCKET + + const data = { + organization, + modelUuid: asset.modelUuid, + modelName: asset.modelName, + assetId: asset.assetId, + position: [obj.position.x, obj.position.y, obj.position.z], + rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + isLocked: false, + isVisible: true, + socketId: socket.id, + versionId: selectedVersion?.versionId || '', + userId, + projectId + }; + + socket.emit("v1:model-asset:add", data); + + }); + }, [selectedAssets, setSelectedAssets, getAssetById, updateAsset, eventStore, productStore, selectedProduct, updateBackend, projectId, organization, socket, selectedVersion, userId, push3D]); + + useEffect(() => { + const temp = tempObjectRef.current; + scene.add(temp); + return () => { + scene.remove(temp); + }; + }, [scene]); + + useEffect(() => { + if (selectedAssets.length === 0) { + setVisible(false); + return; + } + + const bbox = new THREE.Box3(); + selectedAssets.forEach(obj => bbox.expandByObject(obj)); + bbox.getCenter(pivotPointRef.current); + initialPivotPositionRef.current.copy(pivotPointRef.current); + + tempObjectRef.current.position.copy(pivotPointRef.current); + tempObjectRef.current.rotation.set(0, 0, 0); + tempObjectRef.current.scale.set(1, 1, 1); + + initialPositionsRef.current.clear(); + initialRotationsRef.current.clear(); + initialQuaternionsRef.current.clear(); + selectedAssets.forEach(obj => { + initialPositionsRef.current.set(obj.uuid, obj.position.clone()); + initialRotationsRef.current.set(obj.uuid, obj.rotation.clone()); + initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone()); + }); + + setVisible(true); + }, [selectedAssets]); + + useEffect(() => { + const controls = transformControlsRef.current; + if (!controls || !visible) return; + + controls.attach(tempObjectRef.current); + controls.setMode(toolMode === 'Move-Asset' ? 'translate' : 'rotate'); + + const onObjectChange = () => { + const temp = tempObjectRef.current; + if (!temp) return; + + selectedAssets.forEach(obj => { + const initialPos = initialPositionsRef.current.get(obj.uuid); + const initialQuat = initialQuaternionsRef.current.get(obj.uuid); + if (!initialPos || !initialQuat) return; + + if (controls.mode === 'translate') { + const delta = new THREE.Vector3().copy(temp.position).sub(initialPivotPositionRef.current); + obj.position.copy(initialPos).add(delta); + } else if (controls.mode === 'rotate') { + const deltaQuat = new THREE.Quaternion().setFromEuler(temp.rotation); + const relPos = initialPos.clone().sub(initialPivotPositionRef.current); + relPos.applyQuaternion(deltaQuat); + obj.position.copy(initialPivotPositionRef.current).add(relPos); + obj.quaternion.copy(initialQuat).multiply(deltaQuat); + } + }); + }; + + const onMouseUp = () => { + const assetsToUpdate: AssetData[] = []; + const undoActions: UndoRedo3DAction[] = []; + + selectedAssets.forEach((obj) => { + const asset = getAssetById(obj.uuid); + if (!asset) return; + + const initialPos = initialPositionsRef.current.get(obj.uuid); + const initialRot = initialRotationsRef.current.get(obj.uuid); + if (!initialPos || !initialRot) return; + + const assetData: Asset = { + ...asset, + position: [initialPos.x, initialPos.y, initialPos.z], + rotation: [initialRot.x, initialRot.y, initialRot.z], + }; + + const newData: Asset = { + ...asset, + position: [obj.position.x, obj.position.y, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + }; + + assetsToUpdate.push({ + type: "Asset", + assetData, + newData, + timeStap: new Date().toISOString(), + }); + + updateAsset(asset.modelUuid, { + position: [obj.position.x, obj.position.y, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + }); + }); + + 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, + }); + } + + selectedAssets.forEach(obj => { + initialPositionsRef.current.set(obj.uuid, obj.position.clone()); + initialRotationsRef.current.set(obj.uuid, obj.rotation.clone()); + initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone()); + }); + + if (selectedAssets.length > 0) { + const bbox = new THREE.Box3(); + selectedAssets.forEach(obj => bbox.expandByObject(obj)); + bbox.getCenter(pivotPointRef.current); + initialPivotPositionRef.current.copy(pivotPointRef.current); + + tempObjectRef.current.position.copy(pivotPointRef.current); + tempObjectRef.current.rotation.set(0, 0, 0); + } + + recalcGizmo(); + handleTransformationComplete(); + }; + + controls.addEventListener('objectChange', onObjectChange); + controls.addEventListener('mouseUp', onMouseUp); + + return () => { + controls.removeEventListener('objectChange', onObjectChange); + controls.removeEventListener('mouseUp', onMouseUp); + controls.detach(); + }; + }, [selectedAssets, toolMode, visible, handleTransformationComplete]); + + useEffect(() => { + const canvasElement = gl.domElement; + + const handleKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination !== keyEvent) { + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } else { + setKeyEvent(""); + } + } + }; + + const onKeyUp = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === "") { + setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + } + + if (visible) { + canvasElement.addEventListener('keydown', handleKeyDown); + canvasElement.addEventListener('keyup', onKeyUp); + } + + return () => { + canvasElement.removeEventListener('keydown', handleKeyDown); + canvasElement.removeEventListener('keyup', onKeyUp); + } + }, [gl, visible, keyEvent]); + + if (!visible || (toolMode !== 'Move-Asset' && toolMode !== 'Rotate-Asset')) { + return null; + } + + return ( + + ); +} + +export default TransformControls3D; \ No newline at end of file diff --git a/app/src/modules/scene/controls/transformControls/transformControls.tsx b/app/src/modules/scene/controls/transformControls/transformControls.tsx index 415be9b..8428a63 100644 --- a/app/src/modules/scene/controls/transformControls/transformControls.tsx +++ b/app/src/modules/scene/controls/transformControls/transformControls.tsx @@ -1,17 +1,17 @@ -import { TransformControls } from "@react-three/drei"; import * as THREE from "three"; -import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store"; -import { useThree } from "@react-three/fiber"; - -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"; import { useParams } from "react-router-dom"; +import { useThree } from "@react-three/fiber"; +import { TransformControls } from "@react-three/drei"; +import { useEffect, useRef, useState } from "react"; import { useProductContext } from "../../../simulation/products/productContext"; import { getUserData } from "../../../../functions/getUserData"; import { useSceneContext } from "../../sceneContext"; import { useVersionContext } from "../../../builder/version/versionContext"; +import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store"; + +import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys"; +import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi"; +// import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; export default function TransformControl() { const state = useThree(); diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx index 3ca5633..9331320 100644 --- a/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx +++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo3D/undoRedo3DControls.tsx @@ -20,7 +20,7 @@ function UndoRedo3DControls() { const { selectedVersion } = selectedVersionStore(); useEffect(() => { - // console.log(undoStack, redoStack); + console.log(undoStack, redoStack); }, [undoStack, redoStack]); useEffect(() => { diff --git a/app/src/store/builder/useUndoRedo3DStore.ts b/app/src/store/builder/useUndoRedo3DStore.ts index e8c3ce3..d3e2b7b 100644 --- a/app/src/store/builder/useUndoRedo3DStore.ts +++ b/app/src/store/builder/useUndoRedo3DStore.ts @@ -13,6 +13,9 @@ type UndoRedo3DStore = { peekUndo3D: () => UndoRedo3DTypes | undefined; peekRedo3D: () => UndoRedo3DTypes | undefined; + + subscribeUndoRedo: (cb: (action: { type: "undo" | "redo"; entry: UndoRedo3DTypes }) => void) => () => void; + _listeners: ((action: { type: "undo" | "redo"; entry: UndoRedo3DTypes }) => void)[]; }; export const createUndoRedo3DStore = () => { @@ -20,15 +23,14 @@ export const createUndoRedo3DStore = () => { immer((set, get) => ({ undoStack: [], redoStack: [], + _listeners: [], push3D: (entry) => { set((state) => { state.undoStack.push(entry); - if (state.undoStack.length > undoRedoConfig.undoRedoCount) { state.undoStack.shift(); } - state.redoStack = []; }); }, @@ -41,6 +43,9 @@ export const createUndoRedo3DStore = () => { state.redoStack.unshift(lastAction); } }); + if (lastAction) { + get()._listeners.forEach((cb) => cb({ type: "undo", entry: lastAction! })); + } return lastAction; }, @@ -52,6 +57,9 @@ export const createUndoRedo3DStore = () => { state.undoStack.push(redoAction); } }); + if (redoAction) { + get()._listeners.forEach((cb) => cb({ type: "redo", entry: redoAction! })); + } return redoAction; }, @@ -71,8 +79,19 @@ export const createUndoRedo3DStore = () => { const stack = get().redoStack; return stack.length > 0 ? stack[0] : undefined; }, + + subscribeUndoRedo: (cb) => { + set((state) => { + state._listeners.push(cb); + }); + return () => { + set((state) => { + state._listeners = state._listeners.filter((l) => l !== cb); + }); + }; + }, })) ); -} +}; export type UndoRedo3DStoreType = ReturnType; \ No newline at end of file From 06b6b3d0cec4b056d9878514eb26742d2d813c06 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 12:31:27 +0530 Subject: [PATCH 03/19] new TransformControls3D bug fix --- .../selection3D/rotateControls3D.tsx | 19 ++++++++++++++----- .../selection3D/transformControls3D.tsx | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index 66904b2..c4a8d19 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -37,6 +37,7 @@ function RotateControls3D() { const [initialRotations, setInitialRotations] = useState>({}); const [initialPositions, setInitialPositions] = useState>({}); const [isRotating, setIsRotating] = useState(false); + const [isIndividualRotating, setIsIndividualRotating] = useState(false); const prevPointerPosition = useRef(null); const rotationCenter = useRef(null); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); @@ -120,6 +121,12 @@ function RotateControls3D() { } } + if (event.key.toLowerCase() === 'i') { + if (rotatedObjects.length > 0) { + setIsIndividualRotating(!isIndividualRotating); + } + } + if (event.key.toLowerCase() === "escape") { event.preventDefault(); resetToInitialRotations(); @@ -155,7 +162,7 @@ function RotateControls3D() { canvasElement.removeEventListener("keydown", onKeyDown); canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, scene, toggleView, toolMode, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations]); + }, [camera, scene, toggleView, toolMode, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations, isIndividualRotating]); useEffect(() => { if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) { @@ -214,10 +221,12 @@ function RotateControls3D() { 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); + if (!isIndividualRotating) { + 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); + } const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta); obj.quaternion.multiply(rotationQuat); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx index a6aa2bb..3f3162b 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/transformControls3D.tsx @@ -226,11 +226,11 @@ function TransformControls3D() { const delta = new THREE.Vector3().copy(temp.position).sub(initialPivotPositionRef.current); obj.position.copy(initialPos).add(delta); } else if (controls.mode === 'rotate') { - const deltaQuat = new THREE.Quaternion().setFromEuler(temp.rotation); + const deltaQuat = temp.quaternion.clone(); const relPos = initialPos.clone().sub(initialPivotPositionRef.current); relPos.applyQuaternion(deltaQuat); obj.position.copy(initialPivotPositionRef.current).add(relPos); - obj.quaternion.copy(initialQuat).multiply(deltaQuat); + obj.quaternion.copy(deltaQuat).multiply(initialQuat); } }); }; From bb2a27e2f9ecc37625d019357aedaf75a9df02e0 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 12:33:31 +0530 Subject: [PATCH 04/19] added individual rotation to rotation controls --- .../controls/selectionControls/selection3D/rotateControls3D.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index c4a8d19..4f2319f 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -403,6 +403,7 @@ function RotateControls3D() { } setIsRotating(false); + setIsIndividualRotating(false); clearSelection(); }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId, initialPositions, initialRotations]); @@ -412,6 +413,7 @@ function RotateControls3D() { setMovedObjects([]); setRotatedObjects([]); setSelectedAssets([]); + setIsIndividualRotating(false); }; return null; From 0387d7a932d3a474b04f376858303e6f610eaf17 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 13:15:53 +0530 Subject: [PATCH 05/19] file and folder structure changed --- app/src/modules/scene/camera/camMode.tsx | 9 ++- .../{ => functions}/firstPersonCamera.ts | 78 +++++++++---------- .../{ => functions}/switchToFirstPerson.ts | 48 ++++++------ .../{ => functions}/switchToThirdPerson.ts | 56 ++++++------- .../{ => functions}/updateCameraPosition.ts | 52 ++++++------- .../cameraShortcutsControls.tsx} | 37 ++++++--- app/src/modules/scene/controls/controls.tsx | 21 ++--- 7 files changed, 161 insertions(+), 140 deletions(-) rename app/src/modules/scene/camera/{ => functions}/firstPersonCamera.ts (92%) rename app/src/modules/scene/camera/{ => functions}/switchToFirstPerson.ts (91%) rename app/src/modules/scene/camera/{ => functions}/switchToThirdPerson.ts (93%) rename app/src/modules/scene/camera/{ => functions}/updateCameraPosition.ts (88%) rename app/src/{hooks/useCameraShortcuts.ts => modules/scene/camera/shortcutsControls/cameraShortcutsControls.tsx} (50%) diff --git a/app/src/modules/scene/camera/camMode.tsx b/app/src/modules/scene/camera/camMode.tsx index 77e2f31..60d0798 100644 --- a/app/src/modules/scene/camera/camMode.tsx +++ b/app/src/modules/scene/camera/camMode.tsx @@ -1,12 +1,13 @@ import { useFrame, useThree } from "@react-three/fiber"; import React, { useEffect, useState } from "react"; +import { useKeyboardControls } from "@react-three/drei"; import * as CONSTANTS from "../../../types/world/worldConstants"; import { useCamMode, useToggleView } from "../../../store/builder/store"; -import { useKeyboardControls } from "@react-three/drei"; -import switchToThirdPerson from "./switchToThirdPerson"; -import switchToFirstPerson from "./switchToFirstPerson"; + +import switchToThirdPerson from "./functions/switchToThirdPerson"; +import switchToFirstPerson from "./functions/switchToFirstPerson"; import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys"; -import { firstPersonCamera } from "./firstPersonCamera"; +import { firstPersonCamera } from "./functions/firstPersonCamera"; const CamMode: React.FC = () => { const { camMode, setCamMode } = useCamMode(); diff --git a/app/src/modules/scene/camera/firstPersonCamera.ts b/app/src/modules/scene/camera/functions/firstPersonCamera.ts similarity index 92% rename from app/src/modules/scene/camera/firstPersonCamera.ts rename to app/src/modules/scene/camera/functions/firstPersonCamera.ts index 85aacb3..27620f3 100644 --- a/app/src/modules/scene/camera/firstPersonCamera.ts +++ b/app/src/modules/scene/camera/functions/firstPersonCamera.ts @@ -1,39 +1,39 @@ -import * as CONSTANTS from "../../../types/world/worldConstants"; - -interface FirstPersonCameraProps { - setIsTransitioning?: (value: boolean) => void; - state: any; -} - -interface FirstPersonCameraParams extends FirstPersonCameraProps { - camMode: string; - setCamMode: (mode: string) => void; - switchToFirstPerson: (controls: any, camera: any) => Promise; - switchToThirdPerson: (controls: any, camera: any) => Promise; -} - -export async function firstPersonCamera({ - setIsTransitioning, - state, - camMode, - setCamMode, - switchToFirstPerson, - switchToThirdPerson -}: FirstPersonCameraParams): Promise { - setIsTransitioning && setIsTransitioning(true); - - state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse; - state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse; - state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse; - state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse; - - if (camMode === "ThirdPerson") { - setCamMode("FirstPerson"); - await switchToFirstPerson(state.controls, state.camera); - } else if (camMode === "FirstPerson") { - setCamMode("ThirdPerson"); - await switchToThirdPerson(state.controls, state.camera); - } - - setIsTransitioning && setIsTransitioning(false); -} +import * as CONSTANTS from "../../../../types/world/worldConstants"; + +interface FirstPersonCameraProps { + setIsTransitioning?: (value: boolean) => void; + state: any; +} + +interface FirstPersonCameraParams extends FirstPersonCameraProps { + camMode: string; + setCamMode: (mode: string) => void; + switchToFirstPerson: (controls: any, camera: any) => Promise; + switchToThirdPerson: (controls: any, camera: any) => Promise; +} + +export async function firstPersonCamera({ + setIsTransitioning, + state, + camMode, + setCamMode, + switchToFirstPerson, + switchToThirdPerson +}: FirstPersonCameraParams): Promise { + setIsTransitioning && setIsTransitioning(true); + + state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse; + state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse; + state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse; + state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse; + + if (camMode === "ThirdPerson") { + setCamMode("FirstPerson"); + await switchToFirstPerson(state.controls, state.camera); + } else if (camMode === "FirstPerson") { + setCamMode("ThirdPerson"); + await switchToThirdPerson(state.controls, state.camera); + } + + setIsTransitioning && setIsTransitioning(false); +} diff --git a/app/src/modules/scene/camera/switchToFirstPerson.ts b/app/src/modules/scene/camera/functions/switchToFirstPerson.ts similarity index 91% rename from app/src/modules/scene/camera/switchToFirstPerson.ts rename to app/src/modules/scene/camera/functions/switchToFirstPerson.ts index a5371c4..a8a35de 100644 --- a/app/src/modules/scene/camera/switchToFirstPerson.ts +++ b/app/src/modules/scene/camera/functions/switchToFirstPerson.ts @@ -1,25 +1,25 @@ -import * as THREE from 'three'; -import * as CONSTANTS from '../../../types/world/worldConstants'; - -export default async function switchToFirstPerson( - controls: any, - camera: any -) { - if (!controls) return; - - const cameraDirection = new THREE.Vector3(); - camera.getWorldDirection(cameraDirection); - cameraDirection.normalize(); - - await controls.setPosition(camera.position.x, 2, camera.position.z, true); - controls.setTarget(camera.position.x, 2, camera.position.z, true); - controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse; - controls.lockPointer(); - - controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed; - controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed; - controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed; - controls.minDistance = CONSTANTS.firstPersonControls.minDistance; - controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance; - controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle; +import * as THREE from 'three'; +import * as CONSTANTS from '../../../../types/world/worldConstants'; + +export default async function switchToFirstPerson( + controls: any, + camera: any +) { + if (!controls) return; + + const cameraDirection = new THREE.Vector3(); + camera.getWorldDirection(cameraDirection); + cameraDirection.normalize(); + + await controls.setPosition(camera.position.x, 2, camera.position.z, true); + controls.setTarget(camera.position.x, 2, camera.position.z, true); + controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse; + controls.lockPointer(); + + controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed; + controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed; + controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed; + controls.minDistance = CONSTANTS.firstPersonControls.minDistance; + controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance; + controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle; } \ No newline at end of file diff --git a/app/src/modules/scene/camera/switchToThirdPerson.ts b/app/src/modules/scene/camera/functions/switchToThirdPerson.ts similarity index 93% rename from app/src/modules/scene/camera/switchToThirdPerson.ts rename to app/src/modules/scene/camera/functions/switchToThirdPerson.ts index 1e59749..b66adc5 100644 --- a/app/src/modules/scene/camera/switchToThirdPerson.ts +++ b/app/src/modules/scene/camera/functions/switchToThirdPerson.ts @@ -1,29 +1,29 @@ -import * as THREE from 'three'; -import * as CONSTANTS from '../../../types/world/worldConstants'; - -export default async function switchToThirdPerson( - controls: any, - camera: any -) { - if (!controls) return; - controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse; - controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse; - controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse; - controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse; - controls.unlockPointer(); - - const cameraDirection = new THREE.Vector3(); - camera.getWorldDirection(cameraDirection); - const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset); - const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset); - - controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true); - controls.setTarget(targetPosition.x, 0, targetPosition.z, true); - - controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed; - controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed; - controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed; - controls.minDistance = CONSTANTS.threeDimension.minDistance; - controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance; - controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle; +import * as THREE from 'three'; +import * as CONSTANTS from '../../../../types/world/worldConstants'; + +export default async function switchToThirdPerson( + controls: any, + camera: any +) { + if (!controls) return; + controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse; + controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse; + controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse; + controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse; + controls.unlockPointer(); + + const cameraDirection = new THREE.Vector3(); + camera.getWorldDirection(cameraDirection); + const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset); + const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset); + + controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true); + controls.setTarget(targetPosition.x, 0, targetPosition.z, true); + + controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed; + controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed; + controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed; + controls.minDistance = CONSTANTS.threeDimension.minDistance; + controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance; + controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle; } \ No newline at end of file diff --git a/app/src/modules/scene/camera/updateCameraPosition.ts b/app/src/modules/scene/camera/functions/updateCameraPosition.ts similarity index 88% rename from app/src/modules/scene/camera/updateCameraPosition.ts rename to app/src/modules/scene/camera/functions/updateCameraPosition.ts index 26e22ed..fb8c956 100644 --- a/app/src/modules/scene/camera/updateCameraPosition.ts +++ b/app/src/modules/scene/camera/functions/updateCameraPosition.ts @@ -1,26 +1,26 @@ -import { Socket } from "socket.io-client"; -import * as THREE from "three"; -import { getUserData } from "../../../functions/getUserData"; - -export default function updateCamPosition( - controls: any, - socket: Socket, - position: THREE.Vector3, - rotation: THREE.Euler, - projectId?: string -) { - const { userId, organization } = getUserData(); - if (!controls.current) return; - const target = controls.current.getTarget(new THREE.Vector3()); - - const camData = { - organization, - userId: userId, - position: position, - target: new THREE.Vector3(target.x, 0, target.z), - rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z), - socketId: socket.id, - projectId, - }; - socket.emit("v1:Camera:set", camData); -} +import { Socket } from "socket.io-client"; +import * as THREE from "three"; +import { getUserData } from "../../../../functions/getUserData"; + +export default function updateCamPosition( + controls: any, + socket: Socket, + position: THREE.Vector3, + rotation: THREE.Euler, + projectId?: string +) { + const { userId, organization } = getUserData(); + if (!controls.current) return; + const target = controls.current.getTarget(new THREE.Vector3()); + + const camData = { + organization, + userId: userId, + position: position, + target: new THREE.Vector3(target.x, 0, target.z), + rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z), + socketId: socket.id, + projectId, + }; + socket.emit("v1:Camera:set", camData); +} diff --git a/app/src/hooks/useCameraShortcuts.ts b/app/src/modules/scene/camera/shortcutsControls/cameraShortcutsControls.tsx similarity index 50% rename from app/src/hooks/useCameraShortcuts.ts rename to app/src/modules/scene/camera/shortcutsControls/cameraShortcutsControls.tsx index e4b897a..a073e40 100644 --- a/app/src/hooks/useCameraShortcuts.ts +++ b/app/src/modules/scene/camera/shortcutsControls/cameraShortcutsControls.tsx @@ -3,20 +3,24 @@ import { useThree } from "@react-three/fiber"; import * as THREE from "three"; import type { CameraControls } from "@react-three/drei"; -export const useCameraShortcuts = (controlsRef: React.RefObject) => { - const { camera } = useThree(); +const CameraShortcutsControls = () => { + const { camera, controls } = useThree(); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - if (!controlsRef.current) return; + if (!controls) return; - // get current distance from camera to target + const cc = controls as CameraControls; + + // get current target const target = new THREE.Vector3(); - controlsRef.current.getTarget(target); + cc.getTarget(target); const distance = camera.position.distanceTo(target); let pos: THREE.Vector3 | null = null; + const dir = new THREE.Vector3().subVectors(camera.position, target).normalize(); + switch (e.key) { case "1": // Front pos = new THREE.Vector3(0, 0, distance).add(target); @@ -27,13 +31,24 @@ export const useCameraShortcuts = (controlsRef: React.RefObject) case "7": // Top pos = new THREE.Vector3(0, distance, 0).add(target); break; - case "9": // Back - pos = new THREE.Vector3(0, 0, -distance).add(target); + case "9": { + // Opposite view logic + if (Math.abs(dir.z) > Math.abs(dir.x) && Math.abs(dir.z) > Math.abs(dir.y)) { + // Currently looking Front/Back → flip Z + pos = new THREE.Vector3(0, 0, -Math.sign(dir.z) * distance).add(target); + } else if (Math.abs(dir.x) > Math.abs(dir.z) && Math.abs(dir.x) > Math.abs(dir.y)) { + // Currently looking Right/Left → flip X + pos = new THREE.Vector3(-Math.sign(dir.x) * distance, 0, 0).add(target); + } else { + // Currently looking Top/Bottom → stay Top + pos = new THREE.Vector3(0, distance, 0).add(target); + } break; + } } if (pos) { - controlsRef.current.setLookAt( + cc.setLookAt( pos.x, pos.y, pos.z, // camera position target.x, target.y, target.z, // keep same target true // smooth transition @@ -43,5 +58,9 @@ export const useCameraShortcuts = (controlsRef: React.RefObject) window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); - }, [controlsRef, camera]); + }, [controls, camera]); + + return null; }; + +export default CameraShortcutsControls; diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index d63f0ac..a3c9638 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -3,22 +3,22 @@ import { useRef, useEffect } from "react"; import { useThree } from "@react-three/fiber"; import * as THREE from "three"; import * as CONSTANTS from '../../../types/world/worldConstants'; - import { useSocketStore, useToggleView, useResetCamera } from "../../../store/builder/store"; -import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi"; -import updateCamPosition from "../camera/updateCameraPosition"; + import CamMode from "../camera/camMode"; import SwitchView from "../camera/switchView"; -import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D"; -import TransformControl from "./transformControls/transformControls"; -import { useParams } from "react-router-dom"; -import { getUserData } from "../../../functions/getUserData"; - import ContextControls from "./contextControls/contextControls"; +import TransformControl from "./transformControls/transformControls"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; +import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D"; import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls"; -import { useCameraShortcuts } from "../../../hooks/useCameraShortcuts"; +import CameraShortcutsControls from "../camera/shortcutsControls/cameraShortcutsControls"; + +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../functions/getUserData"; +import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi"; +import updateCamPosition from "../camera/functions/updateCameraPosition"; export default function Controls() { const controlsRef = useRef(null); @@ -117,7 +117,6 @@ export default function Controls() { stopInterval(); }; }, [toggleView, state, socket]); - useCameraShortcuts(controlsRef); return ( <> @@ -140,6 +139,8 @@ export default function Controls() { + + From 547fd1af120310f9ca7032d5c84f0a90fbc5c777 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 14:43:38 +0530 Subject: [PATCH 06/19] bug fix in wall, wall Asset, floor , and decal selection and unselection, added decal deletion --- .../components/layout/sidebarLeft/Assets.tsx | 6 +- .../layout/sidebarRight/SideBarRight.tsx | 570 ++++++++---------- .../customInput/RotationInput.tsx | 11 +- .../properties/AssetProperties.tsx | 2 + .../properties/DecalProperties.tsx | 53 -- .../properties/SelectedDecalProperties.tsx | 49 ++ .../modules/builder/Decal/decalInstance.tsx | 117 +++- .../Instances/Instance/floorInstance.tsx | 4 +- .../floor/Instances/floorInstances.tsx | 17 +- app/src/modules/builder/floor/floorGroup.tsx | 6 +- .../builder/wall/Instances/instance/wall.tsx | 20 +- .../builder/wall/Instances/wallInstances.tsx | 19 +- app/src/modules/builder/wall/wallGroup.tsx | 6 +- .../builder/wallAsset/wallAssetCreator.tsx | 6 +- .../builder/wallAsset/wallAssetGroup.tsx | 14 +- app/src/modules/builder/zone/zoneGroup.tsx | 6 +- .../scene/postProcessing/postProcessing.tsx | 37 +- app/src/store/builder/store.ts | 1 + app/src/store/builder/useBuilderStore.ts | 9 + app/src/store/builder/useFloorStore.ts | 20 +- app/src/store/builder/useWallStore.ts | 20 +- 21 files changed, 558 insertions(+), 435 deletions(-) delete mode 100644 app/src/components/layout/sidebarRight/properties/DecalProperties.tsx create mode 100644 app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index c08ef78..2d4aa34 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -127,6 +127,7 @@ const Assets: React.FC = () => { ]; const { selectedSubCategory, setSelectedSubCategory } = useDecalStore(); + return (
@@ -208,9 +209,8 @@ const Assets: React.FC = () => { {activeSubcategories.map((cat, index) => (
setSelectedSubCategory(cat.name)} >
{cat.icon}
diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index 0041aaf..ae9c908 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -1,29 +1,13 @@ import React, { useEffect, useState } from "react"; import Header from "./Header"; -import useModuleStore, { - useSubModuleStore, -} from "../../../store/useModuleStore"; -import { - AnalysisIcon, - FilePackageIcon, - MechanicsIcon, - PropertiesIcon, - SimulationIcon, -} from "../../icons/SimulationIcons"; +import useModuleStore, { useSubModuleStore } from "../../../store/useModuleStore"; +import { AnalysisIcon, FilePackageIcon, MechanicsIcon, PropertiesIcon, SimulationIcon, } from "../../icons/SimulationIcons"; import { useToggleStore } from "../../../store/useUIToggleStore"; import Visualization from "./visualization/Visualization"; import Analysis from "./analysis/Analysis"; import Simulations from "./simulation/Simulations"; -import useVersionHistoryVisibleStore, { - useDecalStore, - useSaveVersion, - useSelectedFloorItem, - useToolMode, -} from "../../../store/builder/store"; -import { - useSelectedEventData, - useSelectedEventSphere, -} from "../../../store/simulation/useSimulationStore"; +import useVersionHistoryVisibleStore, { useDecalStore, useSaveVersion, useSelectedFloorItem, useToolMode, } from "../../../store/builder/store"; +import { useSelectedEventData, useSelectedEventSphere, } from "../../../store/simulation/useSimulationStore"; import { useBuilderStore } from "../../../store/builder/useBuilderStore"; import GlobalProperties from "./properties/GlobalProperties"; import AssetProperties from "./properties/AssetProperties"; @@ -36,318 +20,272 @@ import FloorProperties from "./properties/FloorProperties"; import SelectedWallProperties from "./properties/SelectedWallProperties"; import SelectedFloorProperties from "./properties/SelectedFloorProperties"; import ResourceManagement from "./resourceManagement/ResourceManagement"; -import DecalProperties from "./properties/DecalProperties"; +import SelectedDecalProperties from "./properties/SelectedDecalProperties"; type DisplayComponent = - | "versionHistory" - | "globalProperties" - | "aisleProperties" - | "wallProperties" - | "floorProperties" - | "assetProperties" - | "selectedWallProperties" - | "selectedFloorProperties" - | "zoneProperties" - | "simulations" - | "mechanics" - | "analysis" - | "visualization" - | "selectedDecalProperties" - | "resourceManagement" - | "none"; + | "versionHistory" + | "globalProperties" + | "aisleProperties" + | "wallProperties" + | "floorProperties" + | "assetProperties" + | "selectedWallProperties" + | "selectedFloorProperties" + | "zoneProperties" + | "simulations" + | "mechanics" + | "analysis" + | "visualization" + | "selectedDecalProperties" + | "resourceManagement" + | "none"; const SideBarRight: React.FC = () => { - const { activeModule } = useModuleStore(); - const { toggleUIRight } = useToggleStore(); - const { toolMode } = useToolMode(); - const { subModule, setSubModule } = useSubModuleStore(); - const { selectedFloorItem } = useSelectedFloorItem(); - const { selectedWall, selectedFloor, selectedAisle } = useBuilderStore(); - const { selectedEventData } = useSelectedEventData(); - const { selectedEventSphere } = useSelectedEventSphere(); - const { viewVersionHistory, setVersionHistoryVisible } = - useVersionHistoryVisibleStore(); - const { isVersionSaved } = useSaveVersion(); - const { selectedSubCategory } = useDecalStore(); + const { selectedDecal } = useBuilderStore(); + const { activeModule } = useModuleStore(); + const { toggleUIRight } = useToggleStore(); + const { toolMode } = useToolMode(); + const { subModule, setSubModule } = useSubModuleStore(); + const { selectedFloorItem } = useSelectedFloorItem(); + const { selectedWall, selectedFloor, selectedAisle } = useBuilderStore(); + const { selectedEventData } = useSelectedEventData(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore(); + const { isVersionSaved } = useSaveVersion(); - const [displayComponent, setDisplayComponent] = - useState("none"); + const [displayComponent, setDisplayComponent] = useState("none"); - useEffect(() => { - if (activeModule !== "simulation") setSubModule("properties"); - if (activeModule === "simulation") setSubModule("simulations"); - }, [activeModule, setSubModule]); + useEffect(() => { + if (activeModule !== "simulation") setSubModule("properties"); + if (activeModule === "simulation") setSubModule("simulations"); + }, [activeModule, setSubModule]); - useEffect(() => { - if ( - activeModule !== "mechanics" && - selectedEventData && - selectedEventSphere - ) { - setSubModule("mechanics"); - } else if (!selectedEventData && !selectedEventSphere) { - if (activeModule === "simulation") { - setSubModule("simulations"); - } - } - if (activeModule !== "simulation") { - setSubModule("properties"); - } - }, [activeModule, selectedEventData, selectedEventSphere, setSubModule]); - - useEffect(() => { - if (activeModule === "visualization") { - setDisplayComponent("visualization"); - return; - } - - if (!isVersionSaved && activeModule === "simulation") { - if (subModule === "simulations") { - setDisplayComponent("simulations"); - return; - } - if (subModule === "mechanics") { - setDisplayComponent("mechanics"); - return; - } - if (subModule === "analysis") { - setDisplayComponent("analysis"); - return; - } - if (subModule === "resourceManagement") { - setDisplayComponent("resourceManagement"); - return; - } - } - - if (activeModule === "simulation" || activeModule === "builder") { - if (subModule === "resourceManagement") { - setDisplayComponent("resourceManagement"); - return; - } - } - - if (subModule === "properties" && activeModule !== "visualization") { - if (selectedFloorItem) { - setDisplayComponent("assetProperties"); - return; - } - if ( - !selectedFloorItem && - !selectedFloor && - !selectedAisle && - selectedWall - ) { - setDisplayComponent("selectedWallProperties"); - return; - } - if ( - !selectedFloorItem && - !selectedWall && - !selectedAisle && - selectedFloor - ) { - setDisplayComponent("selectedFloorProperties"); - return; - } - if (viewVersionHistory) { - setDisplayComponent("versionHistory"); - return; - } - if (selectedSubCategory) { - setDisplayComponent("selectedDecalProperties"); - return; - } - if ( - !selectedFloorItem && - !selectedFloor && - !selectedWall && - !selectedSubCategory - ) { - if (toolMode === "Aisle") { - setDisplayComponent("aisleProperties"); - return; + useEffect(() => { + if (activeModule !== "mechanics" && selectedEventData && selectedEventSphere) { + setSubModule("mechanics"); + } else if (!selectedEventData && !selectedEventSphere) { + if (activeModule === "simulation") { + setSubModule("simulations"); + } } - if (toolMode === "Wall") { - setDisplayComponent("wallProperties"); - return; + if (activeModule !== "simulation") { + setSubModule("properties"); } - if (toolMode === "Floor") { - setDisplayComponent("floorProperties"); - return; + }, [activeModule, selectedEventData, selectedEventSphere, setSubModule]); + + useEffect(() => { + if (activeModule === "visualization") { + setDisplayComponent("visualization"); + return; } - setDisplayComponent("globalProperties"); - return; - } - } - if ( - subModule === "zoneProperties" && - (activeModule === "builder" || activeModule === "simulation") - ) { - setDisplayComponent("zoneProperties"); - return; - } + if (!isVersionSaved && activeModule === "simulation") { + if (subModule === "simulations") { + setDisplayComponent("simulations"); + return; + } + if (subModule === "mechanics") { + setDisplayComponent("mechanics"); + return; + } + if (subModule === "analysis") { + setDisplayComponent("analysis"); + return; + } + if (subModule === "resourceManagement") { + setDisplayComponent("resourceManagement"); + return; + } + } - setDisplayComponent("none"); - }, [ - viewVersionHistory, - activeModule, - subModule, - isVersionSaved, - selectedFloorItem, - selectedWall, - selectedFloor, - selectedAisle, - toolMode, - selectedSubCategory, - ]); + if (activeModule === "simulation" || activeModule === "builder") { + if (subModule === "resourceManagement") { + setDisplayComponent("resourceManagement"); + return; + } + } - const renderComponent = () => { - switch (displayComponent) { - case "versionHistory": - return ; - case "globalProperties": - return ; - case "aisleProperties": - return ; - case "wallProperties": - return ; - case "floorProperties": - return ; - case "assetProperties": - return ; - case "selectedWallProperties": - return ; - case "selectedFloorProperties": - return ; - case "zoneProperties": - return ; - case "simulations": - return ; - case "mechanics": - return ; - case "analysis": - return ; - case "visualization": - return ; - case "selectedDecalProperties": - return ; - case "resourceManagement": - return ; - default: - return null; - } - }; + if (subModule === "properties" && activeModule !== "visualization") { + if (selectedFloorItem) { + setDisplayComponent("assetProperties"); + return; + } + if (!selectedFloorItem && !selectedFloor && !selectedAisle && selectedWall) { + setDisplayComponent("selectedWallProperties"); + return; + } + if (!selectedFloorItem && !selectedWall && !selectedAisle && selectedFloor) { + setDisplayComponent("selectedFloorProperties"); + return; + } + if (viewVersionHistory) { + setDisplayComponent("versionHistory"); + return; + } + if (selectedDecal) { + setDisplayComponent("selectedDecalProperties"); + return; + } + if (!selectedFloorItem && !selectedFloor && !selectedWall && !selectedDecal) { + if (toolMode === "Aisle") { + setDisplayComponent("aisleProperties"); + return; + } + if (toolMode === "Wall") { + setDisplayComponent("wallProperties"); + return; + } + if (toolMode === "Floor") { + setDisplayComponent("floorProperties"); + return; + } + setDisplayComponent("globalProperties"); + return; + } + } - return ( -
-
- {toggleUIRight && ( - <> - {(!isVersionSaved || activeModule !== "simulation") && ( -
- {activeModule !== "simulation" && ( + if (subModule === "zoneProperties" && (activeModule === "builder" || activeModule === "simulation")) { + setDisplayComponent("zoneProperties"); + return; + } + + setDisplayComponent("none"); + }, [viewVersionHistory, activeModule, subModule, isVersionSaved, selectedFloorItem, selectedWall, selectedFloor, selectedAisle, toolMode, selectedDecal]); + + const renderComponent = () => { + switch (displayComponent) { + case "versionHistory": + return ; + case "globalProperties": + return ; + case "aisleProperties": + return ; + case "wallProperties": + return ; + case "floorProperties": + return ; + case "assetProperties": + return ; + case "selectedWallProperties": + return ; + case "selectedFloorProperties": + return ; + case "zoneProperties": + return ; + case "simulations": + return ; + case "mechanics": + return ; + case "analysis": + return ; + case "visualization": + return ; + case "selectedDecalProperties": + return ; + case "resourceManagement": + return ; + default: + return null; + } + }; + + return ( +
+
+ {toggleUIRight && ( <> - + {(!isVersionSaved || activeModule !== "simulation") && ( +
+ {activeModule !== "simulation" && ( + <> + + + )} + + {activeModule === "simulation" && ( + <> + + + + + )} + + {(activeModule === "builder" || + activeModule === "simulation") && ( + + )} +
+ )} + + {displayComponent !== "none" && ( +
+
+ {renderComponent()} + {/* */} +
+
+ )} - )} - - {activeModule === "simulation" && ( - <> - - - - - )} - - {(activeModule === "builder" || - activeModule === "simulation") && ( - - )} -
- )} - - {displayComponent !== "none" && ( -
-
- {renderComponent()} - {/* */} -
-
- )} - - )} -
- ); + )} +
+ ); }; export default SideBarRight; diff --git a/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx b/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx index 962e967..ccddafa 100644 --- a/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx +++ b/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx @@ -1,30 +1,37 @@ import React from "react"; interface RotationInputProps { + heading?: string; // Optional label for the input + label?: string; // Optional label for the input onChange: (value: string) => void; // Callback for value change placeholder?: string; // Optional placeholder type?: string; // Input type (e.g., text, number, email) value?: number; + disabled?: boolean; // Disable the input if true } const RotationInput: React.FC = ({ + label = "Rotate :", // Default label + heading = "Rotation", // Default heading onChange, placeholder = "Enter value", // Default placeholder type = "number", // Default type value = "number", + disabled = false, }) => { return (
-
Rotation
+
{heading}
-
Rotate :
+
{label}
onChange(e.target.value)} placeholder={placeholder} value={value} + disabled={disabled} />
diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 9c8b979..3a36e6c 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -57,6 +57,7 @@ const AssetProperties: React.FC = () => {
{objectPosition && ( { }} value1={parseFloat(objectPosition.x.toFixed(5))} value2={parseFloat(objectPosition.z.toFixed(5))} @@ -64,6 +65,7 @@ const AssetProperties: React.FC = () => { )} {objectRotation && ( { }} value={parseFloat(objectRotation.y.toFixed(5))} /> diff --git a/app/src/components/layout/sidebarRight/properties/DecalProperties.tsx b/app/src/components/layout/sidebarRight/properties/DecalProperties.tsx deleted file mode 100644 index 4163ea5..0000000 --- a/app/src/components/layout/sidebarRight/properties/DecalProperties.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { - LayeringBottomIcon, - LayeringTopIcon, -} from "../../../icons/ExportCommonIcons"; -import InputRange from "../../../ui/inputs/InputRange"; -import RotationInput from "../customInput/RotationInput"; -import Vector3Input from "../customInput/Vector3Input"; - -const DecalProperties = () => { - return ( -
-
Decal Propertis
-
- {}} - value={10} - /> - console.log(value)} - header="Scale" - value={[0, 0, 0] as [number, number, number]} - /> -
- -
- console.log(value)} - key={"6"} - /> - -
-
Layering
- -
- - -
-
-
-
- ); -}; - -export default DecalProperties; diff --git a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx new file mode 100644 index 0000000..1db536b --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx @@ -0,0 +1,49 @@ +import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons"; +import InputRange from "../../../ui/inputs/InputRange"; +import RotationInput from "../customInput/RotationInput"; + +const SelectedDecalProperties = () => { + return ( +
+
Decal Properties
+
+ { }} + value={10} + /> + { }} + value={10} + /> +
+ +
+ console.log(value)} + /> + +
+
Layering
+ +
+ + +
+
+
+
+ ); +}; + +export default SelectedDecalProperties; diff --git a/app/src/modules/builder/Decal/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance.tsx index 1b68f3d..69b0c7c 100644 --- a/app/src/modules/builder/Decal/decalInstance.tsx +++ b/app/src/modules/builder/Decal/decalInstance.tsx @@ -1,33 +1,132 @@ import * as THREE from 'three'; import { Decal } from '@react-three/drei' import { useLoader } from '@react-three/fiber'; -import { useToggleView } from '../../../store/builder/store'; +import { useSocketStore, useToggleView, useToolMode } from '../../../store/builder/store'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import defaultMaterial from '../../../assets/textures/floor/wall-tex.png'; import useModuleStore from '../../../store/useModuleStore'; +import { useSceneContext } from '../../scene/sceneContext'; +import { useEffect } from 'react'; +import { getUserData } from '../../../functions/getUserData'; +import { useVersionContext } from '../version/versionContext'; +import { useParams } from 'react-router-dom'; -function DecalInstance({ visible = true, decal, zPosition = decal.decalPosition[2] }: { visible?: boolean, decal: Decal, zPosition?: number }) { - const { setSelectedWall, setSelectedFloor, selectedDecal, setSelectedDecal } = useBuilderStore(); - const { togglView } = useToggleView(); +// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; +// import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi'; + +function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalPosition[2] }: { parent: Wall | Floor; visible?: boolean, decal: Decal, zPosition?: number }) { + const { setSelectedWall, setSelectedFloor, selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore(); + const { wallStore, floorStore } = useSceneContext(); + const { removeDecal: removeDecalFromWall } = wallStore(); + const { removeDecal: removeDecalFromFloor } = floorStore(); + const { toolMode } = useToolMode(); + const { toggleView } = useToggleView(); const { activeModule } = useModuleStore(); + const { userId, organization } = getUserData(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); const material = useLoader(THREE.TextureLoader, defaultMaterial); + useEffect(() => { + if (!toggleView && activeModule === 'builder') { + if (toolMode !== 'cursor') { + if (selectedDecal) setSelectedDecal(null); + } + if (toolMode !== '3D-Delete') { + if (deletableDecal) setDeletableDecal(null); + } + } else { + if (selectedDecal) setSelectedDecal(null); + if (deletableDecal) setDeletableDecal(null); + } + }, [toggleView, toolMode, activeModule, selectedDecal, deletableDecal]); + + const deleteDecal = (decalUuid: string, parent: Wall | Floor) => { + if ('wallUuid' in parent) { + const updatedWall = removeDecalFromWall(decalUuid); + + if (projectId && updatedWall) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + } else if ('floorUuid' in parent) { + const updatedFloor = removeDecalFromFloor(decalUuid); + + if (projectId && updatedFloor) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); + + // SOCKET + + const data = { + floorData: updatedFloor, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + } + } + return ( { - if (visible && !togglView && activeModule === 'builder') { + if (visible && !toggleView && activeModule === 'builder') { if (e.object.userData.decalUuid) { e.stopPropagation(); - setSelectedDecal(e.object); - setSelectedWall(null); - setSelectedFloor(null); + if (toolMode === 'cursor') { + setSelectedDecal(e.object); + setSelectedWall(null); + setSelectedFloor(null); + } else if (toolMode === '3D-Delete') { + deleteDecal(e.object.userData.decalUuid, parent); + } + } + } + }} + onPointerEnter={(e) => { + if (visible && !toggleView && activeModule === 'builder') { + if (e.object.userData.decalUuid) { + e.stopPropagation(); + if (toolMode === '3D-Delete') { + setDeletableDecal(e.object); + } + } + } + }} + onPointerLeave={(e) => { + if (visible && !toggleView && activeModule === 'builder') { + if (e.object.userData.decalUuid) { + e.stopPropagation(); + if (toolMode === '3D-Delete' && deletableDecal && deletableDecal?.userData.decalUuid === e.object.userData.decalUuid) { + setDeletableDecal(null); + } } } }} diff --git a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx index aa60691..f89b38d 100644 --- a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx +++ b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx @@ -28,7 +28,7 @@ import material4MetalicMap from "../../../../../assets/textures/floor/tex3/metal import material4NormalMap from "../../../../../assets/textures/floor/tex3/metal_plate_nor_gl_1k.png"; function FloorInstance({ floor }: { floor: Floor }) { - const { togglView } = useToggleView(); + const { toggleView } = useToggleView(); const { activeModule } = useModuleStore(); const { selectedFloor, setSelectedFloor, setSelectedDecal } = useBuilderStore(); const savedTheme = localStorage.getItem("theme"); @@ -159,7 +159,7 @@ function FloorInstance({ floor }: { floor: Floor }) { position={[0, !floor.isBeveled ? floor.floorDepth - 0.1 : floor.floorDepth - 0.2, 0,]} userData={floor} onDoubleClick={(e) => { - if (!togglView && activeModule === "builder") { + if (!toggleView && activeModule === "builder") { if (e.object.userData.floorUuid) { e.stopPropagation(); setSelectedFloor(e.object); diff --git a/app/src/modules/builder/floor/Instances/floorInstances.tsx b/app/src/modules/builder/floor/Instances/floorInstances.tsx index a578800..2af5787 100644 --- a/app/src/modules/builder/floor/Instances/floorInstances.tsx +++ b/app/src/modules/builder/floor/Instances/floorInstances.tsx @@ -2,21 +2,36 @@ import React, { useEffect, useMemo } from 'react'; import { Vector3 } from 'three'; import { Html } from '@react-three/drei'; import { useSceneContext } from '../../../scene/sceneContext'; -import { useToggleView } from '../../../../store/builder/store'; +import { useToggleView, useToolMode } from '../../../../store/builder/store'; import Line from '../../line/line'; import Point from '../../point/point'; import FloorInstance from './Instance/floorInstance'; import Floor2DInstance from './Instance/floor2DInstance'; +import useModuleStore from '../../../../store/useModuleStore'; +import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; function FloorInstances() { const { floorStore } = useSceneContext(); const { floors } = floorStore(); + const { setSelectedFloor, selectedFloor } = useBuilderStore(); + const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); useEffect(() => { // console.log('floors: ', floors); }, [floors]) + useEffect(() => { + if (!toggleView && activeModule === 'builder') { + if (toolMode !== 'cursor') { + if (selectedFloor) setSelectedFloor(null); + } + } else { + if (selectedFloor) setSelectedFloor(null); + } + }, [toggleView, toolMode, activeModule, selectedFloor]); + const allPoints = useMemo(() => { const points: Point[] = []; const seenUuids = new Set(); diff --git a/app/src/modules/builder/floor/floorGroup.tsx b/app/src/modules/builder/floor/floorGroup.tsx index 47ea3b5..d782e1a 100644 --- a/app/src/modules/builder/floor/floorGroup.tsx +++ b/app/src/modules/builder/floor/floorGroup.tsx @@ -10,7 +10,7 @@ import FloorInstances from './Instances/floorInstances'; import { getFloorsApi } from '../../../services/factoryBuilder/floor/getFloorsApi'; function FloorGroup() { - const { togglView } = useToggleView(); + const { toggleView } = useToggleView(); const { setSelectedFloor, setSelectedDecal } = useBuilderStore(); const { activeModule } = useModuleStore(); const { activeTool } = useActiveTool(); @@ -21,11 +21,11 @@ function FloorGroup() { const { projectId } = useParams(); useEffect(() => { - if (togglView || activeModule !== 'builder') { + if (toggleView || activeModule !== 'builder') { setSelectedFloor(null); setSelectedDecal(null); } - }, [togglView, activeModule, activeTool]) + }, [toggleView, activeModule, activeTool]) useEffect(() => { if (projectId && selectedVersion) { diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index 8798b01..939dc78 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -17,11 +17,11 @@ import material1 from '../../../../../assets/textures/floor/factory wall texture function Wall({ wall }: { readonly wall: Wall }) { const { wallStore, wallAssetStore } = useSceneContext(); - const { walls } = wallStore(); + const { walls, addDecal } = wallStore(); const { wallAssets, getWallAssetsByWall, setVisibility } = wallAssetStore(); const assets = getWallAssetsByWall(wall.wallUuid); const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore(); - const { togglView } = useToggleView(); + const { toggleView } = useToggleView(); const { activeModule } = useModuleStore(); const { camera } = useThree(); const { wallVisibility } = useWallVisibility(); @@ -154,11 +154,23 @@ function Wall({ wall }: { readonly wall: Wall }) { userData={wall} name={`WallReference_${wall.wallUuid}`} onDoubleClick={(e) => { - if (visible && !togglView && activeModule === 'builder') { + if (visible && !toggleView && activeModule === 'builder') { if (e.object.userData.wallUuid) { e.stopPropagation(); setSelectedWall(e.object); setSelectedDecal(null); + + if (wall.decals.length > 0) return; + const decal: Decal = { + decalUuid: THREE.MathUtils.generateUUID(), + decalName: 'Decal', + decalId: 'Default Decal', + decalPosition: [0, 0, wall.wallThickness / 2 + 0.001], + decalRotation: 0, + decalScale: 1, + decalType: { type: 'Wall', wallUuid: wall.wallUuid } + } + addDecal(wall.wallUuid, decal); } } }} @@ -171,7 +183,7 @@ function Wall({ wall }: { readonly wall: Wall }) { {wall.decals.map((decal) => ( - + ))} diff --git a/app/src/modules/builder/wall/Instances/wallInstances.tsx b/app/src/modules/builder/wall/Instances/wallInstances.tsx index 254705a..a67e3a3 100644 --- a/app/src/modules/builder/wall/Instances/wallInstances.tsx +++ b/app/src/modules/builder/wall/Instances/wallInstances.tsx @@ -2,8 +2,9 @@ import React, { useEffect, useMemo } from 'react'; import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three'; import { Html, Extrude } from '@react-three/drei'; import { useLoader } from '@react-three/fiber'; +import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useSceneContext } from '../../../scene/sceneContext'; -import { useToggleView } from '../../../../store/builder/store'; +import { useToggleView, useToolMode } from '../../../../store/builder/store'; import { useWallClassification } from './instance/helpers/useWallClassification'; import Line from '../../line/line'; import Point from '../../point/point'; @@ -12,17 +13,31 @@ import * as Constants from '../../../../types/world/worldConstants'; import texturePath from "../../../../assets/textures/floor/white.png"; import texturePathDark from "../../../../assets/textures/floor/black.png"; +import useModuleStore from '../../../../store/useModuleStore'; function WallInstances() { const { wallStore } = useSceneContext(); + const { setSelectedWall, selectedWall } = useBuilderStore(); + const { toolMode } = useToolMode(); + const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); const { walls } = wallStore(); const { rooms } = useWallClassification(walls); - const { toggleView } = useToggleView(); useEffect(() => { // console.log('walls: ', walls); }, [walls]) + useEffect(() => { + if (!toggleView && activeModule === 'builder') { + if (toolMode !== 'cursor') { + if (selectedWall) setSelectedWall(null); + } + } else { + if (selectedWall) setSelectedWall(null); + } + }, [toggleView, toolMode, activeModule, selectedWall]); + const allPoints = useMemo(() => { const points: Point[] = []; const seenUuids = new Set(); diff --git a/app/src/modules/builder/wall/wallGroup.tsx b/app/src/modules/builder/wall/wallGroup.tsx index 084b969..3561751 100644 --- a/app/src/modules/builder/wall/wallGroup.tsx +++ b/app/src/modules/builder/wall/wallGroup.tsx @@ -11,7 +11,7 @@ import WallInstances from './Instances/wallInstances'; import { getWallsApi } from '../../../services/factoryBuilder/wall/getWallsApi'; function WallGroup() { - const { togglView } = useToggleView(); + const { toggleView } = useToggleView(); const { setSelectedWall, setSelectedDecal } = useBuilderStore(); const { activeModule } = useModuleStore(); const { activeTool } = useActiveTool(); @@ -22,11 +22,11 @@ function WallGroup() { const { projectId } = useParams(); useEffect(() => { - if (togglView || activeModule !== 'builder') { + if (toggleView || activeModule !== 'builder') { setSelectedWall(null); setSelectedDecal(null); } - }, [togglView, activeModule, activeTool]) + }, [toggleView, activeModule, activeTool]) useEffect(() => { if (projectId && selectedVersion) { diff --git a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx index fb95236..13438c2 100644 --- a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx +++ b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx @@ -14,7 +14,7 @@ import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegm function WallAssetCreator() { const { socket } = useSocketStore(); const { pointer, camera, raycaster, scene, gl } = useThree(); - const { togglView } = useToggleView(); + const { toggleView } = useToggleView(); const { activeModule } = useModuleStore(); const { wallAssetStore } = useSceneContext(); const { addWallAsset } = wallAssetStore(); @@ -84,7 +84,7 @@ function WallAssetCreator() { } }; - if (!togglView && activeModule === 'builder') { + if (!toggleView && activeModule === 'builder') { canvasElement.addEventListener('drop', onDrop); } @@ -92,7 +92,7 @@ function WallAssetCreator() { canvasElement.removeEventListener('drop', onDrop); }; - }, [gl, camera, togglView, activeModule, socket, selectedItem, setSelectedItem]); + }, [gl, camera, toggleView, activeModule, socket, selectedItem, setSelectedItem]); return ( <> diff --git a/app/src/modules/builder/wallAsset/wallAssetGroup.tsx b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx index bae9f87..95e2e90 100644 --- a/app/src/modules/builder/wallAsset/wallAssetGroup.tsx +++ b/app/src/modules/builder/wallAsset/wallAssetGroup.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useActiveTool, useToggleView } from '../../../store/builder/store'; +import { useToggleView, useToolMode } from '../../../store/builder/store'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useVersionContext } from '../version/versionContext'; import { useSceneContext } from '../../scene/sceneContext'; @@ -10,10 +10,10 @@ import WallAssetInstances from './Instances/wallAssetInstances' import { getWallAssetsApi } from '../../../services/factoryBuilder/asset/wallAsset/getWallAssetsApi'; function WallAssetGroup() { - const { togglView } = useToggleView(); - const { setSelectedFloorAsset, setDeletableWallAsset } = useBuilderStore(); + const { toggleView } = useToggleView(); + const { setSelectedWallAsset, setDeletableWallAsset } = useBuilderStore(); const { activeModule } = useModuleStore(); - const { activeTool } = useActiveTool(); + const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { wallAssetStore } = useSceneContext(); @@ -21,11 +21,11 @@ function WallAssetGroup() { const { projectId } = useParams(); useEffect(() => { - if (togglView || activeModule !== 'builder') { - setSelectedFloorAsset(null); + if (toggleView || activeModule !== 'builder' || toolMode !== 'cursor') { + setSelectedWallAsset(null); } setDeletableWallAsset(null); - }, [togglView, activeModule, activeTool]) + }, [toggleView, activeModule, toolMode]) useEffect(() => { if (projectId && selectedVersion) { diff --git a/app/src/modules/builder/zone/zoneGroup.tsx b/app/src/modules/builder/zone/zoneGroup.tsx index c44bf09..08d3e9c 100644 --- a/app/src/modules/builder/zone/zoneGroup.tsx +++ b/app/src/modules/builder/zone/zoneGroup.tsx @@ -11,7 +11,7 @@ import ZoneInstances from './Instances/zoneInstances'; import { getZonesApi } from '../../../services/factoryBuilder/zone/getZonesApi'; function ZoneGroup() { - const { togglView } = useToggleView(); + const { toggleView } = useToggleView(); const { setSelectedZone } = useBuilderStore(); const { activeModule } = useModuleStore(); const { activeTool } = useActiveTool(); @@ -22,10 +22,10 @@ function ZoneGroup() { const { projectId } = useParams(); useEffect(() => { - if (togglView || activeModule !== 'builder') { + if (toggleView || activeModule !== 'builder') { setSelectedZone(null); } - }, [togglView, activeModule, activeTool]) + }, [toggleView, activeModule, activeTool]) useEffect(() => { if (projectId && selectedVersion) { diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 8aacd96..5558f9d 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -1,25 +1,19 @@ -import { DepthOfField, Bloom, EffectComposer, N8AO, Outline } from "@react-three/postprocessing"; -import { useThree } from "@react-three/fiber"; -import { BlendFunction } from "postprocessing"; -import { - useDeletableFloorItem, - useSelectedWallItem, - useSelectedFloorItem, -} from "../../../store/builder/store"; -import * as CONSTANTS from "../../../types/world/worldConstants"; -import { useDeletableEventSphere, useSelectedEventSphere, useSelectedPoints } from "../../../store/simulation/useSimulationStore"; import { useEffect } from "react"; +import { BlendFunction } from "postprocessing"; +import { DepthOfField, Bloom, EffectComposer, N8AO, Outline } from "@react-three/postprocessing"; +import { useDeletableFloorItem, useSelectedWallItem, useSelectedFloorItem, } from "../../../store/builder/store"; +import { useDeletableEventSphere, useSelectedEventSphere, useSelectedPoints } from "../../../store/simulation/useSimulationStore"; import { useBuilderStore } from "../../../store/builder/useBuilderStore"; +import * as CONSTANTS from "../../../types/world/worldConstants"; export default function PostProcessing() { - const { scene } = useThree(); const { selectedPoints } = useSelectedPoints(); const { deletableFloorItem } = useDeletableFloorItem(); const { selectedWallItem } = useSelectedWallItem(); const { selectedFloorItem } = useSelectedFloorItem(); const { selectedEventSphere } = useSelectedEventSphere(); const { deletableEventSphere } = useDeletableEventSphere(); - const { selectedAisle, selectedWall, selectedDecal, selectedFloor, selectedWallAsset, deletableWallAsset } = useBuilderStore(); + const { selectedAisle, selectedWall, selectedDecal, selectedFloor, selectedWallAsset, deletableWallAsset, deletableDecal } = useBuilderStore(); function flattenChildren(children: any[]) { const allChildren: any[] = []; @@ -68,6 +62,10 @@ export default function PostProcessing() { // console.log('selectedPoints: ', selectedPoints); }, [selectedPoints]) + useEffect(() => { + // console.log('deletableDecal: ', deletableDecal); + }, [deletableDecal]) + return ( )} + {deletableDecal && ( + + )} {deletableFloorItem && ( ((set: any, get: any) => ({ // }); // }, // })); + export const useLoadingProgress = create<{ loadingProgress: number; setLoadingProgress: (x: number) => void; diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 5be4e3b..d6176f7 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -39,6 +39,7 @@ interface BuilderState { // Decal Settings selectedDecal: Object3D | null; + deletableDecal: Object3D | null; // Aisle General selectedAisle: Object3D | null; @@ -87,6 +88,7 @@ interface BuilderState { // Setters - Decal setSelectedDecal: (decal: Object3D | null) => void; + setDeletableDecal: (decal: Object3D | null) => void; // Setters - Aisle General setSelectedAisle: (aisle: Object3D | null) => void; @@ -140,6 +142,7 @@ export const useBuilderStore = create()( zoneColor: 'blue', selectedDecal: null, + deletableDecal: null, selectedAisle: null, aisleType: 'solid-aisle', @@ -293,6 +296,12 @@ export const useBuilderStore = create()( }) }, + setDeletableDecal: (decal: Object3D | null) => { + set((state) => { + state.deletableDecal = decal; + }) + }, + // === Setters: Aisle General === setSelectedAisle: (aisle: Object3D | null) => { diff --git a/app/src/store/builder/useFloorStore.ts b/app/src/store/builder/useFloorStore.ts index a045884..6f488a5 100644 --- a/app/src/store/builder/useFloorStore.ts +++ b/app/src/store/builder/useFloorStore.ts @@ -21,7 +21,7 @@ interface FloorStore { setMaterial: (uuid: string, sideMaterial: string, topMaterial: string) => void; addDecal: (floors: string, decal: Decal) => void; updateDecal: (decalUuid: string, decal: Decal) => void; - removeDecal: (decalUuid: string) => void; + removeDecal: (decalUuid: string) => Floor | undefined; updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void; updateDecalRotation: (decalUuid: string, rotation: number) => void; updateDecalScale: (decalUuid: string, scale: number) => void; @@ -213,11 +213,19 @@ export const createFloorStore = () => { } }), - removeDecal: (decalUuid) => set(state => { - for (const floor of state.floors) { - floor.decals = floor.decals.filter(d => d.decalUuid !== decalUuid); - } - }), + removeDecal: (decalUuid) => { + let affectedFloor: Floor | undefined; + set(state => { + for (const floor of state.floors) { + const hasDecal = floor.decals.some(d => d.decalUuid === decalUuid); + if (hasDecal) { + floor.decals = floor.decals.filter(d => d.decalUuid !== decalUuid); + affectedFloor = JSON.parse(JSON.stringify(floor)); + } + } + }); + return affectedFloor; + }, updateDecalPosition: (decalUuid, position) => set(state => { for (const floor of state.floors) { diff --git a/app/src/store/builder/useWallStore.ts b/app/src/store/builder/useWallStore.ts index db40269..92b6fe5 100644 --- a/app/src/store/builder/useWallStore.ts +++ b/app/src/store/builder/useWallStore.ts @@ -11,7 +11,7 @@ interface WallStore { removeWallByPoints: (Points: [Point, Point]) => Wall | undefined; addDecal: (wallUuid: string, decal: Decal) => void; updateDecal: (decalUuid: string, decal: Decal) => void; - removeDecal: (decalUuid: string) => void; + removeDecal: (decalUuid: string) => Wall | undefined; updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void; updateDecalRotation: (decalUuid: string, rotation: number) => void; updateDecalScale: (decalUuid: string, scale: number) => void; @@ -99,11 +99,19 @@ export const createWallStore = () => { } }), - removeDecal: (decalUuid) => set((state) => { - for (const wall of state.walls) { - wall.decals = wall.decals.filter(d => d.decalUuid !== decalUuid); - } - }), + removeDecal: (decalUuid) => { + let affectedWall: Wall | undefined; + set((state) => { + for (const wall of state.walls) { + const hasDecal = wall.decals.some(d => d.decalUuid === decalUuid); + if (hasDecal) { + wall.decals = wall.decals.filter(d => d.decalUuid !== decalUuid); + affectedWall = JSON.parse(JSON.stringify(wall)); + } + } + }); + return affectedWall; + }, updateDecalPosition: (decalUuid, position) => set((state) => { for (const wall of state.walls) { From 165325468a819e5fba7b5ef62b6e96c210e13af5 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 15:50:02 +0530 Subject: [PATCH 07/19] added ui fro and iintegerated ui for decal modification --- .../customInput/RotationInput.tsx | 31 ++-- .../properties/SelectedDecalProperties.tsx | 154 +++++++++++++++++- .../modules/builder/Decal/decalInstance.tsx | 8 +- .../builder/wall/Instances/instance/wall.tsx | 3 +- .../scene/postProcessing/postProcessing.tsx | 2 +- app/src/store/builder/useBuilderStore.ts | 6 +- app/src/store/builder/useFloorStore.ts | 23 ++- app/src/store/builder/useWallStore.ts | 21 ++- app/src/types/builderTypes.d.ts | 1 + 9 files changed, 204 insertions(+), 45 deletions(-) diff --git a/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx b/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx index ccddafa..1265d81 100644 --- a/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx +++ b/app/src/components/layout/sidebarRight/customInput/RotationInput.tsx @@ -1,23 +1,29 @@ import React from "react"; interface RotationInputProps { - heading?: string; // Optional label for the input - label?: string; // Optional label for the input - onChange: (value: string) => void; // Callback for value change - placeholder?: string; // Optional placeholder - type?: string; // Input type (e.g., text, number, email) + heading?: string; + label?: string; + onChange: (value: string) => void; + placeholder?: string; + type?: string; value?: number; - disabled?: boolean; // Disable the input if true + disabled?: boolean; + min?: number; + max?: number; + step?: number; } const RotationInput: React.FC = ({ - label = "Rotate :", // Default label - heading = "Rotation", // Default heading + label = "Rotate :", + heading = "Rotation", onChange, - placeholder = "Enter value", // Default placeholder - type = "number", // Default type - value = "number", + placeholder = "Enter value", + type = "number", + value, disabled = false, + min, + max, + step, }) => { return (
@@ -32,6 +38,9 @@ const RotationInput: React.FC = ({ placeholder={placeholder} value={value} disabled={disabled} + min={min} + max={max} + step={step} />
diff --git a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx index 1db536b..7994a1f 100644 --- a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx @@ -1,42 +1,178 @@ +import { useParams } from "react-router-dom"; +import { useVersionContext } from "../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../modules/scene/sceneContext"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons"; +import { useSocketStore } from "../../../../store/builder/store"; import InputRange from "../../../ui/inputs/InputRange"; import RotationInput from "../customInput/RotationInput"; +import { getUserData } from "../../../../functions/getUserData"; +// import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi"; +// import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi"; + const SelectedDecalProperties = () => { + const { selectedDecal, setSelectedDecal } = useBuilderStore(); + const { wallStore, floorStore } = useSceneContext(); + const { updateDecal: updateDecalFromWall } = wallStore(); + const { updateDecal: updateDecalFromFloor } = floorStore(); + const { userId, organization } = getUserData(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); + + const updateBackend = (updatedData: Wall | Floor) => { + if ('wallUuid' in updatedData) { + if (projectId && updatedData) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + wallData: updatedData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + } else if ('floorUuid' in updatedData) { + if (projectId && updatedData) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData); + + // SOCKET + + const data = { + floorData: updatedData, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + } + } + + const handleRotationChange = (value: number) => { + if (!selectedDecal) return; + const updatedDecal = { ...selectedDecal.decalData, decalRotation: value }; + setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); + + if ('wallUuid' in selectedDecal.decalData.decalType) { + const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + if (updatedWall) updateBackend(updatedWall); + } else if ('floorUuid' in selectedDecal.decalData.decalType) { + const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + if (updatedFloor) updateBackend(updatedFloor); + } + } + + const handleScaleChange = (value: number) => { + if (!selectedDecal) return; + const updatedDecal = { ...selectedDecal.decalData, decalScale: value }; + setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); + + if ('wallUuid' in selectedDecal.decalData.decalType) { + const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + if (updatedWall) updateBackend(updatedWall); + } else if ('floorUuid' in selectedDecal.decalData.decalType) { + const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + if (updatedFloor) updateBackend(updatedFloor); + } + } + + const handleOpacityChange = (value: number) => { + if (!selectedDecal) return; + const updatedDecal = { ...selectedDecal.decalData, decalOpacity: value }; + setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); + + if ('wallUuid' in selectedDecal.decalData.decalType) { + const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + if (updatedWall) updateBackend(updatedWall); + } else if ('floorUuid' in selectedDecal.decalData.decalType) { + const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + if (updatedFloor) updateBackend(updatedFloor); + } + } + + const handleLayerChange = (direction: "up" | "down") => { + if (!selectedDecal) return; + + const position: [number, number, number] = [...(selectedDecal.decalData.decalPosition || [0, 0, 0]),]; + + if (direction === "up") { + position[2] = Math.abs(position[2]); + } else { + position[2] = -Math.abs(position[2]); + } + + const updatedDecal: Decal = { ...selectedDecal.decalData, decalPosition: position, }; + + setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); + + if ("wallUuid" in selectedDecal.decalData.decalType) { + const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + if (updatedWall) updateBackend(updatedWall); + } else if ("floorUuid" in selectedDecal.decalData.decalType) { + const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + if (updatedFloor) updateBackend(updatedFloor); + } + }; + + if (!selectedDecal) return null; + return (
Decal Properties
{ }} - value={10} + onChange={(e) => { handleRotationChange(parseFloat(e)) }} + value={selectedDecal.decalData.decalRotation || 0} /> { }} - value={10} + onChange={(e) => { handleScaleChange(parseFloat(e)) }} + value={selectedDecal.decalData.decalScale || 1} />
console.log(value)} + onChange={(value: number) => handleOpacityChange(value)} />
Layering
- -
diff --git a/app/src/modules/builder/Decal/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance.tsx index 69b0c7c..59f2141 100644 --- a/app/src/modules/builder/Decal/decalInstance.tsx +++ b/app/src/modules/builder/Decal/decalInstance.tsx @@ -93,7 +93,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP // debug visible={visible} position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]} - rotation={[0, 0, 0]} + rotation={[0, 0, decal.decalRotation * (Math.PI / 180)]} scale={[decal.decalScale, decal.decalScale, 0.01]} userData={decal} onClick={(e) => { @@ -101,7 +101,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP if (e.object.userData.decalUuid) { e.stopPropagation(); if (toolMode === 'cursor') { - setSelectedDecal(e.object); + setSelectedDecal({ decalMesh: e.object, decalData: decal }); setSelectedWall(null); setSelectedFloor(null); } else if (toolMode === '3D-Delete') { @@ -131,7 +131,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP } }} onPointerMissed={() => { - if (selectedDecal && selectedDecal.userData.decalUuid === decal.decalUuid) { + if (selectedDecal && selectedDecal.decalMesh.userData.decalUuid === decal.decalUuid) { setSelectedDecal(null); } }} @@ -141,6 +141,8 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP side={THREE.DoubleSide} polygonOffset polygonOffsetFactor={-1} + transparent + opacity={decal.decalOpacity} /> ) diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index 939dc78..50d3536 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -167,6 +167,7 @@ function Wall({ wall }: { readonly wall: Wall }) { decalId: 'Default Decal', decalPosition: [0, 0, wall.wallThickness / 2 + 0.001], decalRotation: 0, + decalOpacity: 1, decalScale: 1, decalType: { type: 'Wall', wallUuid: wall.wallUuid } } @@ -183,7 +184,7 @@ function Wall({ wall }: { readonly wall: Wall }) { {wall.decals.map((decal) => ( - + ))} diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 5558f9d..555c7a3 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -165,7 +165,7 @@ export default function PostProcessing() { )} {selectedDecal && ( void; // Setters - Decal - setSelectedDecal: (decal: Object3D | null) => void; + setSelectedDecal: (decal: { decalMesh: Object3D, decalData: Decal } | null) => void; setDeletableDecal: (decal: Object3D | null) => void; // Setters - Aisle General @@ -290,7 +290,7 @@ export const useBuilderStore = create()( // === Setters: Decal === - setSelectedDecal: (decal: Object3D | null) => { + setSelectedDecal: (decal: { decalMesh: Object3D, decalData: Decal } | null) => { set((state) => { state.selectedDecal = decal; }) diff --git a/app/src/store/builder/useFloorStore.ts b/app/src/store/builder/useFloorStore.ts index 6f488a5..c7b9545 100644 --- a/app/src/store/builder/useFloorStore.ts +++ b/app/src/store/builder/useFloorStore.ts @@ -20,7 +20,7 @@ interface FloorStore { setDepth: (uuid: string, depth: number) => void; setMaterial: (uuid: string, sideMaterial: string, topMaterial: string) => void; addDecal: (floors: string, decal: Decal) => void; - updateDecal: (decalUuid: string, decal: Decal) => void; + updateDecal: (decalUuid: string, decal: Decal) => Floor | undefined; removeDecal: (decalUuid: string) => Floor | undefined; updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void; updateDecalRotation: (decalUuid: string, rotation: number) => void; @@ -203,15 +203,20 @@ export const createFloorStore = () => { } }), - updateDecal: (decalUuid, updatedDecal) => set(state => { - for (const floor of state.floors) { - const index = floor.decals.findIndex(d => d.decalUuid === decalUuid); - if (index !== -1) { - floor.decals[index] = updatedDecal; - break; + updateDecal: (decalUuid, updatedDecal) => { + let affectedFloor: Floor | undefined; + set(state => { + for (const floor of state.floors) { + const index = floor.decals.findIndex(d => d.decalUuid === decalUuid); + if (index !== -1) { + floor.decals[index] = updatedDecal; + affectedFloor = JSON.parse(JSON.stringify(floor)); + break; + } } - } - }), + }) + return affectedFloor; + }, removeDecal: (decalUuid) => { let affectedFloor: Floor | undefined; diff --git a/app/src/store/builder/useWallStore.ts b/app/src/store/builder/useWallStore.ts index 92b6fe5..d1b2e86 100644 --- a/app/src/store/builder/useWallStore.ts +++ b/app/src/store/builder/useWallStore.ts @@ -10,7 +10,7 @@ interface WallStore { clearWalls: () => void; removeWallByPoints: (Points: [Point, Point]) => Wall | undefined; addDecal: (wallUuid: string, decal: Decal) => void; - updateDecal: (decalUuid: string, decal: Decal) => void; + updateDecal: (decalUuid: string, decal: Decal) => Wall | undefined; removeDecal: (decalUuid: string) => Wall | undefined; updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void; updateDecalRotation: (decalUuid: string, rotation: number) => void; @@ -90,14 +90,19 @@ export const createWallStore = () => { } }), - updateDecal: (decalUuid, decal) => set((state) => { - for (const wall of state.walls) { - const decalToUpdate = wall.decals.find(d => d.decalUuid === decalUuid); - if (decalToUpdate) { - Object.assign(decalToUpdate, decal); + updateDecal: (decalUuid, decal) => { + let affectedWall: Wall | undefined; + set((state) => { + for (const wall of state.walls) { + const decalToUpdate = wall.decals.find(d => d.decalUuid === decalUuid); + if (decalToUpdate) { + Object.assign(decalToUpdate, decal); + affectedWall = JSON.parse(JSON.stringify(wall)); + } } - } - }), + }); + return affectedWall; + }, removeDecal: (decalUuid) => { let affectedWall: Wall | undefined; diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index 3487fc6..e0440ec 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -88,6 +88,7 @@ interface Decal { decalType: WallDecal | FloorDecal; decalPosition: [number, number, number]; decalRotation: number; + decalOpacity: number; decalScale: number; } From c0e040fb3ab9e25f1652e392e06e1911e5236602 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 16:38:29 +0530 Subject: [PATCH 08/19] added decal movement across the wall --- .../properties/SelectedDecalProperties.tsx | 20 ++-- .../modules/builder/Decal/decalInstance.tsx | 104 ++++++++++++++++-- .../calculateAssetTransformationOnWall.ts | 4 +- .../Instances/Instance/wallAssetInstance.tsx | 35 ++---- app/src/store/builder/useWallStore.ts | 23 ++-- 5 files changed, 131 insertions(+), 55 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx index 7994a1f..99c16da 100644 --- a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx @@ -14,8 +14,8 @@ import { getUserData } from "../../../../functions/getUserData"; const SelectedDecalProperties = () => { const { selectedDecal, setSelectedDecal } = useBuilderStore(); const { wallStore, floorStore } = useSceneContext(); - const { updateDecal: updateDecalFromWall } = wallStore(); - const { updateDecal: updateDecalFromFloor } = floorStore(); + const { updateDecal: updateDecalInWall } = wallStore(); + const { updateDecal: updateDecalInFloor } = floorStore(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -68,10 +68,10 @@ const SelectedDecalProperties = () => { setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); if ('wallUuid' in selectedDecal.decalData.decalType) { - const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal); if (updatedWall) updateBackend(updatedWall); } else if ('floorUuid' in selectedDecal.decalData.decalType) { - const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal); if (updatedFloor) updateBackend(updatedFloor); } } @@ -82,10 +82,10 @@ const SelectedDecalProperties = () => { setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); if ('wallUuid' in selectedDecal.decalData.decalType) { - const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal); if (updatedWall) updateBackend(updatedWall); } else if ('floorUuid' in selectedDecal.decalData.decalType) { - const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal); if (updatedFloor) updateBackend(updatedFloor); } } @@ -96,10 +96,10 @@ const SelectedDecalProperties = () => { setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); if ('wallUuid' in selectedDecal.decalData.decalType) { - const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal); if (updatedWall) updateBackend(updatedWall); } else if ('floorUuid' in selectedDecal.decalData.decalType) { - const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal); if (updatedFloor) updateBackend(updatedFloor); } } @@ -120,10 +120,10 @@ const SelectedDecalProperties = () => { setSelectedDecal({ ...selectedDecal, decalData: updatedDecal }); if ("wallUuid" in selectedDecal.decalData.decalType) { - const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal); + const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal); if (updatedWall) updateBackend(updatedWall); } else if ("floorUuid" in selectedDecal.decalData.decalType) { - const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal); + const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal); if (updatedFloor) updateBackend(updatedFloor); } }; diff --git a/app/src/modules/builder/Decal/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance.tsx index 59f2141..d414419 100644 --- a/app/src/modules/builder/Decal/decalInstance.tsx +++ b/app/src/modules/builder/Decal/decalInstance.tsx @@ -1,13 +1,13 @@ import * as THREE from 'three'; -import { Decal } from '@react-three/drei' -import { useLoader } from '@react-three/fiber'; +import { CameraControls, Decal } from '@react-three/drei' +import { useLoader, useThree } from '@react-three/fiber'; import { useSocketStore, useToggleView, useToolMode } from '../../../store/builder/store'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import defaultMaterial from '../../../assets/textures/floor/wall-tex.png'; import useModuleStore from '../../../store/useModuleStore'; import { useSceneContext } from '../../scene/sceneContext'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { getUserData } from '../../../functions/getUserData'; import { useVersionContext } from '../version/versionContext'; import { useParams } from 'react-router-dom'; @@ -18,8 +18,8 @@ import { useParams } from 'react-router-dom'; function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalPosition[2] }: { parent: Wall | Floor; visible?: boolean, decal: Decal, zPosition?: number }) { const { setSelectedWall, setSelectedFloor, selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore(); const { wallStore, floorStore } = useSceneContext(); - const { removeDecal: removeDecalFromWall } = wallStore(); - const { removeDecal: removeDecalFromFloor } = floorStore(); + const { removeDecal: removeDecalInWall, updateDecalPosition: updateDecalPositionInWall, getWallById } = wallStore(); + const { removeDecal: removeDecalInFloor } = floorStore(); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); const { activeModule } = useModuleStore(); @@ -30,6 +30,10 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP const { socket } = useSocketStore(); const material = useLoader(THREE.TextureLoader, defaultMaterial); + const { raycaster, pointer, camera, scene, gl, controls } = useThree(); + const isDraggingRef = useRef(false); + const dragOffsetRef = useRef(null); + useEffect(() => { if (!toggleView && activeModule === 'builder') { if (toolMode !== 'cursor') { @@ -44,9 +48,77 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP } }, [toggleView, toolMode, activeModule, selectedDecal, deletableDecal]); + useEffect(() => { + const canvasElement = gl.domElement; + + const handlePointerMove = (e: PointerEvent) => { + if (!isDraggingRef.current || !selectedDecal || selectedDecal.decalData.decalUuid !== decal.decalUuid) return; + + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects(scene.children, true); + + const wallIntersect = intersects.find(i => i.object.userData && 'wallUuid' in parent && i.object.userData.wallUuid === parent.wallUuid); + + if (wallIntersect) { + const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone()); + + let offset = dragOffsetRef.current || new THREE.Vector3(0, 0, 0); + + updateDecalPositionInWall(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]); + } + }; + + const handlePointerUp = (e: PointerEvent) => { + if (controls) { + (controls as CameraControls).enabled = true; + } + if (isDraggingRef.current) { + isDraggingRef.current = false; + dragOffsetRef.current = null; + + if ('wallUuid' in parent) { + setTimeout(() => { + const updatedWall = getWallById(parent.wallUuid); + + if (updatedWall) { + if (projectId && updatedWall) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + } + }, 0) + } + + } + }; + + if (activeModule === 'builder' && !toggleView) { + canvasElement.addEventListener('pointermove', handlePointerMove); + canvasElement.addEventListener('pointerup', handlePointerUp); + } + + return () => { + canvasElement.removeEventListener('pointermove', handlePointerMove); + canvasElement.removeEventListener('pointerup', handlePointerUp); + }; + }, [gl, camera, scene, raycaster, selectedDecal, decal, parent, activeModule, toggleView, projectId, selectedVersion, userId, organization, socket]); + const deleteDecal = (decalUuid: string, parent: Wall | Floor) => { if ('wallUuid' in parent) { - const updatedWall = removeDecalFromWall(decalUuid); + const updatedWall = removeDecalInWall(decalUuid); if (projectId && updatedWall) { // API @@ -66,7 +138,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP socket.emit('v1:model-Wall:add', data); } } else if ('floorUuid' in parent) { - const updatedFloor = removeDecalFromFloor(decalUuid); + const updatedFloor = removeDecalInFloor(decalUuid); if (projectId && updatedFloor) { // API @@ -96,6 +168,24 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP rotation={[0, 0, decal.decalRotation * (Math.PI / 180)]} scale={[decal.decalScale, decal.decalScale, 0.01]} userData={decal} + onPointerDown={(e) => { + if (visible && !toggleView && activeModule === 'builder') { + if (e.object.userData.decalUuid && toolMode === 'cursor') { + e.stopPropagation(); + isDraggingRef.current = true; + if (controls) { + (controls as CameraControls).enabled = false; + } + setSelectedDecal({ decalMesh: e.object, decalData: decal }); + setSelectedWall(null); + setSelectedFloor(null); + + const localIntersect = e.object.worldToLocal(e.point.clone()); + dragOffsetRef.current = new THREE.Vector3(decal.decalPosition[0] - localIntersect.x, decal.decalPosition[1] - localIntersect.y, 0); + } + } + }} + onClick={(e) => { if (visible && !toggleView && activeModule === 'builder') { if (e.object.userData.decalUuid) { diff --git a/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts b/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts index aa881c8..89142d5 100644 --- a/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts +++ b/app/src/modules/builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall.ts @@ -18,10 +18,10 @@ const calculateAssetTransformationOnWall = ( const projection = initialWallNormalized.clone().multiplyScalar(dotProduct); const perpendicular = new THREE.Vector3().subVectors(assetVector, projection); - const distanceFromWall = perpendicular.length(); + const distanceInWall = perpendicular.length(); const crossProduct = new THREE.Vector3().crossVectors(initialWallNormalized, perpendicular).y; - const signedDistance = distanceFromWall * (crossProduct >= 0 ? 1 : -1); + const signedDistance = distanceInWall * (crossProduct >= 0 ? 1 : -1); const percentage = Math.max(0, Math.min(1, dotProduct / initialWallLength)); diff --git a/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx b/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx index 76b3ad7..3919f65 100644 --- a/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx +++ b/app/src/modules/builder/wallAsset/Instances/Instance/wallAssetInstance.tsx @@ -23,7 +23,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const { raycaster, pointer, camera, scene, controls, gl } = useThree(); const { wallStore, wallAssetStore } = useSceneContext(); const { walls, getWallById } = wallStore(); - const { updateWallAsset, removeWallAsset } = wallAssetStore(); + const { updateWallAsset, removeWallAsset, getWallAssetById } = wallAssetStore(); const { toggleView } = useToggleView(); const { activeTool } = useActiveTool(); const { activeModule } = useModuleStore(); @@ -116,33 +116,14 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const canvasElement = gl.domElement; const onPointerUp = (e: PointerEvent) => { - draggingRef.current = false; - if (controls) { - (controls as any).enabled = true; - } + if (draggingRef.current) { + draggingRef.current = false; + if (controls) { + (controls as any).enabled = true; + } - if (selectedWallAsset) { - pointer.x = (e.clientX / window.innerWidth) * 2 - 1; - pointer.y = -(e.clientY / window.innerHeight) * 2 + 1; - - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(scene.children, true); - const intersect = intersects.find((i: any) => i.object.name.includes('WallReference')); - - if (intersect && intersect.object.userData.wallUuid && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) { - const newPoint = closestPointOnLineSegment( - new THREE.Vector3(intersect.point.x, 0, intersect.point.z), - new THREE.Vector3(...intersect.object.userData.points[0].position), - new THREE.Vector3(...intersect.object.userData.points[1].position) - ); - - const wallRotation = intersect.object.rotation.clone(); - - const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, { - wallUuid: intersect.object.userData.wallUuid, - position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z], - rotation: [wallRotation.x, wallRotation.y, wallRotation.z], - }); + if (selectedWallAsset) { + const updatedWallAsset = getWallAssetById(wallAsset.modelUuid); if (projectId && updatedWallAsset) { diff --git a/app/src/store/builder/useWallStore.ts b/app/src/store/builder/useWallStore.ts index d1b2e86..c7c07e9 100644 --- a/app/src/store/builder/useWallStore.ts +++ b/app/src/store/builder/useWallStore.ts @@ -12,7 +12,7 @@ interface WallStore { addDecal: (wallUuid: string, decal: Decal) => void; updateDecal: (decalUuid: string, decal: Decal) => Wall | undefined; removeDecal: (decalUuid: string) => Wall | undefined; - updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void; + updateDecalPosition: (decalUuid: string, position: [number, number, number]) => Wall | undefined; updateDecalRotation: (decalUuid: string, rotation: number) => void; updateDecalScale: (decalUuid: string, scale: number) => void; @@ -118,15 +118,20 @@ export const createWallStore = () => { return affectedWall; }, - updateDecalPosition: (decalUuid, position) => set((state) => { - for (const wall of state.walls) { - const decal = wall.decals.find(d => d.decalUuid === decalUuid); - if (decal) { - decal.decalPosition = position; - break; + updateDecalPosition: (decalUuid, position) => { + let affectedWall: Wall | undefined; + set((state) => { + for (const wall of state.walls) { + const decal = wall.decals.find(d => d.decalUuid === decalUuid); + if (decal) { + decal.decalPosition = position; + affectedWall = JSON.parse(JSON.stringify(wall)); + break; + } } - } - }), + }) + return affectedWall; + }, updateDecalRotation: (decalUuid, rotation) => set((state) => { for (const wall of state.walls) { From 441f1efb230f14dd57bbe2866471458de6265e90 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 16:49:16 +0530 Subject: [PATCH 09/19] side bar left bug fix --- app/src/components/layout/sidebarLeft/Assets.tsx | 10 +++++++--- app/src/store/builder/store.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 4e946d3..8216310 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -120,7 +120,12 @@ const Assets: React.FC = () => { echo.error("failed to fetch assets"); setisLoading(false); } + + if (asset === "Decals") { + fetchCategoryDecals("Safety"); + } }; + const fetchCategoryDecals = async (asset: any) => { setisLoading(true); // setSelectedCategory(asset); @@ -226,9 +231,8 @@ const Assets: React.FC = () => { {activeSubcategories.map((cat, index) => (
{ fetchCategoryDecals(cat.name); setSelectedSubCategory(cat.name); diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 893bd65..2f72b98 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -664,6 +664,6 @@ interface DecalStore { // Create the Zustand store with types export const useDecalStore = create((set) => ({ - selectedSubCategory: null, + selectedSubCategory: 'Safety', setSelectedSubCategory: (subCategory: string | null) => set({ selectedSubCategory: subCategory }), })); From d4f12d230fb8573acb953962d805205c9c71a516 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 17:25:04 +0530 Subject: [PATCH 10/19] added IMage store in idex db --- .../components/layout/sidebarLeft/Assets.tsx | 16 ++--- app/src/modules/builder/Decal/decal.tsx | 14 ++++ .../Decal/decalCreator/decalCreator.tsx | 44 ++++++++++++ .../{ => decalInstance}/decalInstance.tsx | 18 ++--- app/src/modules/builder/asset/assetsGroup.tsx | 1 + app/src/modules/builder/builder.tsx | 5 +- .../builder/wall/Instances/instance/wall.tsx | 2 +- app/src/store/builder/store.ts | 15 ++++ app/src/utils/indexDB/idbUtils.ts | 70 +++++++++++++++---- 9 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 app/src/modules/builder/Decal/decal.tsx create mode 100644 app/src/modules/builder/Decal/decalCreator/decalCreator.tsx rename app/src/modules/builder/Decal/{ => decalInstance}/decalInstance.tsx (93%) diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 8216310..824062f 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import Search from "../../ui/inputs/Search"; import { getCategoryAsset } from "../../../services/factoryBuilder/asset/assets/getCategoryAsset"; import { fetchAssets } from "../../../services/marketplace/fetchAssets"; -import { useDecalStore, useSelectedItem } from "../../../store/builder/store"; +import { useDecalStore, useDroppedDecal, useSelectedItem } from "../../../store/builder/store"; // images ------------------- import vehicle from "../../../assets/image/categories/vehicles.png"; @@ -21,7 +21,6 @@ import { HangTagIcon, NavigationIcon, } from "../../icons/ExportCommonIcons"; -import { assert } from "console"; import { getCategoryDecals } from "../../../services/factoryBuilder/asset/decals/getCategoryDecals"; // ------------------------------------- @@ -46,6 +45,7 @@ interface CategoryListProp { } const Assets: React.FC = () => { const { setSelectedItem } = useSelectedItem(); + const { setDroppedDecal } = useDroppedDecal(); const [searchValue, setSearchValue] = useState(""); const [selectedCategory, setSelectedCategory] = useState(null); const [categoryAssets, setCategoryAssets] = useState([]); @@ -304,15 +304,11 @@ const Assets: React.FC = () => { alt={asset.decalName} className="asset-image" onPointerDown={() => { - setSelectedItem({ - name: asset.decalName, - id: asset.id, - type: - asset.type === "undefined" - ? undefined - : asset.type, + setDroppedDecal({ category: asset.category, - // subType: asset.subType, + decalName: asset.decalName, + decalImage: asset.decalImage, + decalId: asset.id }); }} /> diff --git a/app/src/modules/builder/Decal/decal.tsx b/app/src/modules/builder/Decal/decal.tsx new file mode 100644 index 0000000..d14e150 --- /dev/null +++ b/app/src/modules/builder/Decal/decal.tsx @@ -0,0 +1,14 @@ +import DecalCreator from './decalCreator/decalCreator' + +function Decal() { + + return ( + <> + + + + + ) +} + +export default Decal \ No newline at end of file diff --git a/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx new file mode 100644 index 0000000..4335afc --- /dev/null +++ b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx @@ -0,0 +1,44 @@ +import { useEffect } from 'react'; +import { useThree } from '@react-three/fiber'; +import { useDroppedDecal } from '../../../../store/builder/store'; +import useModuleStore from '../../../../store/useModuleStore'; + +function DecalCreator() { + const { droppedDecal } = useDroppedDecal(); + const { activeModule } = useModuleStore(); + const { controls, gl, pointer, camera, raycaster, scene } = useThree(); + + useEffect(() => { + const canvasElement = gl.domElement; + + const onDrop = (event: DragEvent) => { + console.log('event: ', event); + console.log('droppedDecal: ', droppedDecal); + if (!event.dataTransfer?.files[0]) return; + + if (droppedDecal) { + } + }; + + const onDragOver = (event: any) => { + event.preventDefault(); + }; + + if (activeModule === "builder") { + canvasElement.addEventListener("drop", onDrop); + canvasElement.addEventListener("dragover", onDragOver); + } + + return () => { + canvasElement.removeEventListener("drop", onDrop); + canvasElement.removeEventListener("dragover", onDragOver); + }; + }, [droppedDecal, camera, activeModule, controls]); + + return ( + <> + + ) +} + +export default DecalCreator \ No newline at end of file diff --git a/app/src/modules/builder/Decal/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx similarity index 93% rename from app/src/modules/builder/Decal/decalInstance.tsx rename to app/src/modules/builder/Decal/decalInstance/decalInstance.tsx index d414419..bbb1268 100644 --- a/app/src/modules/builder/Decal/decalInstance.tsx +++ b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx @@ -1,19 +1,19 @@ import * as THREE from 'three'; import { CameraControls, Decal } from '@react-three/drei' import { useLoader, useThree } from '@react-three/fiber'; -import { useSocketStore, useToggleView, useToolMode } from '../../../store/builder/store'; -import { useBuilderStore } from '../../../store/builder/useBuilderStore'; +import { useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; +import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; -import defaultMaterial from '../../../assets/textures/floor/wall-tex.png'; -import useModuleStore from '../../../store/useModuleStore'; -import { useSceneContext } from '../../scene/sceneContext'; +import defaultMaterial from '../../../../assets/textures/floor/wall-tex.png'; +import useModuleStore from '../../../../store/useModuleStore'; +import { useSceneContext } from '../../../scene/sceneContext'; import { useEffect, useRef } from 'react'; -import { getUserData } from '../../../functions/getUserData'; -import { useVersionContext } from '../version/versionContext'; +import { getUserData } from '../../../../functions/getUserData'; +import { useVersionContext } from '../../version/versionContext'; import { useParams } from 'react-router-dom'; -// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; -// import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi'; +// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi'; +// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi'; function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalPosition[2] }: { parent: Wall | Floor; visible?: boolean, decal: Decal, zPosition?: number }) { const { setSelectedWall, setSelectedFloor, selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore(); diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 9d36efc..0205a4c 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -363,6 +363,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { setLeft(relativeX); } }; + const onMouseUp = (evt: any) => { setIsRenameMode(false); } diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index ef310fc..d7b8d34 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -33,13 +33,14 @@ import AssetsGroup from "./asset/assetsGroup"; import DxfFile from "./dfx/LoadBlueprint"; import AislesGroup from "./aisle/aislesGroup"; import WallGroup from "./wall/wallGroup"; +import WallAssetGroup from "./wallAsset/wallAssetGroup"; import FloorGroup from "./floor/floorGroup"; import ZoneGroup from "./zone/zoneGroup"; +import Decal from "./Decal/decal"; import { useParams } from "react-router-dom"; import { useBuilderStore } from "../../store/builder/useBuilderStore"; import { getUserData } from "../../functions/getUserData"; -import WallAssetGroup from "./wallAsset/wallAssetGroup"; export default function Builder() { const state = useThree(); @@ -106,6 +107,8 @@ export default function Builder() { + + diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index 50d3536..f52fa4d 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -10,7 +10,7 @@ import { useToggleView, useWallVisibility } from '../../../../../store/builder/s import { useBuilderStore } from '../../../../../store/builder/useBuilderStore'; import * as Constants from '../../../../../types/world/worldConstants'; -import DecalInstance from '../../../Decal/decalInstance'; +import DecalInstance from '../../../Decal/decalInstance/decalInstance'; import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png'; import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg'; diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 2f72b98..c022bb7 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -163,6 +163,21 @@ export const useSelectedItem = create((set: any) => ({ setSelectedItem: (x: any) => set(() => ({ selectedItem: x })), })); +type DroppedDecalType = { + category: string; + decalName: string; + decalImage: string; + decalId: string; +}; + +export const useDroppedDecal = create<{ + droppedDecal: DroppedDecalType | null; + setDroppedDecal: (x: DroppedDecalType | null) => void; +}>((set) => ({ + droppedDecal: null, + setDroppedDecal: (x) => set({ droppedDecal: x }), +})); + export const useNavMesh = create((set: any) => ({ navMesh: null, setNavMesh: (x: any) => set({ navMesh: x }), diff --git a/app/src/utils/indexDB/idbUtils.ts b/app/src/utils/indexDB/idbUtils.ts index 24b1448..79910bb 100644 --- a/app/src/utils/indexDB/idbUtils.ts +++ b/app/src/utils/indexDB/idbUtils.ts @@ -1,15 +1,19 @@ -const DB_NAME = 'GLTFStorage'; -const STORE_NAME = 'models'; const DB_VERSION = 1; -export function initializeDB(): Promise { +const ASSET_DB_NAME = 'GLTFStorage'; +const ASSET_STORE_NAME = 'models'; + +const IMAGE_DB_NAME = 'ImageStorage'; +const IMAGE_STORE_NAME = 'images'; + +export function initializeAssetDB(): Promise { return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION); + const request = indexedDB.open(ASSET_DB_NAME, DB_VERSION); request.onupgradeneeded = () => { const db = request.result; - if (!db.objectStoreNames.contains(STORE_NAME)) { - db.createObjectStore(STORE_NAME); + if (!db.objectStoreNames.contains(ASSET_STORE_NAME)) { + db.createObjectStore(ASSET_STORE_NAME); } }; @@ -19,11 +23,11 @@ export function initializeDB(): Promise { } export async function storeGLTF(key: string, file: Blob): Promise { - const db = await initializeDB(); + const db = await initializeAssetDB(); return new Promise((resolve, reject) => { - const transaction = db.transaction(STORE_NAME, 'readwrite'); - const store = transaction.objectStore(STORE_NAME); + const transaction = db.transaction(ASSET_STORE_NAME, 'readwrite'); + const store = transaction.objectStore(ASSET_STORE_NAME); const request = store.put(file, key); request.onsuccess = () => resolve(); @@ -32,11 +36,53 @@ export async function storeGLTF(key: string, file: Blob): Promise { } export async function retrieveGLTF(key: string): Promise { - const db = await initializeDB(); + const db = await initializeAssetDB(); return new Promise((resolve, reject) => { - const transaction = db.transaction(STORE_NAME, 'readonly'); - const store = transaction.objectStore(STORE_NAME); + const transaction = db.transaction(ASSET_STORE_NAME, 'readonly'); + const store = transaction.objectStore(ASSET_STORE_NAME); + const request = store.get(key); + + request.onsuccess = () => resolve(request.result as Blob | undefined); + request.onerror = () => reject(request.error); + }); +} + +export function initializeImageDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(IMAGE_DB_NAME, DB_VERSION); + + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(IMAGE_STORE_NAME)) { + db.createObjectStore(IMAGE_STORE_NAME); + } + }; + + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); +} + +export async function storeImage(key: string, file: Blob): Promise { + const db = await initializeImageDB(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction(IMAGE_STORE_NAME, 'readwrite'); + const store = transaction.objectStore(IMAGE_STORE_NAME); + const request = store.put(file, key); + + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); +} + +export async function retrieveImage(key: string): Promise { + const db = await initializeImageDB(); + + return new Promise((resolve, reject) => { + const transaction = db.transaction(IMAGE_STORE_NAME, 'readonly'); + const store = transaction.objectStore(IMAGE_STORE_NAME); const request = store.get(key); request.onsuccess = () => resolve(request.result as Blob | undefined); From d129a8688574b2685a933a8214c7ec4adff19692 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Tue, 26 Aug 2025 17:39:45 +0530 Subject: [PATCH 11/19] added cache and indexdb loading for decal --- .../Decal/decalInstance/decalInstance.tsx | 107 +++++++++++++++++- .../builder/wall/Instances/instance/wall.tsx | 2 +- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx index bbb1268..6be22ee 100644 --- a/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx +++ b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx @@ -1,13 +1,14 @@ import * as THREE from 'three'; import { CameraControls, Decal } from '@react-three/drei' -import { useLoader, useThree } from '@react-three/fiber'; +import { useThree } from '@react-three/fiber'; import { useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; +import { retrieveImage, storeImage } from '../../../../utils/indexDB/idbUtils'; import defaultMaterial from '../../../../assets/textures/floor/wall-tex.png'; import useModuleStore from '../../../../store/useModuleStore'; import { useSceneContext } from '../../../scene/sceneContext'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { getUserData } from '../../../../functions/getUserData'; import { useVersionContext } from '../../version/versionContext'; import { useParams } from 'react-router-dom'; @@ -28,12 +29,108 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { socket } = useSocketStore(); - const material = useLoader(THREE.TextureLoader, defaultMaterial); - const { raycaster, pointer, camera, scene, gl, controls } = useThree(); const isDraggingRef = useRef(false); const dragOffsetRef = useRef(null); + const [texture, setTexture] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const loadDefaultTexture = () => { + const textureLoader = new THREE.TextureLoader(); + textureLoader.load( + defaultMaterial, + (fallbackTex) => { + fallbackTex.name = "default-decal"; + setTexture(fallbackTex); + setIsLoading(false); + }, + undefined, + (error) => { + console.error("Error loading default decal texture:", error); + setIsLoading(false); + } + ); + }; + + const loadDecalTexture = async (decalId: string) => { + setIsLoading(true); + + try { + const cachedTexture = THREE.Cache.get(decalId); + if (cachedTexture) { + setTexture(cachedTexture); + setIsLoading(false); + return; + } + + const indexedDBTexture = await retrieveImage(decalId); + if (indexedDBTexture) { + const blobUrl = URL.createObjectURL(indexedDBTexture); + const textureLoader = new THREE.TextureLoader(); + textureLoader.load( + blobUrl, + (tex) => { + URL.revokeObjectURL(blobUrl); + tex.name = decalId; + THREE.Cache.add(decalId, tex); + setTexture(tex); + setIsLoading(false); + }, + undefined, + (error) => { + console.error(`Error loading texture from IndexedDB:`, error); + URL.revokeObjectURL(blobUrl); + loadFromBackend(decalId); + } + ); + return; + } + + loadFromBackend(decalId); + } catch (error) { + console.error("Error loading decal texture:", error); + loadDefaultTexture(); + } + }; + + const loadFromBackend = (decalId: string) => { + console.log('decalId: ', decalId); + const textureUrl = `${process.env.REACT_APP_SERVER_MARKETPLACE_URL}/api/v2/DecalFile/${decalId}`; + const textureLoader = new THREE.TextureLoader(); + + textureLoader.load( + textureUrl, + async (tex) => { + tex.name = decalId; + THREE.Cache.add(decalId, tex); + setTexture(tex); + setIsLoading(false); + + try { + const response = await fetch(textureUrl); + const blob = await response.blob(); + await storeImage(decalId, blob); + } catch (error) { + console.error("Error storing texture in IndexedDB:", error); + } + }, + undefined, + (error) => { + console.error(`Error loading texture from backend:`, error); + loadDefaultTexture(); + } + ); + }; + + useEffect(() => { + if (decal.decalId) { + loadDecalTexture(decal.decalId); + } else { + loadDefaultTexture(); + } + }, [decal.decalId]); + useEffect(() => { if (!toggleView && activeModule === 'builder') { if (toolMode !== 'cursor') { @@ -227,7 +324,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP }} > Date: Thu, 28 Aug 2025 15:50:45 +0530 Subject: [PATCH 12/19] added decal dropping to scene, decal movement from one wall to another wall, one floor to another floor, one wall to floor and one floor to wall --- .../Decal/decalCreator/decalCreator.tsx | 117 ++++++- .../Decal/decalInstance/decalInstance.tsx | 229 ++----------- .../eventHandler/useDecalEventHandlers.ts | 301 ++++++++++++++++++ .../builder/asset/models/model/model.tsx | 7 + .../Instances/Instance/floorInstance.tsx | 117 +++---- .../builder/wall/Instances/instance/wall.tsx | 16 +- .../scene/postProcessing/postProcessing.tsx | 2 +- app/src/store/builder/useBuilderStore.ts | 29 +- app/src/store/builder/useFloorStore.ts | 25 +- app/src/store/builder/useWallStore.ts | 21 +- 10 files changed, 568 insertions(+), 296 deletions(-) create mode 100644 app/src/modules/builder/Decal/eventHandler/useDecalEventHandlers.ts diff --git a/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx index 4335afc..221a2bf 100644 --- a/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx +++ b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx @@ -1,22 +1,129 @@ +import { MathUtils } from 'three'; import { useEffect } from 'react'; import { useThree } from '@react-three/fiber'; -import { useDroppedDecal } from '../../../../store/builder/store'; +import { useParams } from 'react-router-dom'; +import { useDroppedDecal, useSocketStore } from '../../../../store/builder/store'; import useModuleStore from '../../../../store/useModuleStore'; +import { useSceneContext } from '../../../scene/sceneContext'; +import { useVersionContext } from '../../version/versionContext'; + +import { getUserData } from '../../../../functions/getUserData'; + +// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi'; +// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi'; function DecalCreator() { + const { wallStore, floorStore } = useSceneContext(); + const { addDecal: addDecalOnWall, getWallById } = wallStore(); + const { addDecal : addDecalOnFloor, getFloorById } = floorStore(); const { droppedDecal } = useDroppedDecal(); const { activeModule } = useModuleStore(); + const { userId, organization } = getUserData(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + const { socket } = useSocketStore(); const { controls, gl, pointer, camera, raycaster, scene } = useThree(); useEffect(() => { const canvasElement = gl.domElement; const onDrop = (event: DragEvent) => { - console.log('event: ', event); - console.log('droppedDecal: ', droppedDecal); - if (!event.dataTransfer?.files[0]) return; - if (droppedDecal) { + pointer.x = (event.clientX / window.innerWidth) * 2 - 1; + pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects(scene.children, true); + const wallIntersect = intersects.find(i => i.object.userData && i.object.userData.wallUuid); + const floorIntersect = intersects.find(i => i.object.userData && i.object.userData.floorUuid); + + if (wallIntersect) { + const wall = getWallById(wallIntersect.object.userData.wallUuid); + if (!wall) return; + + const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone()); + + const decal: Decal = { + decalUuid: MathUtils.generateUUID(), + decalName: droppedDecal.decalName, + decalId: droppedDecal.decalId, + decalType: { + type: 'Wall', + wallUuid: wallIntersect.object.userData.wallUuid, + }, + decalPosition: [point.x, point.y, wall.wallThickness / 2 + 0.001], + decalRotation: 0, + decalOpacity: 1, + decalScale: 1, + } + + addDecalOnWall(wallIntersect.object.userData.wallUuid, decal); + + setTimeout(() => { + const updatedWall = getWallById(wallIntersect.object.userData.wallUuid); + if (updatedWall) { + if (projectId && updatedWall) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + } + }, 0) + } else if (floorIntersect) { + const floor = getFloorById(floorIntersect.object.userData.floorUuid); + if (!floor) return; + + const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone()); + + const decal: Decal = { + decalUuid: MathUtils.generateUUID(), + decalName: droppedDecal.decalName, + decalId: droppedDecal.decalId, + decalType: { + type: 'Floor', + floorUuid: floorIntersect.object.userData.floorUuid, + }, + decalPosition: [point.x, point.y, -0.001], + decalRotation: 0, + decalOpacity: 1, + decalScale: 1, + } + + addDecalOnFloor(floorIntersect.object.userData.floorUuid, decal); + + setTimeout(() => { + const updatedFloor = getFloorById(floorIntersect.object.userData.floorUuid); + if (projectId && updatedFloor) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); + + // SOCKET + + const data = { + floorData: updatedFloor, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + }, 0) + } } }; diff --git a/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx index 6be22ee..8d0540a 100644 --- a/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx +++ b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx @@ -1,40 +1,38 @@ import * as THREE from 'three'; -import { CameraControls, Decal } from '@react-three/drei' -import { useThree } from '@react-three/fiber'; -import { useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; +import { Decal } from '@react-three/drei' +import { useToggleView, useToolMode } from '../../../../store/builder/store'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { retrieveImage, storeImage } from '../../../../utils/indexDB/idbUtils'; import defaultMaterial from '../../../../assets/textures/floor/wall-tex.png'; import useModuleStore from '../../../../store/useModuleStore'; -import { useSceneContext } from '../../../scene/sceneContext'; import { useEffect, useRef, useState } from 'react'; -import { getUserData } from '../../../../functions/getUserData'; -import { useVersionContext } from '../../version/versionContext'; -import { useParams } from 'react-router-dom'; +import { useDecalEventHandlers } from '../eventHandler/useDecalEventHandlers'; // import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi'; // import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi'; function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalPosition[2] }: { parent: Wall | Floor; visible?: boolean, decal: Decal, zPosition?: number }) { - const { setSelectedWall, setSelectedFloor, selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore(); - const { wallStore, floorStore } = useSceneContext(); - const { removeDecal: removeDecalInWall, updateDecalPosition: updateDecalPositionInWall, getWallById } = wallStore(); - const { removeDecal: removeDecalInFloor } = floorStore(); + const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; + const { selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore(); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); const { activeModule } = useModuleStore(); - const { userId, organization } = getUserData(); - const { selectedVersionStore } = useVersionContext(); - const { selectedVersion } = selectedVersionStore(); - const { projectId } = useParams(); - const { socket } = useSocketStore(); - const { raycaster, pointer, camera, scene, gl, controls } = useThree(); - const isDraggingRef = useRef(false); - const dragOffsetRef = useRef(null); + const decalRef = useRef(null); + + useEffect(() => { + if (selectedDecal?.decalData.decalUuid === decal.decalUuid && !selectedDecal.decalMesh) { + setSelectedDecal({ decalData: selectedDecal.decalData, decalMesh: decalRef.current }); + } + }, [selectedDecal]) + + const { handlePointerMissed, handlePointerLeave, handleClick, handlePointerDown, handlePointerEnter } = useDecalEventHandlers({ parent, decal, visible }); const [texture, setTexture] = useState(null); - const [isLoading, setIsLoading] = useState(true); + + const logDecalStatus = (decalId: string, status: string) => { + // console.log(decalId, status); + } const loadDefaultTexture = () => { const textureLoader = new THREE.TextureLoader(); @@ -43,24 +41,22 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP (fallbackTex) => { fallbackTex.name = "default-decal"; setTexture(fallbackTex); - setIsLoading(false); + logDecalStatus(decal.decalId, 'default-loaded'); }, undefined, (error) => { console.error("Error loading default decal texture:", error); - setIsLoading(false); } ); }; const loadDecalTexture = async (decalId: string) => { - setIsLoading(true); try { const cachedTexture = THREE.Cache.get(decalId); if (cachedTexture) { setTexture(cachedTexture); - setIsLoading(false); + logDecalStatus(decalId, 'cache-loaded'); return; } @@ -75,7 +71,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP tex.name = decalId; THREE.Cache.add(decalId, tex); setTexture(tex); - setIsLoading(false); + logDecalStatus(decalId, 'indexedDB-loaded'); }, undefined, (error) => { @@ -95,8 +91,8 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP }; const loadFromBackend = (decalId: string) => { - console.log('decalId: ', decalId); - const textureUrl = `${process.env.REACT_APP_SERVER_MARKETPLACE_URL}/api/v2/DecalFile/${decalId}`; + + const textureUrl = `${url_Backend_dwinzo}/api/v1/DecalImage/${decalId}`; const textureLoader = new THREE.TextureLoader(); textureLoader.load( @@ -105,7 +101,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP tex.name = decalId; THREE.Cache.add(decalId, tex); setTexture(tex); - setIsLoading(false); + logDecalStatus(decalId, 'backend-loaded'); try { const response = await fetch(textureUrl); @@ -145,183 +141,20 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP } }, [toggleView, toolMode, activeModule, selectedDecal, deletableDecal]); - useEffect(() => { - const canvasElement = gl.domElement; - - const handlePointerMove = (e: PointerEvent) => { - if (!isDraggingRef.current || !selectedDecal || selectedDecal.decalData.decalUuid !== decal.decalUuid) return; - - raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(scene.children, true); - - const wallIntersect = intersects.find(i => i.object.userData && 'wallUuid' in parent && i.object.userData.wallUuid === parent.wallUuid); - - if (wallIntersect) { - const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone()); - - let offset = dragOffsetRef.current || new THREE.Vector3(0, 0, 0); - - updateDecalPositionInWall(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]); - } - }; - - const handlePointerUp = (e: PointerEvent) => { - if (controls) { - (controls as CameraControls).enabled = true; - } - if (isDraggingRef.current) { - isDraggingRef.current = false; - dragOffsetRef.current = null; - - if ('wallUuid' in parent) { - setTimeout(() => { - const updatedWall = getWallById(parent.wallUuid); - - if (updatedWall) { - if (projectId && updatedWall) { - // API - - // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); - - // SOCKET - - const data = { - wallData: updatedWall, - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } - - socket.emit('v1:model-Wall:add', data); - } - } - }, 0) - } - - } - }; - - if (activeModule === 'builder' && !toggleView) { - canvasElement.addEventListener('pointermove', handlePointerMove); - canvasElement.addEventListener('pointerup', handlePointerUp); - } - - return () => { - canvasElement.removeEventListener('pointermove', handlePointerMove); - canvasElement.removeEventListener('pointerup', handlePointerUp); - }; - }, [gl, camera, scene, raycaster, selectedDecal, decal, parent, activeModule, toggleView, projectId, selectedVersion, userId, organization, socket]); - - const deleteDecal = (decalUuid: string, parent: Wall | Floor) => { - if ('wallUuid' in parent) { - const updatedWall = removeDecalInWall(decalUuid); - - if (projectId && updatedWall) { - // API - - // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); - - // SOCKET - - const data = { - wallData: updatedWall, - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } - - socket.emit('v1:model-Wall:add', data); - } - } else if ('floorUuid' in parent) { - const updatedFloor = removeDecalInFloor(decalUuid); - - if (projectId && updatedFloor) { - // API - - // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); - - // SOCKET - - const data = { - floorData: updatedFloor, - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } - - socket.emit('v1:model-Floor:add', data); - } - } - } - return ( { - if (visible && !toggleView && activeModule === 'builder') { - if (e.object.userData.decalUuid && toolMode === 'cursor') { - e.stopPropagation(); - isDraggingRef.current = true; - if (controls) { - (controls as CameraControls).enabled = false; - } - setSelectedDecal({ decalMesh: e.object, decalData: decal }); - setSelectedWall(null); - setSelectedFloor(null); - - const localIntersect = e.object.worldToLocal(e.point.clone()); - dragOffsetRef.current = new THREE.Vector3(decal.decalPosition[0] - localIntersect.x, decal.decalPosition[1] - localIntersect.y, 0); - } - } - }} - - onClick={(e) => { - if (visible && !toggleView && activeModule === 'builder') { - if (e.object.userData.decalUuid) { - e.stopPropagation(); - if (toolMode === 'cursor') { - setSelectedDecal({ decalMesh: e.object, decalData: decal }); - setSelectedWall(null); - setSelectedFloor(null); - } else if (toolMode === '3D-Delete') { - deleteDecal(e.object.userData.decalUuid, parent); - } - } - } - }} - onPointerEnter={(e) => { - if (visible && !toggleView && activeModule === 'builder') { - if (e.object.userData.decalUuid) { - e.stopPropagation(); - if (toolMode === '3D-Delete') { - setDeletableDecal(e.object); - } - } - } - }} - onPointerLeave={(e) => { - if (visible && !toggleView && activeModule === 'builder') { - if (e.object.userData.decalUuid) { - e.stopPropagation(); - if (toolMode === '3D-Delete' && deletableDecal && deletableDecal?.userData.decalUuid === e.object.userData.decalUuid) { - setDeletableDecal(null); - } - } - } - }} - onPointerMissed={() => { - if (selectedDecal && selectedDecal.decalMesh.userData.decalUuid === decal.decalUuid) { - setSelectedDecal(null); - } - }} + onPointerDown={(e) => { if (e.button === 0) handlePointerDown(e) }} + onClick={(e) => { handleClick(e) }} + onPointerEnter={(e) => { handlePointerEnter(e) }} + onPointerLeave={(e) => { handlePointerLeave(e) }} + onPointerMissed={() => handlePointerMissed()} > { + if (activeModule !== 'builder' || toggleView || !decalDragState.isDragging || !selectedDecal || selectedDecal.decalData.decalUuid !== decal.decalUuid) return; + + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects(scene.children, true); + + const wallIntersect = intersects.find(i => i.object.userData?.wallUuid); + const floorIntersect = intersects.find(i => i.object.userData?.floorUuid); + + let offset = decalDragState.dragOffset || new THREE.Vector3(0, 0, 0); + + if (wallIntersect) { + const wallUuid = wallIntersect.object.userData.wallUuid; + const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone()); + + if ("wallUuid" in parent && parent.wallUuid === wallUuid && decal.decalType.type === 'Wall') { + updateDecalPositionInWall(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]); + } else if (decal.decalType.type === 'Wall' && wallUuid) { + deleteDecal(decal.decalUuid, parent); + + const addedDecal = addDecalToWall(wallUuid, { + ...decal, + decalPosition: [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]], + decalType: { type: 'Wall', wallUuid: wallUuid } + }); + + if (addedDecal) { + setSelectedDecal({ decalMesh: null, decalData: addedDecal }) + } + } else if (decal.decalType.type === 'Floor' && wallUuid) { + deleteDecal(decal.decalUuid, parent); + const wall = getWallById(wallUuid); + if (!wall) return; + + const addedDecal = addDecalToWall(wallUuid, { + ...decal, + decalPosition: [point.x + offset.x, point.y + offset.y, wall.wallThickness / 2 + 0.001], + decalType: { type: 'Wall', wallUuid: wallUuid } + }); + + if (addedDecal) { + setSelectedDecal({ decalMesh: null, decalData: addedDecal }) + } + } + } else if (floorIntersect) { + const floorUuid = floorIntersect.object.userData.floorUuid; + const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone()); + + if ("floorUuid" in parent && parent.floorUuid === floorUuid && decal.decalType.type === 'Floor') { + updateDecalPositionInFloor(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]); + } else if (decal.decalType.type === 'Floor' && floorUuid) { + deleteDecal(decal.decalUuid, parent); + + const addedDecal = addDecalToFloor(floorUuid, { + ...decal, + decalPosition: [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]], + decalType: { type: 'Floor', floorUuid: floorUuid } + }); + + if (addedDecal) { + setSelectedDecal({ decalMesh: null, decalData: addedDecal }) + } + } else if (decal.decalType.type === 'Wall' && floorUuid) { + deleteDecal(decal.decalUuid, parent); + const floor = getFloorById(floorUuid); + if (!floor) return; + + const addedDecal = addDecalToFloor(floorUuid, { + ...decal, + decalPosition: [point.x + offset.x, point.y + offset.y, -0.001], + decalType: { type: 'Floor', floorUuid: floorUuid } + }); + + if (addedDecal) { + setSelectedDecal({ decalMesh: null, decalData: addedDecal }) + } + } + } + }); + + const handlePointerUp = (e: PointerEvent) => { + if (controls) { + (controls as CameraControls).enabled = true; + } + if (decalDragState.isDragging) { + setDecalDragState(false, null, null); + + if ('wallUuid' in parent) { + setTimeout(() => { + const updatedWall = getWallById(parent.wallUuid); + if (updatedWall) { + if (projectId && updatedWall) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + } + }, 0) + } else if ('floorUuid' in parent) { + setTimeout(() => { + const updatedFloor = parent; + + if (projectId && updatedFloor) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); + + // SOCKET + + const data = { + floorData: updatedFloor, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + }, 0) + } + } + }; + + const deleteDecal = (decalUuid: string, parent: Wall | Floor) => { + if ('wallUuid' in parent) { + const updatedWall = removeDecalInWall(decalUuid); + + if (projectId && updatedWall) { + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + } + } else if ('floorUuid' in parent) { + const updatedFloor = removeDecalInFloor(decalUuid); + + if (projectId && updatedFloor) { + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); + + // SOCKET + + const data = { + floorData: updatedFloor, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + } + } + } + + const handlePointerDown = (e: ThreeEvent) => { + if (visible && !toggleView && activeModule === 'builder') { + if (e.object.userData.decalUuid && toolMode === 'cursor') { + e.stopPropagation(); + setDecalDragState(true, decal.decalUuid, null); + if (controls) { + (controls as CameraControls).enabled = false; + } + setSelectedDecal({ decalMesh: e.object, decalData: decal }); + setSelectedWall(null); + setSelectedFloor(null); + + const localIntersect = e.object.worldToLocal(e.point.clone()); + let dragOffset = new THREE.Vector3(decal.decalPosition[0] - localIntersect.x, decal.decalPosition[1] - localIntersect.y, 0); + setDecalDragState(true, decal.decalUuid, dragOffset); + } + } + }; + + const handleClick = (e: ThreeEvent) => { + if (visible && !toggleView && activeModule === 'builder') { + if (e.object.userData.decalUuid) { + e.stopPropagation(); + if (toolMode === 'cursor') { + setSelectedDecal({ decalMesh: e.object, decalData: decal }); + setSelectedWall(null); + setSelectedFloor(null); + } else if (toolMode === '3D-Delete') { + deleteDecal(e.object.userData.decalUuid, parent); + } + } + } + }; + + const handlePointerEnter = (e: ThreeEvent) => { + if (visible && !toggleView && activeModule === 'builder') { + if (e.object.userData.decalUuid) { + e.stopPropagation(); + if (toolMode === '3D-Delete') { + setDeletableDecal(e.object); + } + } + } + }; + + const handlePointerLeave = (e: ThreeEvent) => { + if (visible && !toggleView && activeModule === 'builder') { + if (e.object.userData.decalUuid) { + e.stopPropagation(); + if (toolMode === '3D-Delete' && deletableDecal && deletableDecal?.userData.decalUuid === e.object.userData.decalUuid) { + setDeletableDecal(null); + } + } + } + }; + + const handlePointerMissed = () => { + if (selectedDecal && selectedDecal.decalMesh && selectedDecal.decalMesh.userData.decalUuid === decal.decalUuid) { + setSelectedDecal(null); + } + }; + + useEffect(() => { + const canvasElement = gl.domElement; + + if (activeModule === 'builder' && !toggleView && selectedDecal && selectedDecal.decalData.decalUuid === decal.decalUuid) { + canvasElement.addEventListener('pointerup', handlePointerUp); + } + + return () => { + canvasElement.removeEventListener('pointerup', handlePointerUp); + }; + }, [gl, activeModule, toggleView, selectedDecal, camera, controls, visible, parent, decal, decalDragState]); + + return { + handlePointerDown, + handleClick, + handlePointerEnter, + handlePointerLeave, + handlePointerMissed, + deleteDecal + }; +} \ No newline at end of file diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 2f5f77c..ca1b406 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -88,6 +88,10 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere } }, [gltfScene]); + const logModelStatus = (modelId: string, status: string) => { + // console.log(modelId, status); + } + useEffect(() => { // Calculate Bounding Box const calculateBoundingBox = (scene: THREE.Object3D) => { @@ -103,6 +107,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere clone.animations = cachedModel.animations || []; setGltfScene(clone); calculateBoundingBox(clone); + logModelStatus(assetId, 'cache-loaded'); return; } @@ -118,6 +123,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere THREE.Cache.add(assetId, gltf); setGltfScene(gltf.scene.clone()); calculateBoundingBox(gltf.scene); + logModelStatus(assetId, 'indexedDB-loaded'); }, undefined, (error) => { @@ -140,6 +146,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere THREE.Cache.add(assetId, gltf); setGltfScene(gltf.scene.clone()); calculateBoundingBox(gltf.scene); + logModelStatus(assetId, 'backend-loaded'); }) .catch((error) => { console.error( diff --git a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx index f89b38d..4ea5b43 100644 --- a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx +++ b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx @@ -1,12 +1,13 @@ import { useMemo } from "react"; -import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, } from "three"; +import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, ExtrudeGeometry, Vector3, Euler, } from "three"; import { useLoader } from "@react-three/fiber"; -import { Extrude } from "@react-three/drei"; import useModuleStore from "../../../../../store/useModuleStore"; import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; import { useToggleView } from "../../../../../store/builder/store"; import * as Constants from "../../../../../types/world/worldConstants"; +import DecalInstance from "../../../Decal/decalInstance/decalInstance"; + import texturePath from "../../../../../assets/textures/floor/white.png"; import texturePathDark from "../../../../../assets/textures/floor/black.png"; import material1 from "../../../../../assets/textures/floor/factory wall texture.jpg"; @@ -67,20 +68,26 @@ function FloorInstance({ floor }: { floor: Floor }) { }, }; - const shape = useMemo(() => { - const shape = new Shape(); + const shapeData = useMemo(() => { const points = floor.points.map((p) => new Vector2(p.position[0], p.position[2])); if (points.length < 3) return null; - shape.moveTo(points[0].x, points[0].y); - for (let i = 1; i < points.length; i++) { - shape.lineTo(points[i].x, points[i].y); + + const centroidX = points.reduce((sum, p) => sum + p.x, 0) / points.length; + const centroidY = points.reduce((sum, p) => sum + p.y, 0) / points.length; + + const relativePoints = points.map((p) => new Vector2(p.x - centroidX, p.y - centroidY)); + + const shape = new Shape(); + shape.moveTo(relativePoints[0].x, relativePoints[0].y); + for (let i = 1; i < relativePoints.length; i++) { + shape.lineTo(relativePoints[i].x, relativePoints[i].y); } - return shape; + + return { shape, center: [centroidX, centroidY] }; }, [floor]); const textureScale = Constants.floorConfig.textureScale; - // Helper function to handle texture maps and filter out null values function getMaterialMaps(material: any, defaultMap: any) { const materialMap = material.map || defaultMap; const normalMap = material.normalMap || null; @@ -90,26 +97,18 @@ function FloorInstance({ floor }: { floor: Floor }) { return [materialMap, normalMap, roughnessMap, metalnessMap].filter((texture): texture is string => texture !== null); } - // Default material map const defaultMaterialMap = materials["Default Material"].map; - // Get top and side material maps const topMaterial = materials[floor.topMaterial]; const sideMaterial = materials[floor.sideMaterial]; - // Get the filtered lists for top and side textures const topTexturesList = getMaterialMaps(topMaterial, defaultMaterialMap); const sideTexturesList = getMaterialMaps(sideMaterial, defaultMaterialMap); - // Use loader to load top and side textures const [topTexture, topNormalTexture, topRoughnessTexture, topMetalicTexture] = useLoader(TextureLoader, topTexturesList); const [sideTexture, sideNormalTexture, sideRoughnessTexture, sideMetalicTexture] = useLoader(TextureLoader, sideTexturesList); - // Early exit if materials are missing - if (!materials[floor.topMaterial] || !materials[floor.sideMaterial]) return null; - - // Combine and pair textures with their corresponding material const textureMaterialMap = [ { textures: [ @@ -131,7 +130,6 @@ function FloorInstance({ floor }: { floor: Floor }) { }, ]; - // Apply texture settings textureMaterialMap.forEach(({ textures, materialKey }) => { const tileScale = materials[materialKey]?.textureTileScale ?? [ textureScale, @@ -143,20 +141,36 @@ function FloorInstance({ floor }: { floor: Floor }) { tex.wrapS = tex.wrapT = RepeatWrapping; tex.repeat.set(tileScale[0], tileScale[1]); tex.anisotropy = 16; - // First texture is always the color map (use SRGB), others should be linear tex.colorSpace = idx < 1 ? SRGBColorSpace : NoColorSpace; }); }); - if (!shape) return null; + const geometry = useMemo(() => { + if (!shapeData) return null; + return new ExtrudeGeometry(shapeData.shape, { + depth: !floor.isBeveled ? floor.floorDepth : floor.floorDepth - 0.1, + bevelEnabled: floor.isBeveled, + bevelSegments: floor.bevelStrength, + bevelOffset: -0.1, + bevelSize: 0.1, + bevelThickness: 0.1, + }); + }, [shapeData, floor]); + + if (!geometry) return null; return ( { if (!toggleView && activeModule === "builder") { @@ -173,41 +187,32 @@ function FloorInstance({ floor }: { floor: Floor }) { } }} > - - - - + + + + {floor.decals.map((decal) => ( + + ))} ); } diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index 71ccdd4..67d5b8a 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -17,7 +17,7 @@ import material1 from '../../../../../assets/textures/floor/factory wall texture function Wall({ wall }: { readonly wall: Wall }) { const { wallStore, wallAssetStore } = useSceneContext(); - const { walls, addDecal } = wallStore(); + const { walls } = wallStore(); const { wallAssets, getWallAssetsByWall, setVisibility } = wallAssetStore(); const assets = getWallAssetsByWall(wall.wallUuid); const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore(); @@ -118,6 +118,7 @@ function Wall({ wall }: { readonly wall: Wall }) { > {(assets.length > 0 || (walls[0].wallUuid === wall.wallUuid && wallAssets.length > 0)) ? 0) return; - const decal: Decal = { - decalUuid: THREE.MathUtils.generateUUID(), - decalName: 'Decal', - decalId: "68abe2f771863f0888b0d35d", - decalPosition: [0, 0, wall.wallThickness / 2 + 0.001], - decalRotation: 0, - decalOpacity: 1, - decalScale: 1, - decalType: { type: 'Wall', wallUuid: wall.wallUuid } - } - addDecal(wall.wallUuid, decal); } } }} diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 555c7a3..24f7428 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -165,7 +165,7 @@ export default function PostProcessing() { )} {selectedDecal && ( void; // Setters - Decal - setSelectedDecal: (decal: { decalMesh: Object3D, decalData: Decal } | null) => void; + setSelectedDecal: (decal: { decalMesh: Object3D | null, decalData: Decal } | null) => void; setDeletableDecal: (decal: Object3D | null) => void; + setDecalDragState: (isDragging: boolean, draggingDecalUuid: string | null, dragOffset: Vector3 | null) => void; // Setters - Aisle General setSelectedAisle: (aisle: Object3D | null) => void; @@ -143,6 +149,11 @@ export const useBuilderStore = create()( selectedDecal: null, deletableDecal: null, + decalDragState: { + isDragging: false, + draggingDecalUuid: null, + dragOffset: null, + }, selectedAisle: null, aisleType: 'solid-aisle', @@ -290,7 +301,7 @@ export const useBuilderStore = create()( // === Setters: Decal === - setSelectedDecal: (decal: { decalMesh: Object3D, decalData: Decal } | null) => { + setSelectedDecal: (decal: { decalMesh: Object3D | null, decalData: Decal } | null) => { set((state) => { state.selectedDecal = decal; }) @@ -302,6 +313,16 @@ export const useBuilderStore = create()( }) }, + setDecalDragState: (isDragging: boolean, draggingDecalUuid: string | null, dragOffset: Vector3 | null) => { + set((state) => { + state.decalDragState = { + isDragging, + draggingDecalUuid, + dragOffset, + } + }) + }, + // === Setters: Aisle General === setSelectedAisle: (aisle: Object3D | null) => { diff --git a/app/src/store/builder/useFloorStore.ts b/app/src/store/builder/useFloorStore.ts index c7b9545..e875c78 100644 --- a/app/src/store/builder/useFloorStore.ts +++ b/app/src/store/builder/useFloorStore.ts @@ -19,8 +19,8 @@ interface FloorStore { setBevelStrength: (uuid: string, strength: number) => void; setDepth: (uuid: string, depth: number) => void; setMaterial: (uuid: string, sideMaterial: string, topMaterial: string) => void; - addDecal: (floors: string, decal: Decal) => void; - updateDecal: (decalUuid: string, decal: Decal) => Floor | undefined; + addDecal: (floors: string, decal: Decal) => Decal | undefined; + updateDecal: (decalUuid: string, decal: Partial) => Floor | undefined; removeDecal: (decalUuid: string) => Floor | undefined; updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void; updateDecalRotation: (decalUuid: string, rotation: number) => void; @@ -196,12 +196,17 @@ export const createFloorStore = () => { } }), - addDecal: (floorUuid, decal) => set(state => { - const floor = state.floors.find(f => f.floorUuid === floorUuid); - if (floor) { - floor.decals.push(decal); - } - }), + addDecal: (floorUuid, decal) => { + let addedDecal: Decal | undefined; + set(state => { + const floor = state.floors.find(f => f.floorUuid === floorUuid); + if (floor) { + floor.decals.push(decal); + addedDecal = JSON.parse(JSON.stringify(decal)); + } + }) + return addedDecal; + }, updateDecal: (decalUuid, updatedDecal) => { let affectedFloor: Floor | undefined; @@ -209,12 +214,12 @@ export const createFloorStore = () => { for (const floor of state.floors) { const index = floor.decals.findIndex(d => d.decalUuid === decalUuid); if (index !== -1) { - floor.decals[index] = updatedDecal; + Object.assign(floor.decals[index], updatedDecal); affectedFloor = JSON.parse(JSON.stringify(floor)); break; } } - }) + }); return affectedFloor; }, diff --git a/app/src/store/builder/useWallStore.ts b/app/src/store/builder/useWallStore.ts index c7c07e9..705e611 100644 --- a/app/src/store/builder/useWallStore.ts +++ b/app/src/store/builder/useWallStore.ts @@ -9,8 +9,8 @@ interface WallStore { removeWall: (uuid: string) => void; clearWalls: () => void; removeWallByPoints: (Points: [Point, Point]) => Wall | undefined; - addDecal: (wallUuid: string, decal: Decal) => void; - updateDecal: (decalUuid: string, decal: Decal) => Wall | undefined; + addDecal: (wallUuid: string, decal: Decal) => Decal | undefined; + updateDecal: (decalUuid: string, decal: Partial) => Wall | undefined; removeDecal: (decalUuid: string) => Wall | undefined; updateDecalPosition: (decalUuid: string, position: [number, number, number]) => Wall | undefined; updateDecalRotation: (decalUuid: string, rotation: number) => void; @@ -83,12 +83,17 @@ export const createWallStore = () => { return removedWall; }, - addDecal: (wallUuid, decal) => set((state) => { - const wallToUpdate = state.walls.find(w => w.wallUuid === wallUuid); - if (wallToUpdate) { - wallToUpdate.decals.push(decal); - } - }), + addDecal: (wallUuid, decal) => { + let addedDecal: Decal | undefined; + set((state) => { + const wallToUpdate = state.walls.find(w => w.wallUuid === wallUuid); + if (wallToUpdate) { + wallToUpdate.decals.push(decal); + addedDecal = JSON.parse(JSON.stringify(decal)); + } + }) + return addedDecal; + }, updateDecal: (decalUuid, decal) => { let affectedWall: Wall | undefined; From c4d1f90ee7cd0322de219d1b038f4eb4b47a91fc Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 28 Aug 2025 15:51:48 +0530 Subject: [PATCH 13/19] refactor: enhance asset search UI and improve styling for headers and buttons --- .../components/layout/sidebarLeft/Assets.tsx | 27 +++++++--- app/src/styles/layout/sidebar.scss | 49 ++++++++++++++++--- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 824062f..47e797a 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -2,7 +2,11 @@ import React, { useEffect, useState } from "react"; import Search from "../../ui/inputs/Search"; import { getCategoryAsset } from "../../../services/factoryBuilder/asset/assets/getCategoryAsset"; import { fetchAssets } from "../../../services/marketplace/fetchAssets"; -import { useDecalStore, useDroppedDecal, useSelectedItem } from "../../../store/builder/store"; +import { + useDecalStore, + useDroppedDecal, + useSelectedItem, +} from "../../../store/builder/store"; // images ------------------- import vehicle from "../../../assets/image/categories/vehicles.png"; @@ -17,6 +21,7 @@ import decal from "../../../assets/image/categories/decal.png"; import SkeletonUI from "../../templates/SkeletonUI"; import { AlertIcon, + ArrowIcon, DecalInfoIcon, HangTagIcon, NavigationIcon, @@ -164,7 +169,9 @@ const Assets: React.FC = () => {
-

Results for {searchValue}

+

+ Results for '{searchValue}' +

{categoryAssets?.map((asset: any, index: number) => ( @@ -210,7 +217,7 @@ const Assets: React.FC = () => { if (selectedCategory) { return (
-

+

{selectedCategory}

@@ -231,8 +241,9 @@ const Assets: React.FC = () => { {activeSubcategories.map((cat, index) => (
{ fetchCategoryDecals(cat.name); setSelectedSubCategory(cat.name); @@ -308,7 +319,7 @@ const Assets: React.FC = () => { category: asset.category, decalName: asset.decalName, decalImage: asset.decalImage, - decalId: asset.id + decalId: asset.id, }); }} /> @@ -337,7 +348,7 @@ const Assets: React.FC = () => { return (
-

Categories

+

Categories

{Array.from( new Set(categoryList.map((asset) => asset.category)) diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index aa8e84e..e3dd706 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -1984,17 +1984,51 @@ max-height: 50vh; overflow: auto; - h2, - .searched-content { + .header, + .searched-content, + .categories-header { color: var(--text-color); - font-family: $large; font-weight: $bold-weight; - padding: 8px; + padding: 4px 8px; @include flex-space-between; - - .back-button { - cursor: pointer; + position: sticky; + top: 0; + z-index: 4; + background: var(--background-color); + border-radius: 0 0 12px 12px; + backdrop-filter: blur(4px); + .search-for{ + display: inline-block; + color: var(--accent-color); + max-width: 238px; + overflow: hidden; + text-overflow: ellipsis; } + .back-button { + padding: 4px 12px; + border-radius: 20px; + transition: background 0.2s; + display: flex; + align-items: center; + .back-arrow { + margin-right: 2px; + transform: translateX(0) translateY(-1px) scale(1.6) rotate(90deg); + transition: all 0.2s; + } + &:hover { + background: var(--background-color-solid); + .back-arrow { + transform: translateX(-2px) translateY(-1px) scale(1.6) + rotate(90deg); + } + } + } + } + + .categories-header, + .searched-content { + position: relative; + padding: 8px; } .categories-container { @@ -2132,6 +2166,7 @@ border: 1px solid #564b69; padding: 12px 10px; border-radius: 15px; + margin-bottom: 4px; .catogory-asset-filter-wrapper { display: flex; From 92b1ba61973268f5232a5406fbd83bfbb0473aff Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 28 Aug 2025 16:01:16 +0530 Subject: [PATCH 14/19] decal bug fix --- app/src/modules/builder/Decal/decalCreator/decalCreator.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx index 221a2bf..35b54ce 100644 --- a/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx +++ b/app/src/modules/builder/Decal/decalCreator/decalCreator.tsx @@ -15,7 +15,7 @@ import { getUserData } from '../../../../functions/getUserData'; function DecalCreator() { const { wallStore, floorStore } = useSceneContext(); const { addDecal: addDecalOnWall, getWallById } = wallStore(); - const { addDecal : addDecalOnFloor, getFloorById } = floorStore(); + const { addDecal: addDecalOnFloor, getFloorById } = floorStore(); const { droppedDecal } = useDroppedDecal(); const { activeModule } = useModuleStore(); const { userId, organization } = getUserData(); @@ -36,6 +36,7 @@ function DecalCreator() { const intersects = raycaster.intersectObjects(scene.children, true); const wallIntersect = intersects.find(i => i.object.userData && i.object.userData.wallUuid); const floorIntersect = intersects.find(i => i.object.userData && i.object.userData.floorUuid); + console.log('wallIntersect: ', wallIntersect); if (wallIntersect) { const wall = getWallById(wallIntersect.object.userData.wallUuid); @@ -51,7 +52,7 @@ function DecalCreator() { type: 'Wall', wallUuid: wallIntersect.object.userData.wallUuid, }, - decalPosition: [point.x, point.y, wall.wallThickness / 2 + 0.001], + decalPosition: [point.x, point.y, (wall.wallThickness / 2 + 0.001) * (wallIntersect.normal?.z || 1)], decalRotation: 0, decalOpacity: 1, decalScale: 1, From cb87cd067b5381ef12f3345c64452019efe48f5b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 28 Aug 2025 16:32:45 +0530 Subject: [PATCH 15/19] added decal fallback --- .../assets/image/fallback/fallback decal 1.png | Bin 0 -> 19237 bytes .../assets/image/fallback/fallback decal.png | Bin 0 -> 2168 bytes .../Decal/decalInstance/decalInstance.tsx | 7 ++++--- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 app/src/assets/image/fallback/fallback decal 1.png create mode 100644 app/src/assets/image/fallback/fallback decal.png diff --git a/app/src/assets/image/fallback/fallback decal 1.png b/app/src/assets/image/fallback/fallback decal 1.png new file mode 100644 index 0000000000000000000000000000000000000000..b47f4081ed66cb815cf3fcbed4ed4aabd033e83f GIT binary patch literal 19237 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 M07*qoM6N<$f*~=E_5c6? literal 0 HcmV?d00001 diff --git a/app/src/assets/image/fallback/fallback decal.png b/app/src/assets/image/fallback/fallback decal.png new file mode 100644 index 0000000000000000000000000000000000000000..b30e9fceb33ca45dbd3e03a326271f6478fc1543 GIT binary patch literal 2168 zcmbuB=Q|q;8^(i#*hHl291>eqNQ_#ih;T}W+G^C^RP9wowNa{ym^E9o_6`nODxDVE}-JWOlkE2mktl|t1ydV3aHzS+kJHmpx>8bdMBsI-!L3g{A!tGTPY+WiUq?>M| zEs~Wsv2QQ+UK$b*FlTV^m>Ddh-FWfj;h{QOdQ?6+`V-lnHsw;c^Bv{65sTUDMh!jD zifj$iy0|&kN4jsxsX$99bDVOSJKH_=t{Nz|FPw9;ijut7*D!cOR^nKKd8T(`3p6B2 zuX9U`ar|jYGQ8hnrQ=|_=w`&L_aI{qWW19H28v}b60qhWe)(TkixR_X0cyDcA#Z$g z2T}j@c^|oGXpzoF@{igv!c7sU*6euF-{yqsyhu)9B$VsGC^V~(Q4<~<5*9vov-5-y zpFy?`!rz|QC9?`eX_hT7N;L+k!ARnDE5hzAie5bHs4Ou;d=BZiHM=xh08BP4H?<0B^~Uct4^(v~D7 zWqvwMASm@W?_70pqSKz9PIG{(g)Hz{ryatgM;c;U+zc(uJZ*%08qp#kfzJerv1iRF zMUmD=9p~X3=k7U67WGovDwz-{ZF_MwVBVb2iQsW?axsck@l_F=RM#(Eyx}etM@%Z1 zLm-i0pa5LBgq)h~qF#xx64q*ce=d!*e=F7=CG%6q#)4<7GqoowM^MP*_Uk5>DI6#= zKjevCd%d&Y@_7jLvqTrU&_;oo9NX483igyTieAD~hA>6a> z-y&p%kFzZR)X`BQO4NCWs~^trYRUC2jU65n+L_3hW?dmlBZh};Exg3O$0AIzAu*bU zP7!x?{nd*Jv$WNGHMD}Ryj|Ty`KXkrSy#$xcdi3kQ-bzxx|=zm3#Yh+7nNnUYo0hB zDsbcSv((>q!2IW5#ZJ?wk|q<|UfKjp_NbMO-Z2UJ^Kj=HR11r9Fk| z0ZIN0>bUDwThEXLP wxY395jxvpJ)Yb9nF^oTf;>JoP`bmHl%DHH#GiF5yI!b$0eAM;6Cy3dD9l{D<)hWlmY!mS2h|tr*RA^i;y8W}|JS5&yD~ zEW0?{hC=vtC#wu?*y!KL(c~&wfDjuvux!0q7e1;Jg67PmI*&wEKH(Z=$Pa6tt%K*w zyf>rwiJ@y%jLhX9f?aMS4hSitU);T^}Jw(f(BMy)m(L1&Sc)R z1G+rO@T{H7c=jbn@mgasH$pYXc7@1`cW$ zVw5-Sd(0)uNaVh`SlO0>;Fwo0fA5?>88Sf3h1i<6xz;c#kr>RQvI*vuyH3*b@y(~H z=1C-RPmkdAXxuoBdw=ja@VcA1y|cB+a%#@<%&9-uat*DLExYTzZ$*e}Ucn?|t(Tim z8lEddcM^f+gkP^zu~%L7 zId{tFpHzLgh=o6R1$U02sX2-cg7+cQP4k>A;ndJX(M#=BpCaJd)%n% zUySEv!b=tDcnIB066;u-X<0kqDp!?51In>wqM=EHenHoTNSRnN{}l>MKdDZBphANu zMXy?E>WW*X-&?E5vZt2^ZIgM1-ihOWop7LsH8IG_J% z6)xk~+;5lXpp<4dd0Zu{Nq26cYR?MT@JGmeI>+bvw yj-SZqTe&3_+=Q;jzwPy73%)v_c6dV1Sk8KL=Jnc(cP<|VfPtQ&Zmo_(?0*4CZT#*4 literal 0 HcmV?d00001 diff --git a/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx index 8d0540a..f31dddd 100644 --- a/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx +++ b/app/src/modules/builder/Decal/decalInstance/decalInstance.tsx @@ -4,9 +4,10 @@ import { useToggleView, useToolMode } from '../../../../store/builder/store'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { retrieveImage, storeImage } from '../../../../utils/indexDB/idbUtils'; -import defaultMaterial from '../../../../assets/textures/floor/wall-tex.png'; +import defaultMaterial from '../../../../assets/image/fallback/fallback decal 1.png'; import useModuleStore from '../../../../store/useModuleStore'; import { useEffect, useRef, useState } from 'react'; + import { useDecalEventHandlers } from '../eventHandler/useDecalEventHandlers'; // import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi'; @@ -111,9 +112,9 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP console.error("Error storing texture in IndexedDB:", error); } }, - undefined, + undefined, (error) => { - console.error(`Error loading texture from backend:`, error); + echo.error(`Error loading texture from backend: ${decal.decalName}`); loadDefaultTexture(); } ); From 94bec4f2f0e36bbd7499fb9ad81e506a3dfa650e Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 28 Aug 2025 18:00:00 +0530 Subject: [PATCH 16/19] added selectedAisleProperties and its backend updation --- .../layout/sidebarRight/SideBarRight.tsx | 32 +- .../properties/SelectedAisleProperties.tsx | 545 ++++++++++++++++++ .../properties/SelectedDecalProperties.tsx | 23 +- .../instance/aisleTypes/arcAisle.tsx | 14 +- .../instance/aisleTypes/arrowAisle.tsx | 14 +- .../instance/aisleTypes/arrowsAisle.tsx | 14 +- .../instance/aisleTypes/circleAisle.tsx | 14 +- .../instance/aisleTypes/dashedAisle.tsx | 14 +- .../instance/aisleTypes/dottedAisle.tsx | 14 +- .../instance/aisleTypes/junctionAisle.tsx | 14 +- .../instance/aisleTypes/solidAisle.tsx | 15 +- .../scene/postProcessing/postProcessing.tsx | 2 +- app/src/store/builder/useAisleStore.ts | 149 +++-- app/src/store/builder/useBuilderStore.ts | 6 +- 14 files changed, 765 insertions(+), 105 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index ae9c908..5e283c9 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -6,7 +6,7 @@ import { useToggleStore } from "../../../store/useUIToggleStore"; import Visualization from "./visualization/Visualization"; import Analysis from "./analysis/Analysis"; import Simulations from "./simulation/Simulations"; -import useVersionHistoryVisibleStore, { useDecalStore, useSaveVersion, useSelectedFloorItem, useToolMode, } from "../../../store/builder/store"; +import useVersionHistoryVisibleStore, { useSaveVersion, useSelectedFloorItem, useToolMode } from "../../../store/builder/store"; import { useSelectedEventData, useSelectedEventSphere, } from "../../../store/simulation/useSimulationStore"; import { useBuilderStore } from "../../../store/builder/useBuilderStore"; import GlobalProperties from "./properties/GlobalProperties"; @@ -19,8 +19,9 @@ import WallProperties from "./properties/WallProperties"; import FloorProperties from "./properties/FloorProperties"; import SelectedWallProperties from "./properties/SelectedWallProperties"; import SelectedFloorProperties from "./properties/SelectedFloorProperties"; -import ResourceManagement from "./resourceManagement/ResourceManagement"; import SelectedDecalProperties from "./properties/SelectedDecalProperties"; +import SelectedAisleProperties from "./properties/SelectedAisleProperties"; +import ResourceManagement from "./resourceManagement/ResourceManagement"; type DisplayComponent = | "versionHistory" @@ -31,12 +32,13 @@ type DisplayComponent = | "assetProperties" | "selectedWallProperties" | "selectedFloorProperties" + | "selectedDecalProperties" + | "selectedAisleProperties" | "zoneProperties" | "simulations" | "mechanics" | "analysis" | "visualization" - | "selectedDecalProperties" | "resourceManagement" | "none"; @@ -110,23 +112,27 @@ const SideBarRight: React.FC = () => { setDisplayComponent("assetProperties"); return; } - if (!selectedFloorItem && !selectedFloor && !selectedAisle && selectedWall) { + if (!selectedFloorItem && !selectedFloor && !selectedAisle && !selectedDecal && selectedWall) { setDisplayComponent("selectedWallProperties"); return; } - if (!selectedFloorItem && !selectedWall && !selectedAisle && selectedFloor) { + if (!selectedFloorItem && !selectedWall && !selectedAisle && !selectedDecal && selectedFloor) { setDisplayComponent("selectedFloorProperties"); return; } - if (viewVersionHistory) { + if (viewVersionHistory && !selectedFloorItem && !selectedWall && !selectedAisle && !selectedFloor && !selectedDecal) { setDisplayComponent("versionHistory"); return; } - if (selectedDecal) { + if (!selectedFloorItem && !selectedFloor && !selectedAisle && !selectedWall && selectedDecal) { setDisplayComponent("selectedDecalProperties"); return; } - if (!selectedFloorItem && !selectedFloor && !selectedWall && !selectedDecal) { + if (!selectedFloorItem && !selectedFloor && !selectedWall && !selectedDecal && selectedAisle) { + setDisplayComponent("selectedAisleProperties"); + return; + } + if (!selectedFloorItem && !selectedFloor && !selectedWall && !selectedDecal && !selectedAisle) { if (toolMode === "Aisle") { setDisplayComponent("aisleProperties"); return; @@ -166,12 +172,16 @@ const SideBarRight: React.FC = () => { return ; case "assetProperties": return ; + case "zoneProperties": + return ; case "selectedWallProperties": return ; case "selectedFloorProperties": return ; - case "zoneProperties": - return ; + case "selectedDecalProperties": + return ; + case "selectedAisleProperties": + return ; case "simulations": return ; case "mechanics": @@ -180,8 +190,6 @@ const SideBarRight: React.FC = () => { return ; case "visualization": return ; - case "selectedDecalProperties": - return ; case "resourceManagement": return ; default: diff --git a/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx new file mode 100644 index 0000000..c1e1d24 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx @@ -0,0 +1,545 @@ +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; +import { ArrowIcon } from "../../../icons/ExportCommonIcons"; + +// image imports +import Arc from "../../../../assets/image/aisleTypes/Arc.png"; +import Arrow from "../../../../assets/image/aisleTypes/Arrow.png"; +import Arrows from "../../../../assets/image/aisleTypes/Arrows.png"; +import Circle from "../../../../assets/image/aisleTypes/Circle.png"; +import Dashed from "../../../../assets/image/aisleTypes/Dashed.png"; +import Directional from "../../../../assets/image/aisleTypes/Directional.png"; +import Dotted from "../../../../assets/image/aisleTypes/Dotted.png"; +import Solid from "../../../../assets/image/aisleTypes/Solid.png"; +import InputToggle from "../../../ui/inputs/InputToggle"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; +import { useSceneContext } from "../../../../modules/scene/sceneContext"; +import { useVersionContext } from "../../../../modules/builder/version/versionContext"; +import { useSocketStore } from "../../../../store/builder/store"; +import { getUserData } from "../../../../functions/getUserData"; + +// import { upsertAisleApi } from "../../../../services/factoryBuilder/aisle/upsertAisleApi"; + +interface TextureItem { + color: string; + id: AisleColors; + brief: string; + texture: string; +} + +const SelectedAisleProperties: React.FC = () => { + const [collapsePresets, setCollapsePresets] = useState(false); + const [collapseTexture, setCollapseTexture] = useState(true); + const { aisleStore } = useSceneContext(); + const { getAisleById, updateAisle, setDashedAisleProperties, setDottedAisleProperties, setArrowsAisleProperties, setArcAisleWidth, setColor } = aisleStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { socket } = useSocketStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const [selectedAisleData, setSelectedAisleData] = useState(); + + const { selectedAisle, setSelectedAisle } = useBuilderStore(); + + useEffect(() => { + const aisleData = getAisleById(selectedAisle?.aisleMesh?.uuid || ""); + setSelectedAisleData(aisleData); + }, [selectedAisle, getAisleById]); + + if (!selectedAisleData) return null; + + const updateBackend = (updatedAisle: Aisle) => { + if (updatedAisle && projectId) { + + // API + + // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + const data = { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: updatedAisle.aisleUuid, + points: updatedAisle.points, + type: updatedAisle.type + } + + socket.emit('v1:model-aisle:add', data); + } + } + + const aisleTextureList: TextureItem[] = [ + { color: "yellow", id: "yellow", brief: "pedestrian walkways", texture: "" }, + { color: "gray", id: "gray", brief: "basic", texture: "" }, + { color: "green", id: "green", brief: "pedestrian walkways", texture: "" }, + { color: "orange", id: "orange", brief: "material flow", texture: "" }, + { color: "blue", id: "blue", brief: "vehicle paths", texture: "" }, + { color: "purple", id: "purple", brief: "material flow", texture: "" }, + { color: "red", id: "red", brief: "safety zone", texture: "" }, + { color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" }, + { color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" }, + { color: "white-black", id: "white-black", brief: "utility aisles", texture: "" }, + ]; + + const aisleTypes: { + name: string; + type: AisleTypes; + id: string; + thumbnail: string; + }[] = [ + { name: "Solid", type: "solid-aisle", id: "1", thumbnail: Solid }, + { name: "Dotted", type: "dotted-aisle", id: "2", thumbnail: Dotted }, + { name: "Dashed", type: "dashed-aisle", id: "3", thumbnail: Dashed }, + { name: "Arrow", type: "arrow-aisle", id: "4", thumbnail: Arrow }, + { name: "Continuous Arrows", type: "arrows-aisle", id: "5", thumbnail: Arrows }, + { name: "Directional", type: "junction-aisle", id: "6", thumbnail: Directional }, + { name: "Arc", type: "arc-aisle", id: "7", thumbnail: Arc }, + { name: "Circle", type: "circle-aisle", id: "8", thumbnail: Circle }, + ]; + + const createAisleTypeObject = (newType: AisleTypes, currentType: AisleType): AisleType => { + switch (newType) { + case 'solid-aisle': + return { + aisleType: 'solid-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1 + } as SolidAisle; + + case 'dashed-aisle': + return { + aisleType: 'dashed-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1, + dashLength: 'dashLength' in currentType ? (currentType as DashedAisle).dashLength : 0.5, + gapLength: 'gapLength' in currentType ? (currentType as DashedAisle).gapLength : 0.3 + } as DashedAisle; + + case 'dotted-aisle': + return { + aisleType: 'dotted-aisle', + aisleColor: currentType.aisleColor, + dotRadius: 'dotRadius' in currentType ? (currentType as DottedAisle).dotRadius : 0.1, + gapLength: 'gapLength' in currentType ? (currentType as DottedAisle).gapLength : 0.3 + } as DottedAisle; + + case 'arrow-aisle': + return { + aisleType: 'arrow-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1 + } as ArrowAisle; + + case 'arrows-aisle': + return { + aisleType: 'arrows-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1, + aisleLength: 'aisleLength' in currentType ? (currentType as ArrowsAisle).aisleLength : 0.6, + gapLength: 'gapLength' in currentType ? (currentType as ArrowsAisle).gapLength : 0.3 + } as ArrowsAisle; + + case 'arc-aisle': + return { + aisleType: 'arc-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1, + isFlipped: 'isFlipped' in currentType ? (currentType as ArcAisle).isFlipped : false + } as ArcAisle; + + case 'circle-aisle': + return { + aisleType: 'circle-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1 + } as CircleAisle; + + case 'junction-aisle': + return { + aisleType: 'junction-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1, + isFlipped: 'isFlipped' in currentType ? (currentType as JunctionAisle).isFlipped : false + } as JunctionAisle; + + default: + return { + aisleType: 'solid-aisle', + aisleColor: currentType.aisleColor, + aisleWidth: 0.1 + } as SolidAisle; + } + }; + + const handleAisleTypeChange = (newType: AisleTypes) => { + if (!selectedAisle?.aisleData) return; + const newAisleType = createAisleTypeObject(newType, selectedAisleData.type); + + const updatedAisle = updateAisle(selectedAisleData.aisleUuid, { + type: newAisleType + }); + + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: null }); + + setSelectedAisleData({ + ...selectedAisleData, + type: newAisleType + }); + + if (updatedAisle) { + updateBackend(updatedAisle); + } + }; + + const handleColorChange = (value: AisleColors) => { + const updatedAisle = setColor(selectedAisleData.aisleUuid, value); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...selectedAisleData.type, + aisleColor: value + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + }; + + const handleAisleWidthChange = (value: string) => { + const width = parseFloat(value); + if (!isNaN(width) && selectedAisleData.type.aisleType !== 'dotted-aisle') { + const updatedAisle = updateAisle(selectedAisleData.aisleUuid, { + type: { + ...selectedAisleData.type, + aisleWidth: width + } + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...selectedAisleData.type, + aisleWidth: width + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } + }; + + const handleDashLengthChange = (value: string) => { + const length = parseFloat(value); + if (!isNaN(length) && selectedAisleData.type.aisleType === 'dashed-aisle') { + const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, { + dashLength: length + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...(selectedAisleData.type as DashedAisle), + dashLength: length + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } + }; + + const handleGapLengthChange = (value: string) => { + const length = parseFloat(value); + if (!isNaN(length) && (selectedAisleData.type.aisleType === 'dashed-aisle' || selectedAisleData.type.aisleType === 'dotted-aisle' || selectedAisleData.type.aisleType === 'arrows-aisle')) { + if (selectedAisleData.type.aisleType === 'dashed-aisle') { + const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, { + gapLength: length + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...(selectedAisleData.type as DashedAisle), + gapLength: length + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } else if (selectedAisleData.type.aisleType === 'dotted-aisle') { + const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, { + gapLength: length + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...(selectedAisleData.type as DottedAisle), + gapLength: length + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } else if (selectedAisleData.type.aisleType === 'arrows-aisle') { + const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, { + gapLength: length + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...(selectedAisleData.type as ArrowsAisle), + gapLength: length + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } + } + }; + + const handleDotRadiusChange = (value: string) => { + const radius = parseFloat(value); + if (!isNaN(radius) && selectedAisleData.type.aisleType === 'dotted-aisle') { + const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, { + dotRadius: radius + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...(selectedAisleData.type as DottedAisle), + dotRadius: radius + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } + }; + + const handleAisleLengthChange = (value: string) => { + const length = parseFloat(value); + if (!isNaN(length) && selectedAisleData.type.aisleType === 'arrows-aisle') { + const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, { + aisleLength: length + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...(selectedAisleData.type as ArrowsAisle), + aisleLength: length + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } + }; + + const handleIsFlippedChange = () => { + if (selectedAisleData.type.aisleType === 'arc-aisle' || selectedAisleData.type.aisleType === 'junction-aisle') { + const currentType = selectedAisleData.type as ArcAisle | JunctionAisle; + const currentFlipped = currentType.isFlipped || false; + const updatedAisle = setArcAisleWidth(selectedAisleData.aisleUuid, { + isFlipped: !currentFlipped + }); + + setSelectedAisleData({ + ...selectedAisleData, + type: { + ...currentType, + isFlipped: !currentFlipped + } + }) + + if (updatedAisle) { + updateBackend(updatedAisle); + } + } + }; + + const renderAdvancedProperties = () => { + switch (selectedAisleData.type.aisleType) { + case 'dashed-aisle': + const dashedType = selectedAisleData.type as DashedAisle; + return ( + <> + + + + ); + case 'dotted-aisle': + const dottedType = selectedAisleData.type as DottedAisle; + return ( + <> + + + + ); + case 'arrows-aisle': + const arrowsType = selectedAisleData.type as ArrowsAisle; + return ( + <> + + + + ); + case 'junction-aisle': + case 'arc-aisle': + const flippedType = selectedAisleData.type as ArcAisle | JunctionAisle; + return ( + + ); + default: + return null; + } + }; + + return ( +
+
Properties
+ + {/* Basic Properties */} +
+ {selectedAisleData.type.aisleType !== 'dotted-aisle' && + + } + {renderAdvancedProperties()} +
+ + {/* Presets */} +
+ + {!collapsePresets && ( +
+ {aisleTypes.map((val) => ( +
+ +
+ ))} +
+ )} +
+ + {/* Texture */} +
+ + + {collapseTexture && ( +
+ {aisleTextureList.map((val) => ( + + ))} +
+ )} +
+
+ ); +}; + +export default SelectedAisleProperties; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx index 99c16da..e605363 100644 --- a/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/SelectedDecalProperties.tsx @@ -5,7 +5,6 @@ import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons"; import { useSocketStore } from "../../../../store/builder/store"; import InputRange from "../../../ui/inputs/InputRange"; -import RotationInput from "../customInput/RotationInput"; import { getUserData } from "../../../../functions/getUserData"; // import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi"; @@ -134,18 +133,22 @@ const SelectedDecalProperties = () => {
Decal Properties
- { handleRotationChange(parseFloat(e)) }} + handleRotationChange(value)} /> - { handleScaleChange(parseFloat(e)) }} + + handleScaleChange(value)} />
diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx index bc09b68..d791f8f 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arcAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Extrude } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function ArcAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) const arc = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'arc-aisle') return null; @@ -63,8 +69,8 @@ function ArcAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } } diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx index 49bf666..115edc6 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Extrude } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function ArrowAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) const arrow = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null; @@ -50,8 +56,8 @@ function ArrowAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } } diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx index 6908cf3..3700d55 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Instances, Instance } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) const { arrowGeometry, arrowInstances } = useMemo(() => { const result = { @@ -68,8 +74,8 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } }; diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx index af852e8..d9bc2d2 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/circleAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Extrude } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function CircleAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) const circle = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null; @@ -38,8 +44,8 @@ function CircleAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } } diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx index 72fb3c7..b00c87b 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Instances, Instance } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function DashedAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) const dashInstances = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return []; @@ -43,8 +49,8 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } }; diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx index e2b264a..09557aa 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Instance, Instances } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function DottedAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) const dotPositions = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return []; @@ -27,8 +33,8 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } } diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx index a3f8bdf..8d45763 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/junctionAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Extrude } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function JunctionAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) const arrows = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null; @@ -85,8 +91,8 @@ function JunctionAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } } diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx index e9321b0..e43f717 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/solidAisle.tsx @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { Extrude } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; @@ -8,7 +8,14 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore function SolidAisle({ aisle }: { readonly aisle: Aisle }) { const aisleRef = useRef(null); const { toolMode } = useToolMode(); - const { setSelectedAisle, hoveredPoint } = useBuilderStore(); + const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore(); + + useEffect(() => { + if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) { + setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current }); + } + }, [selectedAisle]) + const shape = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'solid-aisle') return null; @@ -35,8 +42,8 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) { }, [aisle]); const handleClick = () => { - if (toolMode === 'move' && !hoveredPoint) { - setSelectedAisle(aisleRef.current); + if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) { + setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle }); } } diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 24f7428..e6d656d 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -120,7 +120,7 @@ export default function PostProcessing() { )} {selectedAisle && ( void; - addAisle: (aisle: Aisle) => void; - updateAisle: (uuid: string, updated: Partial) => void; - removeAisle: (uuid: string) => void; + setAisles: (aisles: Aisles) => Aisles; + addAisle: (aisle: Aisle) => Aisle; + updateAisle: (uuid: string, updated: Partial) => Aisle | undefined; + removeAisle: (uuid: string) => Aisle | undefined; removePoint: (uuid: string) => Aisles; - clearAisles: () => void; + clearAisles: () => Aisles; setPosition: ( pointUuid: string, position: [number, number, number] ) => Aisle | undefined; - setLayer: (pointUuid: string, layer: number) => void; - setColor: (aisleUuid: string, color: AisleColors) => void; + setLayer: (pointUuid: string, layer: number) => Aisle | undefined; + setColor: (aisleUuid: string, color: AisleColors) => Aisle | undefined; - // Type-specific setters - setSolidAisleWidth: (aisleUuid: string, width: number) => void; + setSolidAisleWidth: (aisleUuid: string, width: number) => Aisle | undefined; setDashedAisleProperties: ( aisleUuid: string, props: { aisleWidth?: number; dashLength?: number; gapLength?: number } - ) => void; + ) => Aisle | undefined; setDottedAisleProperties: ( aisleUuid: string, props: { dotRadius?: number; gapLength?: number } - ) => void; - setArrowAisleWidth: (aisleUuid: string, width: number) => void; + ) => Aisle | undefined; + setArrowAisleWidth: (aisleUuid: string, width: number) => Aisle | undefined; setArrowsAisleProperties: ( aisleUuid: string, props: { aisleWidth?: number; aisleLength?: number; gapLength?: number } - ) => void; + ) => Aisle | undefined; setArcAisleWidth: ( aisleUuid: string, props: { aisleWidth?: number; isFlipped: boolean } - ) => void; - setCircleAisleWidth: (aisleUuid: string, width: number) => void; + ) => Aisle | undefined; + setCircleAisleWidth: (aisleUuid: string, width: number) => Aisle | undefined; setJunctionAisleProperties: ( aisleUuid: string, props: { aisleWidth?: number; isFlipped: boolean } - ) => void; + ) => Aisle | undefined; getAisleById: (uuid: string) => Aisle | undefined; getAislesByPointId: (uuid: string) => Aisle[] | []; @@ -53,28 +52,43 @@ export const createAisleStore = () => { immer((set, get) => ({ aisles: [], - setAisles: (aisles) => + setAisles: (aisles) => { set((state) => { state.aisles = aisles; - }), + }); + return aisles; + }, - addAisle: (aisle) => + addAisle: (aisle) => { set((state) => { state.aisles.push(aisle); - }), + }); + return aisle; + }, - updateAisle: (uuid, updated) => + updateAisle: (uuid, updated) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === uuid); if (aisle) { Object.assign(aisle, updated); + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - removeAisle: (uuid) => + removeAisle: (uuid) => { + let removedAisle: Aisle | undefined; set((state) => { - state.aisles = state.aisles.filter((a) => a.aisleUuid !== uuid); - }), + const index = state.aisles.findIndex((a) => a.aisleUuid === uuid); + if (index !== -1) { + removedAisle = JSON.parse(JSON.stringify(state.aisles[index])); + state.aisles.splice(index, 1); + } + }); + return removedAisle; + }, removePoint: (uuid) => { const removedAisles: Aisle[] = []; @@ -94,9 +108,11 @@ export const createAisleStore = () => { }, clearAisles: () => { + const clearedAisles = get().aisles; set((state) => { state.aisles = []; - }) + }); + return clearedAisles; }, setPosition: (pointUuid, position) => { @@ -113,34 +129,46 @@ export const createAisleStore = () => { return updatedAisle; }, - setLayer: (pointUuid, layer) => + setLayer: (pointUuid, layer) => { + let updatedAisle: Aisle | undefined; set((state) => { for (const aisle of state.aisles) { const point = aisle.points.find((p) => p.pointUuid === pointUuid); if (point) { point.layer = layer; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } } - }), + }); + return updatedAisle; + }, - setColor: (aisleUuid, color) => + setColor: (aisleUuid, color) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle) { aisle.type.aisleColor = color; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - // Type-specific property setters - setSolidAisleWidth: (aisleUuid, width) => + setSolidAisleWidth: (aisleUuid, width) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "solid-aisle") { aisle.type.aisleWidth = width; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - setDashedAisleProperties: (aisleUuid, props) => + setDashedAisleProperties: (aisleUuid, props) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "dashed-aisle") { @@ -150,10 +178,14 @@ export const createAisleStore = () => { aisle.type.dashLength = props.dashLength; if (props.gapLength !== undefined) aisle.type.gapLength = props.gapLength; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - setDottedAisleProperties: (aisleUuid, props) => + setDottedAisleProperties: (aisleUuid, props) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "dotted-aisle") { @@ -161,18 +193,26 @@ export const createAisleStore = () => { aisle.type.dotRadius = props.dotRadius; if (props.gapLength !== undefined) aisle.type.gapLength = props.gapLength; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - setArrowAisleWidth: (aisleUuid, width) => + setArrowAisleWidth: (aisleUuid, width) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "arrow-aisle") { aisle.type.aisleWidth = width; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - setArrowsAisleProperties: (aisleUuid, props) => + setArrowsAisleProperties: (aisleUuid, props) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "arrows-aisle") { @@ -182,10 +222,14 @@ export const createAisleStore = () => { aisle.type.aisleLength = props.aisleLength; if (props.gapLength !== undefined) aisle.type.gapLength = props.gapLength; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - setArcAisleWidth: (aisleUuid, props) => + setArcAisleWidth: (aisleUuid, props) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "arc-aisle") { @@ -193,18 +237,26 @@ export const createAisleStore = () => { aisle.type.aisleWidth = props.aisleWidth; if (props.isFlipped !== undefined) aisle.type.isFlipped = props.isFlipped; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - setCircleAisleWidth: (aisleUuid, width) => + setCircleAisleWidth: (aisleUuid, width) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "circle-aisle") { aisle.type.aisleWidth = width; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, - setJunctionAisleProperties: (aisleUuid, props) => + setJunctionAisleProperties: (aisleUuid, props) => { + let updatedAisle: Aisle | undefined; set((state) => { const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid); if (aisle && aisle.type.aisleType === "junction-aisle") { @@ -212,8 +264,11 @@ export const createAisleStore = () => { aisle.type.aisleWidth = props.aisleWidth; if (props.isFlipped !== undefined) aisle.type.isFlipped = props.isFlipped; + updatedAisle = JSON.parse(JSON.stringify(aisle)); } - }), + }); + return updatedAisle; + }, getAisleById: (uuid) => { return get().aisles.find((a) => a.aisleUuid === uuid); diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 476631d..376689b 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -47,7 +47,7 @@ interface BuilderState { }, // Aisle General - selectedAisle: Object3D | null; + selectedAisle: {aisleMesh: Object3D | null, aisleData: Aisle} | null; aisleType: AisleTypes; aisleWidth: number; aisleColor: AisleColors; @@ -97,7 +97,7 @@ interface BuilderState { setDecalDragState: (isDragging: boolean, draggingDecalUuid: string | null, dragOffset: Vector3 | null) => void; // Setters - Aisle General - setSelectedAisle: (aisle: Object3D | null) => void; + setSelectedAisle: (aisle: {aisleMesh: Object3D | null, aisleData: Aisle} | null) => void; setAisleType: (type: AisleTypes) => void; setAisleWidth: (width: number) => void; setAisleColor: (color: AisleColors) => void; @@ -325,7 +325,7 @@ export const useBuilderStore = create()( // === Setters: Aisle General === - setSelectedAisle: (aisle: Object3D | null) => { + setSelectedAisle: (aisle: {aisleMesh: Object3D | null, aisleData: Aisle} | null) => { set((state) => { state.selectedAisle = aisle; }); From 7bbb221c663f4f5cfab16aabbbd2cc305d821dd2 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 28 Aug 2025 18:06:34 +0530 Subject: [PATCH 17/19] - asset icons updated - aisle props changes - aisle style updated --- app/src/components/icons/AssetTypeIcons.tsx | 353 ++++++++++++++++++ .../components/icons/ExportCommonIcons.tsx | 87 ----- .../components/layout/sidebarLeft/Assets.tsx | 4 +- .../properties/AisleProperties.tsx | 21 +- .../hrm/assetManagement/AssetManagement.tsx | 15 +- app/src/components/ui/Tools.tsx | 2 +- app/src/store/builder/useBuilderStore.ts | 2 +- app/src/styles/layout/sidebar.scss | 24 +- app/src/types/builderTypes.d.ts | 2 +- 9 files changed, 400 insertions(+), 110 deletions(-) create mode 100644 app/src/components/icons/AssetTypeIcons.tsx diff --git a/app/src/components/icons/AssetTypeIcons.tsx b/app/src/components/icons/AssetTypeIcons.tsx new file mode 100644 index 0000000..2f32358 --- /dev/null +++ b/app/src/components/icons/AssetTypeIcons.tsx @@ -0,0 +1,353 @@ +export const ForkLiftIcon = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const ConveyorIcon = () => { + return ( + + + + + + + + + + + + + + + ); +}; + +export const RoboticArmIcon = () => { + return ( + + + + + + + + + + + + + + + + + ); +}; + +export const MachineIcon = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +type TypeBasedAssetIconsProps = { + assetType: string; +}; + +export function TypeBasedAssetIcons({ assetType }: TypeBasedAssetIconsProps) { + console.log("assetType: ", assetType); + return ( +
+ {assetType === "machine" && } + {assetType === "vehicle" && } + {assetType === "transfer" && } + {assetType === "roboticArm" && } +
+ ); +} diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index 72f9ca0..eaf1cf4 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -1697,93 +1697,6 @@ export const TargetIcon = () => { ); }; -export const ForkLiftIcon = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; export const RightHalfFillCircleIcon = () => { return ( {

- Results for '{searchValue}' + Results for{" "} + '{searchValue}'

@@ -256,7 +257,6 @@ const Assets: React.FC = () => {
)} - {selectedCategory !== "Decals" && !selectedSubCategory ? (
{categoryAssets?.map((asset: any, index: number) => ( diff --git a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx index c343805..2fb8042 100644 --- a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx @@ -28,13 +28,13 @@ const AisleProperties: React.FC = () => { const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength, setIsFlipped } = useBuilderStore(); const aisleTextureList: TextureItem[] = [ - { color: "yellow", id: "yellow", brief: "pedestrian walkways", texture: "" }, - { color: "gray", id: "gray", brief: "basic", texture: "" }, - { color: "green", id: "green", brief: "pedestrian walkways", texture: "" }, - { color: "orange", id: "orange", brief: "material flow", texture: "" }, - { color: "blue", id: "blue", brief: "vehicle paths", texture: "" }, - { color: "purple", id: "purple", brief: "material flow", texture: "" }, - { color: "red", id: "red", brief: "safety zone", texture: "" }, + { color: "yellow", id: "#FBE50E", brief: "pedestrian walkways", texture: "" }, + { color: "gray", id: "#6F6F7A", brief: "basic", texture: "" }, + { color: "green", id: "#43C06D", brief: "pedestrian walkways", texture: "" }, + { color: "orange", id: "#FF711B", brief: "material flow", texture: "" }, + { color: "blue", id: "#488EF6", brief: "vehicle paths", texture: "" }, + { color: "purple", id: "#AF52DE", brief: "material flow", texture: "" }, + { color: "red", id: "#FF3B30", brief: "safety zone", texture: "" }, { color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" }, { color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" }, { color: "white-black", id: "white-black", brief: "utility aisles", texture: "" }, @@ -285,7 +285,12 @@ const AisleProperties: React.FC = () => { onClick={() => setAisleColor(val.id)} aria-pressed={aisleColor === val.id} > -
{val.texture}
+
+ {val.texture} +
{val.color}
{`( ${val.brief} )`}
diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx index faa6015..718915c 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx @@ -1,18 +1,19 @@ import { useEffect, useState } from 'react' // import NavigateCatagory from '../../NavigateCatagory' -import { EyeIcon, ForkLiftIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons'; +import { EyeIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons'; import assetImage from "../../../../../../assets/image/asset-image.png" import { useSceneContext } from '../../../../../../modules/scene/sceneContext'; import { useProductContext } from '../../../../../../modules/simulation/products/productContext'; import RenameInput from '../../../../../ui/inputs/RenameInput'; import { useResourceManagementId } from '../../../../../../store/builder/store'; +import { TypeBasedAssetIcons } from '../../../../../icons/AssetTypeIcons'; const AssetManagement = () => { // const [selectedCategory, setSelectedCategory] = useState("All Assets"); const [expandedAssetId, setExpandedAssetId] = useState(null); const [assets, setAssets] = useState([]); const { productStore } = useSceneContext(); - const { products, getProductById } = productStore(); + const { getProductById } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { setResourceManagementId } = useResourceManagementId(); @@ -30,6 +31,7 @@ const AssetManagement = () => { grouped[asset.modelName] = { id: asset.modelUuid, name: asset.modelName, + type: asset.type, model: asset.modelCode || "N/A", status: asset.status || "Online", usageRate: asset.usageRate || 15, @@ -46,6 +48,7 @@ const AssetManagement = () => { setAssets(Object.values(grouped)); } + // eslint-disable-next-line }, [selectedProduct]); function handleRenameAsset(newName: string) { @@ -130,7 +133,7 @@ const AssetManagement = () => { : -
+
}
@@ -210,16 +213,10 @@ const AssetManagement = () => {
{expandedAssetId === asset.id ? "View Less" : "View More"}
-
- -
- )} -
- ))}
diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index b89f2ea..4d29ad0 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -374,7 +374,7 @@ const Tools: React.FC = () => { )}
- {activeModule !== "visualization" && ( + {toggleThreeD && activeModule !== "visualization" && ( <>
diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 476631d..efda83a 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -158,7 +158,7 @@ export const useBuilderStore = create()( selectedAisle: null, aisleType: 'solid-aisle', aisleWidth: 0.1, - aisleColor: 'yellow', + aisleColor: '#FBE50E', dashLength: 0.5, gapLength: 0.3, diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index e3dd706..34f5b6d 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -1500,6 +1500,28 @@ border-radius: #{$border-radius-large}; margin-right: 4px; overflow: hidden; + &.yellow-black { + background-color: black; + background-size: 10px 10px; + background-image: repeating-linear-gradient( + 45deg, + #FBE50E 0, + #FBE50E 2px, + black 0, + black 50% + ); + } + &.white-black { + background-color: black; + background-size: 10px 10px; + background-image: repeating-linear-gradient( + 45deg, + white 0, + white 2px, + black 0, + black 50% + ); + } } .aisle-color { @@ -1997,7 +2019,7 @@ background: var(--background-color); border-radius: 0 0 12px 12px; backdrop-filter: blur(4px); - .search-for{ + .search-for { display: inline-block; color: var(--accent-color); max-width: 238px; diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index e0440ec..7d5eefb 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -151,7 +151,7 @@ type Zones = Zone[]; type AisleTypes = 'solid-aisle' | 'dashed-aisle' | 'stripped-aisle' | 'dotted-aisle' | 'arrow-aisle' | 'arrows-aisle' | 'arc-aisle' | 'circle-aisle' | 'junction-aisle'; -type AisleColors = 'gray' | 'yellow' | 'green' | 'orange' | 'blue' | 'purple' | 'red' | '#66FF00' | 'yellow-black' | 'white-black' +type AisleColors = '#6F6F7A' | '#FBE50E' | '#43C06D' | '#FF711B' | '#488EF6' | '#AF52DE' | '#FF3B30' | '#66FF00' | 'yellow-black' | 'white-black' interface SolidAisle { aisleType: 'solid-aisle'; From 982a8ef4aa2fd8e0befdf4b209b8779f2cd782a0 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 28 Aug 2025 18:11:54 +0530 Subject: [PATCH 18/19] aisle props update --- .../properties/AisleProperties.tsx | 25 ++++++++-------- .../properties/SelectedAisleProperties.tsx | 30 +++++-------------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx index 2fb8042..ca2317d 100644 --- a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx @@ -21,24 +21,25 @@ interface TextureItem { texture: string; } +export const aisleTextureList: TextureItem[] = [ + { color: "yellow", id: "#FBE50E", brief: "pedestrian walkways", texture: "" }, + { color: "gray", id: "#6F6F7A", brief: "basic", texture: "" }, + { color: "green", id: "#43C06D", brief: "pedestrian walkways", texture: "" }, + { color: "orange", id: "#FF711B", brief: "material flow", texture: "" }, + { color: "blue", id: "#488EF6", brief: "vehicle paths", texture: "" }, + { color: "purple", id: "#AF52DE", brief: "material flow", texture: "" }, + { color: "red", id: "#FF3B30", brief: "safety zone", texture: "" }, + { color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" }, + { color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" }, + { color: "white-black", id: "white-black", brief: "utility aisles", texture: "" }, +]; + const AisleProperties: React.FC = () => { const [collapsePresets, setCollapsePresets] = useState(false); const [collapseTexture, setCollapseTexture] = useState(true); const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength, setIsFlipped } = useBuilderStore(); - const aisleTextureList: TextureItem[] = [ - { color: "yellow", id: "#FBE50E", brief: "pedestrian walkways", texture: "" }, - { color: "gray", id: "#6F6F7A", brief: "basic", texture: "" }, - { color: "green", id: "#43C06D", brief: "pedestrian walkways", texture: "" }, - { color: "orange", id: "#FF711B", brief: "material flow", texture: "" }, - { color: "blue", id: "#488EF6", brief: "vehicle paths", texture: "" }, - { color: "purple", id: "#AF52DE", brief: "material flow", texture: "" }, - { color: "red", id: "#FF3B30", brief: "safety zone", texture: "" }, - { color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" }, - { color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" }, - { color: "white-black", id: "white-black", brief: "utility aisles", texture: "" }, - ]; const aisleTypes: { name: string; diff --git a/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx index c1e1d24..3dc2540 100644 --- a/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/SelectedAisleProperties.tsx @@ -18,15 +18,7 @@ import { useSceneContext } from "../../../../modules/scene/sceneContext"; import { useVersionContext } from "../../../../modules/builder/version/versionContext"; import { useSocketStore } from "../../../../store/builder/store"; import { getUserData } from "../../../../functions/getUserData"; - -// import { upsertAisleApi } from "../../../../services/factoryBuilder/aisle/upsertAisleApi"; - -interface TextureItem { - color: string; - id: AisleColors; - brief: string; - texture: string; -} +import { aisleTextureList } from "./AisleProperties"; const SelectedAisleProperties: React.FC = () => { const [collapsePresets, setCollapsePresets] = useState(false); @@ -72,19 +64,6 @@ const SelectedAisleProperties: React.FC = () => { } } - const aisleTextureList: TextureItem[] = [ - { color: "yellow", id: "yellow", brief: "pedestrian walkways", texture: "" }, - { color: "gray", id: "gray", brief: "basic", texture: "" }, - { color: "green", id: "green", brief: "pedestrian walkways", texture: "" }, - { color: "orange", id: "orange", brief: "material flow", texture: "" }, - { color: "blue", id: "blue", brief: "vehicle paths", texture: "" }, - { color: "purple", id: "purple", brief: "material flow", texture: "" }, - { color: "red", id: "red", brief: "safety zone", texture: "" }, - { color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" }, - { color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" }, - { color: "white-black", id: "white-black", brief: "utility aisles", texture: "" }, - ]; - const aisleTypes: { name: string; type: AisleTypes; @@ -530,7 +509,12 @@ const SelectedAisleProperties: React.FC = () => { onClick={() => handleColorChange(val.id)} aria-pressed={selectedAisleData.type.aisleColor === val.id} > -
{val.texture}
+
+ {val.texture} +
{val.color}
{`( ${val.brief} )`}
From 2ba418ab6c80c5b62646ec75e68b6637cd323523 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 28 Aug 2025 18:15:10 +0530 Subject: [PATCH 19/19] dashboard style fix --- app/src/styles/pages/dashboard.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/styles/pages/dashboard.scss b/app/src/styles/pages/dashboard.scss index 5a628a4..01024d0 100644 --- a/app/src/styles/pages/dashboard.scss +++ b/app/src/styles/pages/dashboard.scss @@ -144,7 +144,8 @@ } .cards-container { - height: 100%; + height: auto; + max-height: 100%; display: flex; flex-wrap: wrap; position: relative;