diff --git a/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx index b647145..b5099d5 100644 --- a/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/SelectedWallProperties.tsx @@ -6,10 +6,20 @@ import wallTexture1 from '../../../../assets/textures/floor/factory wall texture import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; +import { useVersionContext } from "../../../../modules/builder/version/versionContext"; +import { useParams } from "react-router-dom"; +import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi"; +import { getUserData } from "../../../../functions/getUserData"; +import { useSocketStore } from "../../../../store/builder/store"; const SelectedWallProperties = () => { const { selectedWall } = useBuilderStore(); const { wallStore } = useSceneContext(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { socket } = useSocketStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); const { getWallById, updateWall } = wallStore(); const [activeSide, setActiveSide] = useState<"side1" | "side2">("side1"); @@ -24,14 +34,50 @@ const SelectedWallProperties = () => { const handleHeightChange = (val: string) => { const height = parseFloat(val); if (!isNaN(height) && wall) { - updateWall(wall.wallUuid, { wallHeight: height }); + const updatedWall = updateWall(wall.wallUuid, { wallHeight: height }); + if (updatedWall && projectId) { + + // 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); + } } }; const handleThicknessChange = (val: string) => { const thickness = parseFloat(val); if (!isNaN(thickness) && wall) { - updateWall(wall.wallUuid, { wallThickness: thickness }); + const updatedWall = updateWall(wall.wallUuid, { wallThickness: thickness }); + if (updatedWall && projectId) { + + // 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); + } } }; @@ -39,8 +85,25 @@ const SelectedWallProperties = () => { if (!wall) return; const updated = (activeSide === "side1" ? { insideMaterial: material.textureId } : { outsideMaterial: material.textureId }) + const updatedWall = updateWall(wall.wallUuid, updated); + if (updatedWall && projectId) { - updateWall(wall.wallUuid, updated); + // 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); + } }; if (!wall) return null; diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index e0805e9..31ed1f6 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -2,13 +2,13 @@ import * as THREE from 'three' import { useEffect, useMemo, useRef, useState } from 'react' import { useThree } from '@react-three/fiber'; import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; -import ReferenceAisle from './referenceAisle'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; -import ReferencePoint from '../../point/reference/referencePoint'; -import { createAisleApi } from '../../../../services/factoryBuilder/aisle/createAisleApi'; +import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi'; import { useParams } from 'react-router-dom'; import { useVersionContext } from '../../version/versionContext'; import { useSceneContext } from '../../../scene/sceneContext'; +import ReferenceAisle from './referenceAisle'; +import ReferencePoint from '../../point/reference/referencePoint'; function AisleCreator() { const { scene, camera, raycaster, gl, pointer } = useThree(); @@ -74,6 +74,8 @@ function AisleCreator() { newPoint.layer = snappedPoint.layer; } + if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { return } + if (snappedPosition && !snappedPoint) { newPoint.position = snappedPosition; } @@ -104,7 +106,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } @@ -127,7 +129,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } @@ -149,7 +151,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } @@ -170,7 +172,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } @@ -193,7 +195,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } @@ -215,7 +217,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } @@ -236,7 +238,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } @@ -258,7 +260,7 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') } setTempPoints([newPoint]); } diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index b977781..9ad8bd2 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -33,7 +33,7 @@ import * as Types from "../../types/world/worldTypes"; import SocketResponses from "../collaboration/socket/socketResponses.dev"; import FloorPlanGroup from "./groups/floorPlanGroup"; -import FloorGroup from "./groups/floorGroup"; +// import FloorGroup from "./groups/floorGroup"; import Draw from "./functions/draw"; import WallsAndWallItems from "./groups/wallsAndWallItems"; import Ground from "../scene/environment/ground"; @@ -52,6 +52,7 @@ import AislesGroup from "./aisle/aislesGroup"; import WallGroup from "./wall/wallGroup"; import { useBuilderStore } from "../../store/builder/useBuilderStore"; import { getUserData } from "../../functions/getUserData"; +import FloorGroup from "./floor/floorGroup"; export default function Builder() { const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. @@ -185,7 +186,7 @@ export default function Builder() { - + {/* */} - + /> */} - + /> */} - {/* */} + + + ); } diff --git a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx new file mode 100644 index 0000000..1b6dcb7 --- /dev/null +++ b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function FloorInstance({ floor }: { floor: Floor }) { + return ( + <> + + ) +} + +export default FloorInstance \ No newline at end of file diff --git a/app/src/modules/builder/floor/Instances/floorInstances.tsx b/app/src/modules/builder/floor/Instances/floorInstances.tsx new file mode 100644 index 0000000..75fd359 --- /dev/null +++ b/app/src/modules/builder/floor/Instances/floorInstances.tsx @@ -0,0 +1,121 @@ +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 Line from '../../line/line'; +import Point from '../../point/point'; +import FloorInstance from './Instance/floorInstance'; + +function FloorInstances() { + const { floorStore } = useSceneContext(); + const { floors } = floorStore(); + const { toggleView } = useToggleView(); + + useEffect(() => { + // console.log('floors: ', floors); + }, [floors]) + + const allPoints = useMemo(() => { + const points: Point[] = []; + const seenUuids = new Set(); + + floors.forEach(floor => { + floor.points.forEach(point => { + if (!seenUuids.has(point.pointUuid)) { + seenUuids.add(point.pointUuid); + points.push(point); + } + }); + }); + + return points; + }, [floors]); + + const allLines = useMemo(() => { + const lines: { start: Point; end: Point; key: string }[] = []; + + floors.forEach((floor) => { + const points = floor.points; + if (points.length < 2) return; + + for (let i = 0; i < points.length; i++) { + const current = points[i]; + const next = points[(i + 1) % points.length]; + if (current.pointUuid !== next.pointUuid) { + lines.push({ + start: current, + end: next, + key: `${current.pointUuid}-${next.pointUuid}` + }); + } + } + }); + + return lines; + }, [floors]); + + return ( + <> + + {!toggleView && floors.length > 1 && ( + + {floors.map((floor) => ( + + ))} + + )} + + {toggleView && ( + <> + + {allPoints.map((point) => ( + + ))} + + + + + {allLines.map(({ start, end, key }) => ( + + ))} + + {allLines.map((line) => { + const { start, end, key } = line; + const textPosition = new Vector3().addVectors(new Vector3(...start.position), new Vector3(...end.position)).divideScalar(2); + const distance = new Vector3(...start.position).distanceTo(new Vector3(...end.position)); + + return ( + + {toggleView && + +
+ {distance.toFixed(2)} m +
+ + } +
+ ) + })} + +
+ + + )} + + ) +} + +export default FloorInstances; \ No newline at end of file diff --git a/app/src/modules/builder/floor/floorCreator/floorCreator.tsx b/app/src/modules/builder/floor/floorCreator/floorCreator.tsx new file mode 100644 index 0000000..db031d5 --- /dev/null +++ b/app/src/modules/builder/floor/floorCreator/floorCreator.tsx @@ -0,0 +1,175 @@ +import * as THREE from 'three' +import { useEffect, useMemo, useRef, useState } from 'react' +import { useThree } from '@react-three/fiber'; +import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; +import { useSceneContext } from '../../../scene/sceneContext'; +import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; +import ReferencePoint from '../../point/reference/referencePoint'; +import ReferenceFloor from './referenceFloor'; + +function FloorCreator() { + const { scene, camera, raycaster, gl, pointer } = useThree(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); + const { toggleView } = useToggleView(); + const { toolMode } = useToolMode(); + const { activeLayer } = useActiveLayer(); + const { socket } = useSocketStore(); + const { floorStore } = useSceneContext(); + const { addFloor, removeDecal, getFloorPointById, getFloorByPoints } = floorStore(); + const drag = useRef(false); + const isLeftMouseDown = useRef(false); + + const [tempPoints, setTempPoints] = useState([]); + const [isCreating, setIsCreating] = useState(false); + const { floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint } = useBuilderStore(); + + useEffect(() => { + const canvasElement = gl.domElement; + + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = true; + drag.current = false; + } + }; + + const onMouseUp = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = false; + } + }; + + const onMouseMove = () => { + if (isLeftMouseDown) { + drag.current = true; + } + }; + + const onMouseClick = () => { + if (drag.current || !toggleView) return; + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + let position = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!position) return; + + const pointIntersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Floor-Point'); + + const floorIntersect = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Floor-Line'); + + if (floorIntersect && !pointIntersects) { + + } + + const newPoint: Point = { + pointUuid: THREE.MathUtils.generateUUID(), + pointType: 'Floor', + position: [position.x, position.y, position.z], + layer: activeLayer + }; + + if (snappedPosition && snappedPoint) { + newPoint.pointUuid = snappedPoint.pointUuid; + newPoint.position = snappedPosition; + newPoint.layer = snappedPoint.layer; + } + + if (snappedPoint && snappedPoint.pointUuid === tempPoints[tempPoints.length - 1]?.pointUuid) { return } + + if (snappedPosition && !snappedPoint) { + newPoint.position = snappedPosition; + } + + if (pointIntersects && !snappedPoint) { + if (tempPoints.length > 2 && isCreating && pointIntersects.object.userData.pointUuid === tempPoints[0].pointUuid) { + if (tempPoints.length >= 3) { + const floor: Floor = { + floorUuid: THREE.MathUtils.generateUUID(), + points: tempPoints, + topMaterial, + sideMaterial, + floorDepth, + isBeveled, + bevelStrength, + decals: [], + }; + + addFloor(floor); + } + setTempPoints([]); + setIsCreating(false); + } + } + + setTempPoints(prev => [...prev, newPoint]); + setIsCreating(true); + + }; + + const onContext = (event: any) => { + event.preventDefault(); + if (isCreating) { + if (tempPoints.length >= 3) { + const floor: Floor = { + floorUuid: THREE.MathUtils.generateUUID(), + points: tempPoints, + topMaterial, + sideMaterial, + floorDepth, + isBeveled, + bevelStrength, + decals: [], + }; + + addFloor(floor); + } + setTempPoints([]); + setIsCreating(false); + } + }; + + if (toolMode === "Floor" && toggleView) { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener("click", onMouseClick); + canvasElement.addEventListener("contextmenu", onContext); + } else { + setTempPoints([]); + setIsCreating(false); + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("click", onMouseClick); + canvasElement.removeEventListener("contextmenu", onContext); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("click", onMouseClick); + canvasElement.removeEventListener("contextmenu", onContext); + }; + }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, removeDecal, getFloorPointById, getFloorByPoints, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]); + + return ( + <> + {toggleView && + <> + + {tempPoints.map((point) => ( + + ))} + + + {tempPoints.length > 0 && + + } + + } + + ) +} + +export default FloorCreator \ No newline at end of file diff --git a/app/src/modules/builder/floor/floorCreator/referenceFloor.tsx b/app/src/modules/builder/floor/floorCreator/referenceFloor.tsx new file mode 100644 index 0000000..6e40a1c --- /dev/null +++ b/app/src/modules/builder/floor/floorCreator/referenceFloor.tsx @@ -0,0 +1,164 @@ +import { useEffect, useRef, useState, useMemo } from 'react'; +import * as THREE from 'three'; +import { useFrame, useThree } from '@react-three/fiber'; +import { Extrude, Html } from '@react-three/drei'; +import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; +import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store'; +import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping'; +import { usePointSnapping } from '../../point/helpers/usePointSnapping'; +import ReferenceLine from '../../line/reference/referenceLine'; + +interface ReferenceFloorProps { + tempPoints: Point[]; +} + +function ReferenceFloor({ tempPoints }: Readonly) { + const { floorDepth, isBeveled, bevelStrength, setSnappedPosition, setSnappedPoint } = useBuilderStore(); + const { pointer, raycaster, camera } = useThree(); + const { toolMode } = useToolMode(); + const { toggleView } = useToggleView(); + const { activeLayer } = useActiveLayer(); + const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + const finalPosition = useRef<[number, number, number] | null>(null); + + const [tempFloor, setTempFloor] = useState(null); + const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); + + const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[tempPoints.length - 1]?.position || null); + const { snapFloorPoint } = usePointSnapping({ uuid: 'temp-floor', pointType: 'Floor', position: directionalSnap.position || [0, 0, 0], }); + + useFrame(() => { + if (toolMode === 'Floor' && toggleView && tempPoints.length > 0) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + raycaster.ray.intersectPlane(plane, intersectionPoint); + + setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); + + if (!intersectionPoint) return; + const snapped = snapFloorPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z], tempPoints.slice(0, -2)); + + if (snapped.isSnapped && snapped.snappedPoint) { + finalPosition.current = snapped.position; + setSnappedPosition(snapped.position); + setSnappedPoint(snapped.snappedPoint); + } else if (directionalSnap.isSnapped) { + finalPosition.current = directionalSnap.position; + setSnappedPosition(directionalSnap.position); + setSnappedPoint(null); + } else { + finalPosition.current = [intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]; + setSnappedPosition(null); + setSnappedPoint(null); + } + + if (!finalPosition.current) return; + + const floorPoints: Point[] = [ + ...tempPoints, + { + pointUuid: 'temp-point', + pointType: 'Floor', + position: finalPosition.current, + layer: activeLayer, + }, + ]; + + setTempFloor({ + floorUuid: 'temp-floor', + points: floorPoints, + topMaterial: 'default', + sideMaterial: 'default', + floorDepth, + bevelStrength, + isBeveled, + decals: [], + }); + + } else if (tempFloor !== null) { + setTempFloor(null); + } + }); + + useEffect(() => { + setTempFloor(null); + }, [toolMode, toggleView, tempPoints.length, floorDepth, bevelStrength, isBeveled]); + + const allLines = useMemo(() => { + if (!tempFloor || tempFloor.points.length < 2) return []; + + const lines: [Point, Point][] = []; + const pts = tempFloor.points; + + for (let i = 0; i < pts.length - 1; i++) { + lines.push([pts[i], pts[i + 1]]); + } + + return lines; + }, [tempFloor]); + + if (!tempFloor) return null; + + return ( + + {allLines.map(([p1, p2], idx) => { + const v1 = new THREE.Vector3(...p1.position); + const v2 = new THREE.Vector3(...p2.position); + const mid = new THREE.Vector3().addVectors(v1, v2).multiplyScalar(0.5); + const dist = v1.distanceTo(v2); + + return ( + + + {toggleView && ( + +
{dist.toFixed(2)} m
+ + )} +
+ ); + })} + + {tempPoints.length >= 3 && ( + + )} +
+ ); +} + +export default ReferenceFloor; + + +function Floor({ floor }: { floor: Point[] }) { + const savedTheme: string | null = localStorage.getItem('theme'); + + const shape = useMemo(() => { + const shape = new THREE.Shape(); + const points = floor.map(p => new THREE.Vector2(p.position[0], p.position[2])); + if (points.length < 3) return null; + shape.moveTo(points[0].x, points[0].y); + points.forEach((pt) => { shape.lineTo(pt.x, pt.y); }); + return shape; + }, [floor]); + + if (!shape) return null; + + return ( + + + + + + ); +} diff --git a/app/src/modules/builder/floor/floorGroup.tsx b/app/src/modules/builder/floor/floorGroup.tsx new file mode 100644 index 0000000..fdcf37c --- /dev/null +++ b/app/src/modules/builder/floor/floorGroup.tsx @@ -0,0 +1,31 @@ +import { useEffect } from 'react'; +import { useToggleView } from '../../../store/builder/store' +import { useBuilderStore } from '../../../store/builder/useBuilderStore'; +import FloorCreator from './floorCreator/floorCreator'; +import FloorInstances from './Instances/floorInstances'; +import useModuleStore from '../../../store/useModuleStore'; + +function FloorGroup() { + const { togglView } = useToggleView(); + const { setSelectedFloor, setSelectedDecal } = useBuilderStore(); + const { activeModule } = useModuleStore(); + + useEffect(() => { + if (togglView || activeModule !== 'builder') { + setSelectedFloor(null); + setSelectedDecal(null); + } + }, [togglView, activeModule]) + + return ( + <> + + + + + + + ) +} + +export default FloorGroup \ No newline at end of file diff --git a/app/src/modules/builder/groups/floorPlanGroup.tsx b/app/src/modules/builder/groups/floorPlanGroup.tsx index 8f1c2f2..a9a845e 100644 --- a/app/src/modules/builder/groups/floorPlanGroup.tsx +++ b/app/src/modules/builder/groups/floorPlanGroup.tsx @@ -148,11 +148,11 @@ const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoin } if (toolMode === "Wall") { - drawWall(raycaster, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket, projectId, selectedVersion?.versionId || '',); + // drawWall(raycaster, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket, projectId, selectedVersion?.versionId || '',); } if (toolMode === "Floor") { - drawOnlyFloor(raycaster, state, camera, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, onlyFloorline, onlyFloorlines, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket, projectId, selectedVersion?.versionId || '',); + // drawOnlyFloor(raycaster, state, camera, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, onlyFloorline, onlyFloorlines, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket, projectId, selectedVersion?.versionId || '',); } } diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx index 19f0810..42aa5c2 100644 --- a/app/src/modules/builder/line/line.tsx +++ b/app/src/modules/builder/line/line.tsx @@ -2,10 +2,15 @@ import * as THREE from 'three'; import { useThree } from '@react-three/fiber'; import { useEffect, useMemo, useState } from "react"; import { DragControls, Tube } from '@react-three/drei'; -import { useToolMode } from '../../../store/builder/store'; +import { useSocketStore, useToolMode } from '../../../store/builder/store'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useSceneContext } from '../../scene/sceneContext'; import * as Constants from '../../../types/world/worldConstants'; +import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi'; +import { useVersionContext } from '../version/versionContext'; +import { useParams } from 'react-router-dom'; +import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; +import { getUserData } from '../../../functions/getUserData'; interface LineProps { points: [Point, Point]; @@ -16,9 +21,15 @@ function Line({ points }: Readonly) { const { raycaster, camera, pointer, gl } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const [isDeletable, setIsDeletable] = useState(false); + const { socket } = useSocketStore(); const { toolMode } = useToolMode(); - const { wallStore } = useSceneContext(); - const { removeWallByPoints, setPosition } = wallStore(); + const { wallStore, floorStore } = useSceneContext(); + const { removeWallByPoints, setPosition: setWallPosition, getWallsByPointId } = wallStore(); + const { removeFloorByPoints, setPosition: setFloorPosition } = floorStore(); + const { userId, organization } = getUserData(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); const [dragOffset, setDragOffset] = useState(null); const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore(); @@ -79,7 +90,29 @@ function Line({ points }: Readonly) { const handlePointClick = (points: [Point, Point]) => { if (toolMode === '2D-Delete') { if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { - removeWallByPoints(points); + const removedWall = removeWallByPoints(points); + if (removedWall && projectId) { + + // API + + // deleteWallApi(projectId, selectedVersion?.versionId || '', removedWall.wallUuid); + + // SOCKET + + const data = { + wallUuid: removedWall.wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:delete', data); + } + setHoveredLine(null); + } + if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { + removeFloorByPoints(points); setHoveredLine(null); } gl.domElement.style.cursor = 'default'; @@ -105,8 +138,14 @@ function Line({ points }: Readonly) { const newStart = new THREE.Vector3().addVectors(start, delta); const newEnd = new THREE.Vector3().addVectors(end, delta); - setPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); - setPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); + if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { + setWallPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); + setWallPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); + } + if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { + setFloorPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); + setFloorPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); + } } } }; @@ -127,11 +166,38 @@ function Line({ points }: Readonly) { }; const handleDragEnd = (points: [Point, Point]) => { + if (toolMode !== 'move' || !dragOffset) return; gl.domElement.style.cursor = 'default'; setDragOffset(null); - if (toolMode !== 'move') return; if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { - // console.log('Wall after drag: ', points); + const updatedWalls1 = getWallsByPointId(points[0].pointUuid); + const updatedWalls2 = getWallsByPointId(points[1].pointUuid); + const updatedWalls = [...updatedWalls1, ...updatedWalls2].filter((wall, index, self) => index === self.findIndex((w) => w.wallUuid === wall.wallUuid)); + + if (updatedWalls.length > 0 && projectId) { + updatedWalls.forEach(updatedWall => { + + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall).catch((error) => { + // console.error('Error updating wall:', error); + // }); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + }) + } + } else if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { + // Handle floor update logic here if needed } } diff --git a/app/src/modules/builder/point/helpers/usePointSnapping.tsx b/app/src/modules/builder/point/helpers/usePointSnapping.tsx index c7d31cc..3d0f7e1 100644 --- a/app/src/modules/builder/point/helpers/usePointSnapping.tsx +++ b/app/src/modules/builder/point/helpers/usePointSnapping.tsx @@ -11,9 +11,10 @@ const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping i const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => { - const { aisleStore, wallStore } = useSceneContext(); + const { aisleStore, wallStore, floorStore } = useSceneContext(); const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore(); const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore(); + const { floors, getConnectedPoints: getConnectedFloorPoints } = floorStore(); // Wall Snapping @@ -24,10 +25,13 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string ); }, [walls, currentPoint]); - const snapWallPoint = useCallback((position: [number, number, number]) => { + const snapWallPoint = useCallback((position: [number, number, number], tempPoints?: Point) => { if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; const otherPoints = getAllOtherWallPoints(); + if (tempPoints) { + otherPoints.push(tempPoints); + } const currentVec = new THREE.Vector3(...position); for (const point of otherPoints) { const pointVec = new THREE.Vector3(...point.position); @@ -187,10 +191,96 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string }; }, [currentPoint, getConnectedAislePoints]); + // Floor Snapping + + const getAllOtherFloorPoints = useCallback(() => { + if (!currentPoint) return []; + + return floors.flatMap(floor => + floor.points.filter(point => point.pointUuid !== currentPoint.uuid) + ); + }, [floors, currentPoint]); + + const snapFloorPoint = useCallback((position: [number, number, number], tempPoints: Point[] | []) => { + if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; + + const otherPoints = [...getAllOtherFloorPoints(), ...tempPoints]; + const currentVec = new THREE.Vector3(...position); + + for (const point of otherPoints) { + const pointVec = new THREE.Vector3(...point.position); + const distance = currentVec.distanceTo(pointVec); + + if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Floor') { + return { position: point.position, isSnapped: true, snappedPoint: point }; + } + } + + return { position: position, isSnapped: false, snappedPoint: null }; + }, [currentPoint, getAllOtherFloorPoints]); + + const snapFloorAngle = useCallback((newPosition: [number, number, number]): { + position: [number, number, number], + isSnapped: boolean, + snapSources: THREE.Vector3[] + } => { + if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; + + const connectedPoints = getConnectedFloorPoints(currentPoint.uuid); + if (connectedPoints.length === 0) { + return { position: newPosition, isSnapped: false, snapSources: [] }; + } + + const newPos = new THREE.Vector3(...newPosition); + let closestX: { pos: THREE.Vector3, dist: number } | null = null; + let closestZ: { pos: THREE.Vector3, dist: number } | null = null; + + for (const connectedPoint of connectedPoints) { + const cPos = new THREE.Vector3(...connectedPoint.position); + const xDist = Math.abs(newPos.x - cPos.x); + const zDist = Math.abs(newPos.z - cPos.z); + + if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { + if (!closestX || xDist < closestX.dist) { + closestX = { pos: cPos, dist: xDist }; + } + } + + if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { + if (!closestZ || zDist < closestZ.dist) { + closestZ = { pos: cPos, dist: zDist }; + } + } + } + + const snappedPos = newPos.clone(); + const snapSources: THREE.Vector3[] = []; + + if (closestX) { + snappedPos.x = closestX.pos.x; + snapSources.push(closestX.pos.clone()); + } + + if (closestZ) { + snappedPos.z = closestZ.pos.z; + snapSources.push(closestZ.pos.clone()); + } + + const isSnapped = snapSources.length > 0; + + return { + position: [snappedPos.x, snappedPos.y, snappedPos.z], + isSnapped, + snapSources + }; + }, [currentPoint, getConnectedFloorPoints]); + return { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, + snapFloorPoint, + snapFloorAngle, }; }; \ No newline at end of file diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index a671806..c3b94d3 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -1,29 +1,36 @@ import * as THREE from 'three'; import * as Constants from '../../../types/world/worldConstants'; import { useRef, useState, useEffect, useMemo } from 'react'; -import { useToolMode } from '../../../store/builder/store'; +import { useSocketStore, useToolMode } from '../../../store/builder/store'; import { DragControls } from '@react-three/drei'; import { useThree } from '@react-three/fiber'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { usePointSnapping } from './helpers/usePointSnapping'; -import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi'; import { useParams } from 'react-router-dom'; -import { createAisleApi } from '../../../services/factoryBuilder/aisle/createAisleApi'; import { useVersionContext } from '../version/versionContext'; import { useSceneContext } from '../../scene/sceneContext'; +import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi'; +import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi'; +import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; +import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi'; +import { getUserData } from '../../../functions/getUserData'; + function Point({ point }: { readonly point: Point }) { const materialRef = useRef(null); const { raycaster, camera, pointer, gl } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const [isHovered, setIsHovered] = useState(false); const [dragOffset, setDragOffset] = useState(null); + const { socket } = useSocketStore(); const { toolMode } = useToolMode(); - const { aisleStore, wallStore } = useSceneContext(); + const { aisleStore, wallStore, floorStore } = useSceneContext(); const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore(); - const { setPosition: setWallPosition, removePoint: removeWallPoint } = wallStore(); + const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore(); + const { setPosition: setFloorPosition, removePoint: removeFloorPoint } = floorStore(); const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position }); const { hoveredPoint, setHoveredPoint } = useBuilderStore(); + const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); @@ -113,6 +120,10 @@ function Point({ point }: { readonly point: Point }) { const wallSnapped = snapWallAngle(newPosition); const finalSnapped = snapWallPoint(wallSnapped.position); setWallPosition(point.pointUuid, finalSnapped.position); + } else if (point.pointType === 'Floor') { + const floorSnapped = snapWallAngle(newPosition); + const finalSnapped = snapWallPoint(floorSnapped.position); + setFloorPosition(point.pointUuid, finalSnapped.position); } } } @@ -138,11 +149,34 @@ function Point({ point }: { readonly point: Point }) { const updatedAisles = getAislesByPointId(point.pointUuid); if (updatedAisles.length > 0 && projectId) { updatedAisles.forEach((updatedAisle) => { - createAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '') + upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '') }) } } else if (point.pointType === 'Wall') { + const updatedWalls = getWallsByPointId(point.pointUuid); + if (updatedWalls && updatedWalls.length > 0 && projectId) { + updatedWalls.forEach((updatedWall) => { + + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + }); + } // console.log('Wall after drag: ', point); + } else if (point.pointType === 'Floor') { + // console.log('Floor after drag: ', point); } } @@ -152,16 +186,43 @@ function Point({ point }: { readonly point: Point }) { const removedAisles = removeAislePoint(point.pointUuid); if (removedAisles.length > 0) { removedAisles.forEach(aisle => { - if (projectId) + if (projectId) { deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '') + } }); setHoveredPoint(null); } } if (point.pointType === 'Wall') { - const removedAisles = removeWallPoint(point.pointUuid); - if (removedAisles.length > 0) { + const removedWalls = removeWallPoint(point.pointUuid); + if (removedWalls.length > 0) { setHoveredPoint(null); + removedWalls.forEach(wall => { + if (projectId) { + + // API + + // deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid); + + // SOCKET + + const data = { + wallUuid: wall.wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:delete', data); + } + }); + } + } + if (point.pointType === 'Floor') { + const removedFloors = removeFloorPoint(point.pointUuid); + setHoveredPoint(null); + if (removedFloors.length > 0) { } } gl.domElement.style.cursor = 'default'; diff --git a/app/src/modules/builder/point/reference/referencePoint.tsx b/app/src/modules/builder/point/reference/referencePoint.tsx index 44d399f..3be6f95 100644 --- a/app/src/modules/builder/point/reference/referencePoint.tsx +++ b/app/src/modules/builder/point/reference/referencePoint.tsx @@ -21,9 +21,15 @@ function ReferencePoint({ point }: { readonly point: Point }) { return null; } + const pointName = point.pointType === 'Wall' ? 'Wall-Point' : + point.pointType === 'Floor' ? 'Floor-Point' : + point.pointType === 'Aisle' ? 'Aisle-Point' : + point.pointType === 'Zone' ? 'Zone-Point' : 'Point'; + return ( (); - walls.forEach(aisle => { - aisle.points.forEach(point => { + walls.forEach(wall => { + wall.points.forEach(point => { if (!seenUuids.has(point.pointUuid)) { seenUuids.add(point.pointUuid); points.push(point); diff --git a/app/src/modules/builder/wall/wallCreator/referenceWall.tsx b/app/src/modules/builder/wall/wallCreator/referenceWall.tsx index f932f46..278fe67 100644 --- a/app/src/modules/builder/wall/wallCreator/referenceWall.tsx +++ b/app/src/modules/builder/wall/wallCreator/referenceWall.tsx @@ -36,46 +36,45 @@ function ReferenceWall({ tempPoints }: Readonly) { setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); - if (intersectionPoint) { - const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); - - if (snapped.isSnapped && snapped.snappedPoint) { - finalPosition.current = snapped.position; - setSnappedPosition(snapped.position); - setSnappedPoint(snapped.snappedPoint); - } else if (directionalSnap.isSnapped) { - finalPosition.current = directionalSnap.position; - setSnappedPosition(directionalSnap.position); - setSnappedPoint(null); - } else { - finalPosition.current = [intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]; - setSnappedPosition(null); - setSnappedPoint(null); - } - - if (!finalPosition.current) return; - - const wallPoints: [Point, Point] = [ - tempPoints[0], - { - pointUuid: 'temp-point', - pointType: 'Wall', - position: finalPosition.current, - layer: activeLayer, - } - ]; - - setTempWall({ - wallUuid: 'temp-wall', - points: wallPoints, - outsideMaterial: 'default', - insideMaterial: 'default', - wallThickness: wallThickness, - wallHeight: wallHeight, - decals: [] - }) + if (!intersectionPoint) return; + const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z], tempPoints[0]); + if (snapped.isSnapped && snapped.snappedPoint) { + finalPosition.current = snapped.position; + setSnappedPosition(snapped.position); + setSnappedPoint(snapped.snappedPoint); + } else if (directionalSnap.isSnapped) { + finalPosition.current = directionalSnap.position; + setSnappedPosition(directionalSnap.position); + setSnappedPoint(null); + } else { + finalPosition.current = [intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]; + setSnappedPosition(null); + setSnappedPoint(null); } + + if (!finalPosition.current) return; + + const wallPoints: [Point, Point] = [ + tempPoints[0], + { + pointUuid: 'temp-point', + pointType: 'Wall', + position: finalPosition.current, + layer: activeLayer, + } + ]; + + setTempWall({ + wallUuid: 'temp-wall', + points: wallPoints, + outsideMaterial: 'default', + insideMaterial: 'default', + wallThickness: wallThickness, + wallHeight: wallHeight, + decals: [] + }) + } else if (tempWall !== null) { setTempWall(null); } diff --git a/app/src/modules/builder/wall/wallCreator/wallCreator.tsx b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx index a136a41..c3a6c66 100644 --- a/app/src/modules/builder/wall/wallCreator/wallCreator.tsx +++ b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx @@ -5,9 +5,15 @@ import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../. import * as Constants from '../../../../types/world/worldConstants'; import { useSceneContext } from '../../../scene/sceneContext'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; +import { useParams } from 'react-router-dom'; +import { useVersionContext } from '../../version/versionContext'; +import { getUserData } from '../../../../functions/getUserData'; +import getClosestIntersection from '../../geomentries/lines/getClosestIntersection'; import ReferencePoint from '../../point/reference/referencePoint'; import ReferenceWall from './referenceWall'; -import getClosestIntersection from '../../geomentries/lines/getClosestIntersection'; + +import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi'; +import { deleteWallApi } from '../../../../services/factoryBuilder/wall/deleteWallApi'; function WallCreator() { const { scene, camera, raycaster, gl, pointer } = useThree(); @@ -20,10 +26,14 @@ function WallCreator() { const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); - const { wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint } = useBuilderStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); const [tempPoints, setTempPoints] = useState([]); const [isCreating, setIsCreating] = useState(false); + const { wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint, setSnappedPoint, setSnappedPosition } = useBuilderStore(); useEffect(() => { const canvasElement = gl.domElement; @@ -58,6 +68,7 @@ function WallCreator() { const pointIntersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Wall-Point'); const wallIntersect = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Wall-Line'); + if (wallIntersect && !pointIntersects) { const wall = getWallByPoints(wallIntersect.object.userData.points); if (wall) { @@ -80,6 +91,25 @@ function WallCreator() { const closestPoint = new THREE.Vector3().lerpVectors(point1Vec, point2Vec, t); removeWall(wall.wallUuid); + if (projectId) { + + // API + + // deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid); + + // SOCKET + + const data = { + wallUuid: wall.wallUuid, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:delete', data); + + } const point1: Point = { pointUuid: wall.points[0].pointUuid, @@ -114,6 +144,24 @@ function WallCreator() { } addWall(wall2); + // API + + // if (projectId) { + // upsertWallApi(projectId, selectedVersion?.versionId || '', wall2); + // } + + // SOCKET + + const data = { + wallData: wall2, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + const wall3: Wall = { wallUuid: THREE.MathUtils.generateUUID(), points: [point2, newPoint], @@ -125,6 +173,24 @@ function WallCreator() { } addWall(wall3); + // API + + // if (projectId) { + // upsertWallApi(projectId, selectedVersion?.versionId || '', wall3); + // } + + // SOCKET + + const data2 = { + wallData: wall3, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data2); + setTempPoints([newPoint]); setIsCreating(true); } else { @@ -139,6 +205,24 @@ function WallCreator() { }; addWall(wall1); + // API + + // if (projectId) { + // upsertWallApi(projectId, selectedVersion?.versionId || '', wall1); + // } + + // SOCKET + + const data = { + wallData: wall1, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + const wall2: Wall = { wallUuid: THREE.MathUtils.generateUUID(), points: [point1, newPoint], @@ -150,6 +234,24 @@ function WallCreator() { } addWall(wall2); + // API + + // if (projectId) { + // upsertWallApi(projectId, selectedVersion?.versionId || '', wall2); + // } + + // SOCKET + + const data1 = { + wallData: wall2, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data1); + const wall3: Wall = { wallUuid: THREE.MathUtils.generateUUID(), points: [point2, newPoint], @@ -161,6 +263,24 @@ function WallCreator() { } addWall(wall3); + // API + + // if (projectId) { + // upsertWallApi(projectId, selectedVersion?.versionId || '', wall3); + // } + + // SOCKET + + const data3 = { + wallData: wall3, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data3); + setTempPoints([newPoint]); } @@ -181,6 +301,8 @@ function WallCreator() { newPoint.layer = snappedPoint.layer; } + if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { return } + if (snappedPosition && !snappedPoint) { newPoint.position = snappedPosition; } @@ -208,6 +330,25 @@ function WallCreator() { decals: [] }; addWall(wall); + + // API + + // if (projectId) { + // upsertWallApi(projectId, selectedVersion?.versionId || '', wall); + // } + + // SOCKET + + const data = { + wallData: wall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + setTempPoints([newPoint]); } @@ -222,6 +363,10 @@ function WallCreator() { }; if (toolMode === "Wall" && toggleView) { + if (tempPoints.length === 0) { + setSnappedPosition(null); + setSnappedPoint(null); + } canvasElement.addEventListener("mousedown", onMouseDown); canvasElement.addEventListener("mouseup", onMouseUp); canvasElement.addEventListener("mousemove", onMouseMove); @@ -244,7 +389,7 @@ function WallCreator() { canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("contextmenu", onContext); }; - }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addWall, getWallPointById, wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint]); + }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addWall, getWallPointById, wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint, selectedVersion?.versionId]); return ( <> diff --git a/app/src/modules/builder/wall/wallGroup.tsx b/app/src/modules/builder/wall/wallGroup.tsx index aae36b9..0722d56 100644 --- a/app/src/modules/builder/wall/wallGroup.tsx +++ b/app/src/modules/builder/wall/wallGroup.tsx @@ -1,14 +1,23 @@ import { useEffect } from 'react'; -import { useToggleView } from '../../../store/builder/store' +import { useToggleView } from '../../../store/builder/store'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; -import WallCreator from './wallCreator/wallCreator' -import WallInstances from './Instances/wallInstances' +import WallCreator from './wallCreator/wallCreator'; +import WallInstances from './Instances/wallInstances'; import useModuleStore from '../../../store/useModuleStore'; +import { useVersionContext } from '../version/versionContext'; +import { useParams } from 'react-router-dom'; +import { useSceneContext } from '../../scene/sceneContext'; +import { getWallsApi } from '../../../services/factoryBuilder/wall/getWallsApi'; function WallGroup() { const { togglView } = useToggleView(); const { setSelectedWall, setSelectedDecal } = useBuilderStore(); const { activeModule } = useModuleStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { wallStore } = useSceneContext(); + const { setWalls } = wallStore(); + const { projectId } = useParams(); useEffect(() => { if (togglView || activeModule !== 'builder') { @@ -17,6 +26,20 @@ function WallGroup() { } }, [togglView, activeModule]) + useEffect(() => { + if (projectId && selectedVersion) { + getWallsApi(projectId, selectedVersion?.versionId || '').then((walls) => { + if (walls && walls.length > 0) { + setWalls(walls); + } else { + setWalls([]); + } + }).catch((err) => { + console.log(err); + }) + } + }, [projectId, selectedVersion?.versionId]) + return ( <> diff --git a/app/src/services/factoryBuilder/aisle/deleteAisleApi.ts b/app/src/services/factoryBuilder/aisle/deleteAisleApi.ts index 5ef96a3..28fde46 100644 --- a/app/src/services/factoryBuilder/aisle/deleteAisleApi.ts +++ b/app/src/services/factoryBuilder/aisle/deleteAisleApi.ts @@ -10,7 +10,7 @@ export const deleteAisleApi = async (aisleUuid: string, projectId: string, versi token: localStorage.getItem("token") || "", // Coerce null to empty string refresh_token: localStorage.getItem("refreshToken") || "", }, - body: JSON.stringify({ aisleUuid, projectId }), + body: JSON.stringify({ aisleUuid, projectId, versionId }), }); const newAccessToken = response.headers.get("x-access-token"); if (newAccessToken) { diff --git a/app/src/services/factoryBuilder/aisle/createAisleApi.ts b/app/src/services/factoryBuilder/aisle/upsertAisleApi.ts similarity index 97% rename from app/src/services/factoryBuilder/aisle/createAisleApi.ts rename to app/src/services/factoryBuilder/aisle/upsertAisleApi.ts index 27ae378..b980c61 100644 --- a/app/src/services/factoryBuilder/aisle/createAisleApi.ts +++ b/app/src/services/factoryBuilder/aisle/upsertAisleApi.ts @@ -1,6 +1,6 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; -export const createAisleApi = async ( +export const upsertAisleApi = async ( aisleUuid: string, points: any, type: Object, diff --git a/app/src/services/factoryBuilder/assest/wallAsset/deleteWallItemApi.ts b/app/src/services/factoryBuilder/assest/wallAsset/deleteWallItemApi.ts index 84dee42..a959ebe 100644 --- a/app/src/services/factoryBuilder/assest/wallAsset/deleteWallItemApi.ts +++ b/app/src/services/factoryBuilder/assest/wallAsset/deleteWallItemApi.ts @@ -7,7 +7,7 @@ export const deleteWallItem = async ( ) => { try { const response = await fetch( - `${url_Backend_dwinzo}/api/v1/deleteWallItem`, + `${url_Backend_dwinzo}/api/V1/wallItems/delete`, { method: "DELETE", headers: { diff --git a/app/src/services/factoryBuilder/assest/wallAsset/getWallItemsApi.ts b/app/src/services/factoryBuilder/assest/wallAsset/getWallItemsApi.ts index 55a09f0..ca72867 100644 --- a/app/src/services/factoryBuilder/assest/wallAsset/getWallItemsApi.ts +++ b/app/src/services/factoryBuilder/assest/wallAsset/getWallItemsApi.ts @@ -3,7 +3,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR export const getWallItems = async (organization: string, projectId?: string, versionId?: string) => { try { const response = await fetch( - `${url_Backend_dwinzo}/api/V1/walls/${projectId}/${versionId}`, + `${url_Backend_dwinzo}/api/V1/wallItems/${projectId}/${versionId}`, { method: "GET", headers: { diff --git a/app/src/services/factoryBuilder/assest/wallAsset/setWallItemApi.ts b/app/src/services/factoryBuilder/assest/wallAsset/setWallItemApi.ts index b7a0dbd..4a7aab4 100644 --- a/app/src/services/factoryBuilder/assest/wallAsset/setWallItemApi.ts +++ b/app/src/services/factoryBuilder/assest/wallAsset/setWallItemApi.ts @@ -12,7 +12,7 @@ export const setWallItem = async ( scale: Object ) => { try { - const response = await fetch(`${url_Backend_dwinzo}/api/v1/setWallItems`, { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/wallItems`, { method: "POST", headers: { Authorization: "Bearer ", diff --git a/app/src/services/factoryBuilder/wall/deleteWallApi.ts b/app/src/services/factoryBuilder/wall/deleteWallApi.ts new file mode 100644 index 0000000..8a91d11 --- /dev/null +++ b/app/src/services/factoryBuilder/wall/deleteWallApi.ts @@ -0,0 +1,39 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteWallApi = async ( + projectId: string, + versionId: string, + wallUuid: string +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/deleteWall`, { + method: "PATCH", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify({ projectId, versionId, wallUuid }), + }); + + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to delete wall:", response.statusText); + } + + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to delete wall"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/factoryBuilder/wall/getWallsApi.ts b/app/src/services/factoryBuilder/wall/getWallsApi.ts new file mode 100644 index 0000000..88bf3e7 --- /dev/null +++ b/app/src/services/factoryBuilder/wall/getWallsApi.ts @@ -0,0 +1,37 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getWallsApi = async ( + projectId: string, + versionId: string, +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/Walls/${projectId}/${versionId}`, { + method: "GET", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + }); + + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to get wall:", response.statusText); + } + + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to get wall"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/factoryBuilder/wall/upsertWallApi.ts b/app/src/services/factoryBuilder/wall/upsertWallApi.ts new file mode 100644 index 0000000..023da68 --- /dev/null +++ b/app/src/services/factoryBuilder/wall/upsertWallApi.ts @@ -0,0 +1,39 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const upsertWallApi = async ( + projectId: string, + versionId: string, + wallData: Wall +) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/UpsertWall`, { + method: "POST", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify({ projectId, versionId, wallData }), + }); + + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to upsert wall:", response.statusText); + } + + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to upsert wall"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 7803df9..18facd9 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -16,6 +16,14 @@ interface BuilderState { outsideMaterial: string; insideMaterial: string; + // Floor Settings + selectedFloor: Object3D | null; + floorDepth: number; + isBeveled: boolean; + bevelStrength: number; + sideMaterial: string; + topMaterial: string; + // Decal Settings selectedDecal: Object3D | null; @@ -44,6 +52,13 @@ interface BuilderState { setWallHeight: (height: number) => void; setWallMaterial: (material: string, side: 'inside' | 'outside') => void; + // Setters - Floor + setSelectedFloor: (floor: Object3D | null) => void; + setFloorDepth: (depth: number) => void; + setBeveled: (isBeveled: boolean) => void; + setBevelStrength: (strength: number) => void; + setFloorMaterial: (material: string, side: 'side' | 'top') => void; + // Setters - Decal setSelectedDecal: (decal: Object3D | null) => void; @@ -81,6 +96,13 @@ export const useBuilderStore = create()( outsideMaterial: 'Default Material', insideMaterial: 'Material 1', + selectedFloor: null, + floorDepth: 0.3, + isBeveled: false, + bevelStrength: 0.05, + sideMaterial: 'Default Side', + topMaterial: 'Default Top', + selectedDecal: null, selectedAisle: null, @@ -147,6 +169,38 @@ export const useBuilderStore = create()( }); }, + // === Setters: Floor === + setSelectedFloor: (floor: Object3D | null) => { + set((state) => { + state.selectedFloor = floor; + }); + }, + + setFloorDepth: (depth: number) => { + set((state) => { + state.floorDepth = depth; + }); + }, + + setBeveled: (isBeveled: boolean) => { + set((state) => { + state.isBeveled = isBeveled; + }); + }, + + setBevelStrength: (strength: number) => { + set((state) => { + state.bevelStrength = strength; + }); + }, + + setFloorMaterial: (material: string, side: 'side' | 'top') => { + set((state) => { + if (side === 'side') state.sideMaterial = material; + else state.topMaterial = material; + }); + }, + // === Setters: Decal === setSelectedDecal: (decal: Object3D | null) => { diff --git a/app/src/store/builder/useFloorStore.ts b/app/src/store/builder/useFloorStore.ts index 4c89333..f1c9bfa 100644 --- a/app/src/store/builder/useFloorStore.ts +++ b/app/src/store/builder/useFloorStore.ts @@ -7,14 +7,29 @@ interface FloorStore { addFloor: (floor: Floor) => void; updateFloor: (uuid: string, updated: Partial) => void; removeFloor: (uuid: string) => void; - removePointFromFloors: (pointUuid: string) => void; + removePoint: (pointUuid: string) => Floor[]; + removeFloorByPoints: (Points: [Point, Point]) => Floor[]; clearFloors: () => void; + setPosition: ( + pointUuid: string, + position: [number, number, number] + ) => Floor | undefined; setIsBeveled: (uuid: string, isBeveled: boolean) => void; 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) => void; + removeDecal: (decalUuid: string) => void; + updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void; + updateDecalRotation: (decalUuid: string, rotation: number) => void; + updateDecalScale: (decalUuid: string, scale: number) => void; getFloorById: (uuid: string) => Floor | undefined; + getFloorsByPointId: (uuid: string) => Floor | undefined; + getFloorByPoints: (points: Point[]) => Floor | undefined; + getFloorPointById: (uuid: string) => Point | undefined; + getConnectedPoints: (uuid: string) => Point[]; } export const createFloorStore = () => { @@ -41,16 +56,98 @@ export const createFloorStore = () => { state.floors = state.floors.filter(f => f.floorUuid !== uuid); }), - removePointFromFloors: (pointUuid) => set(state => { - for (const floor of state.floors) { - floor.points = floor.points.filter(p => p.pointUuid !== pointUuid); - } - }), + removePoint: (pointUuid) => { + const removedFloors: Floor[] = []; + + set(state => { + const updatedFloors: Floor[] = []; + + for (const floor of state.floors) { + const hasPoint = floor.points.some(p => p.pointUuid === pointUuid); + if (!hasPoint) { + updatedFloors.push(floor); + continue; + } + + const remainingPoints = floor.points.filter(p => p.pointUuid !== pointUuid); + if (remainingPoints.length > 2) { + floor.points = remainingPoints; + updatedFloors.push(floor); + } else { + removedFloors.push(floor); + } + } + + state.floors = updatedFloors; + }); + + return removedFloors; + }, + removeFloorByPoints: ([pointA, pointB]) => { + const removedFloors: Floor[] = []; + + set(state => { + const updatedFloors: Floor[] = []; + + for (const floor of state.floors) { + const indices = floor.points + .map((p, i) => ({ uuid: p.pointUuid, index: i })); + + const idxA = indices.find(i => i.uuid === pointA.pointUuid)?.index ?? -1; + const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1; + + if (idxA === -1 || idxB === -1) { + updatedFloors.push(floor); + continue; + } + + const areAdjacent = + Math.abs(idxA - idxB) === 1 || + (idxA === 0 && idxB === floor.points.length - 1) || + (idxB === 0 && idxA === floor.points.length - 1); + + if (!areAdjacent) { + updatedFloors.push(floor); + continue; + } + + const remainingPoints = floor.points.filter( + p => p.pointUuid !== pointA.pointUuid && p.pointUuid !== pointB.pointUuid + ); + + if (remainingPoints.length > 2) { + floor.points = remainingPoints; + updatedFloors.push(floor); + } else { + removedFloors.push(floor); + } + } + + state.floors = updatedFloors; + }); + + return removedFloors; + }, clearFloors: () => set(state => { state.floors = []; }), + setPosition: (pointUuid, position) => { + let updatedFloor: Floor | undefined; + set((state) => { + for (const floor of state.floors) { + const point = floor.points.find((p) => p.pointUuid === pointUuid); + if (point) { + point.position = position; + updatedFloor = JSON.parse(JSON.stringify(floor)); + break; + } + } + }); + return updatedFloor; + }, + setIsBeveled: (uuid, isBeveled) => set(state => { const floor = state.floors.find(f => f.floorUuid === uuid); if (floor) { @@ -80,9 +177,97 @@ export const createFloorStore = () => { } }), + addDecal: (floorUuid, decal) => set(state => { + const floor = state.floors.find(f => f.floorUuid === floorUuid); + if (floor) { + floor.decals.push(decal); + } + }), + + 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; + } + } + }), + + removeDecal: (decalUuid) => set(state => { + for (const floor of state.floors) { + floor.decals = floor.decals.filter(d => d.decalUuid !== decalUuid); + } + }), + + updateDecalPosition: (decalUuid, position) => set(state => { + for (const floor of state.floors) { + const decal = floor.decals.find(d => d.decalUuid === decalUuid); + if (decal) { + decal.decalPosition = position; + break; + } + } + }), + + updateDecalRotation: (decalUuid, rotation) => set(state => { + for (const floor of state.floors) { + const decal = floor.decals.find(d => d.decalUuid === decalUuid); + if (decal) { + decal.decalRotation = rotation; + break; + } + } + }), + + updateDecalScale: (decalUuid, scale) => set(state => { + for (const floor of state.floors) { + const decal = floor.decals.find(d => d.decalUuid === decalUuid); + if (decal) { + decal.decalScale = scale; + break; + } + } + }), + + getFloorById: (uuid) => { return get().floors.find(f => f.floorUuid === uuid); }, + + getFloorsByPointId: (pointUuid) => { + return get().floors.find(floor => + floor.points.some(p => p.pointUuid === pointUuid) + ); + }, + + getFloorByPoints: (points) => { + return get().floors.find(floor => { + const floorPointIds = new Set(floor.points.map(p => p.pointUuid)); + const givenPointIds = new Set(points.map(p => p.pointUuid)); + return floorPointIds.size === givenPointIds.size && + [...floorPointIds].every(id => givenPointIds.has(id)); + }); + }, + + getFloorPointById: (pointUuid) => { + for (const floor of get().floors) { + const point = floor.points.find(p => p.pointUuid === pointUuid); + if (point) return point; + } + return undefined; + }, + + getConnectedPoints: (pointUuid) => { + const connected: Point[] = []; + for (const floor of get().floors) { + if (floor.points.some(p => p.pointUuid === pointUuid)) { + connected.push(...floor.points.filter(p => p.pointUuid !== pointUuid)); + } + } + return connected; + } + })) ); }; diff --git a/app/src/store/builder/useWallStore.ts b/app/src/store/builder/useWallStore.ts index 3547330..11f7130 100644 --- a/app/src/store/builder/useWallStore.ts +++ b/app/src/store/builder/useWallStore.ts @@ -5,7 +5,7 @@ interface WallStore { walls: Wall[]; setWalls: (walls: Wall[]) => void; addWall: (wall: Wall) => void; - updateWall: (uuid: string, updated: Partial) => void; + updateWall: (uuid: string, updated: Partial) => Wall | undefined; removeWall: (uuid: string) => void; clearWalls: () => void; removeWallByPoints: (Points: [Point, Point]) => Wall | undefined; @@ -17,11 +17,11 @@ interface WallStore { updateDecalScale: (decalUuid: string, scale: number) => void; removePoint: (pointUuid: string) => Wall[]; - setPosition: (pointUuid: string, position: [number, number, number]) => void; + setPosition: (pointUuid: string, position: [number, number, number]) => Wall[] | []; setLayer: (pointUuid: string, layer: number) => void; getWallById: (uuid: string) => Wall | undefined; - getWallByPointId: (uuid: string) => Wall | undefined; + getWallsByPointId: (uuid: string) => Wall[] | []; getWallByPoints: (points: Point[]) => Wall | undefined; getWallPointById: (uuid: string) => Point | undefined; getConnectedPoints: (uuid: string) => Point[]; @@ -40,12 +40,17 @@ export const createWallStore = () => { state.walls.push(wall); }), - updateWall: (uuid, updated) => set((state) => { - const wall = state.walls.find(w => w.wallUuid === uuid); - if (wall) { - Object.assign(wall, updated); - } - }), + updateWall: (uuid, updated) => { + let updatedWall: Wall | undefined; + set((state) => { + const wall = state.walls.find(w => w.wallUuid === uuid); + if (wall) { + Object.assign(wall, updated); + updatedWall = JSON.parse(JSON.stringify(wall)); + } + }); + return updatedWall; + }, removeWall: (uuid) => set((state) => { state.walls = state.walls.filter(w => w.wallUuid !== uuid); @@ -144,14 +149,19 @@ export const createWallStore = () => { return removedWalls; }, - setPosition: (pointUuid, position) => set((state) => { - for (const wall of state.walls) { - const point = wall.points.find(p => p.pointUuid === pointUuid); - if (point) { - point.position = position; + setPosition: (pointUuid, position) => { + let updatedWalls: Wall[] = []; + set((state) => { + for (const wall of state.walls) { + const point = wall.points.find(p => p.pointUuid === pointUuid); + if (point) { + point.position = position; + updatedWalls.push(wall); + } } - } - }), + }); + return updatedWalls; + }, setLayer: (pointUuid, layer) => set((state) => { for (const wall of state.walls) { @@ -166,13 +176,10 @@ export const createWallStore = () => { return get().walls.find(w => w.wallUuid === uuid); }, - getWallByPointId: (uuid) => { - for (const wall of get().walls) { - if (wall.points.some(p => p.pointUuid === uuid)) { - return wall; - } - } - return undefined; + getWallsByPointId: (uuid) => { + return get().walls.filter((a) => { + return a.points.some((p) => p.pointUuid === uuid); + }) }, getWallByPoints: (point) => {