Refactor aisle API services: remove createAisleApi, update deleteAisleApi to include versionId, and modify wall asset APIs for consistency in endpoint naming. Enhance floor and wall store functionality with new setters and methods for managing floors and walls, including decal handling. Introduce FloorInstance and FloorInstances components for rendering floor data, and implement FloorCreator for interactive floor creation. Add reference floor visualization with snapping capabilities. Update wall API services for improved error handling and response management.

This commit is contained in:
2025-06-26 17:47:32 +05:30
parent b4745451d2
commit 1e88006780
28 changed files with 1442 additions and 122 deletions

View File

@@ -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;

View File

@@ -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]);
}

View File

@@ -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<Types.ThreeState>(); // 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() {
<Ground grid={grid} plane={plane} />
<Bvh firstHitOnly>
<DistanceText key={toggleView} />
{/* <DistanceText key={toggleView} /> */}
</Bvh>
<ReferenceDistanceText
@@ -207,7 +208,7 @@ export default function Builder() {
/>
</Bvh>
<WallsAndWallItems
{/* <WallsAndWallItems
CSGGroup={CSGGroup}
setSelectedItemsIndex={setSelectedItemsIndex}
selectedItemsIndex={selectedItemsIndex}
@@ -215,16 +216,16 @@ export default function Builder() {
csg={csg}
lines={lines}
hoveredDeletableWallItem={hoveredDeletableWallItem}
/>
/> */}
<Bvh firstHitOnly>
<FloorGroup
{/* <FloorGroup
floorGroup={floorGroup}
lines={lines}
referencePole={referencePole}
hoveredDeletablePillar={hoveredDeletablePillar}
/>
/> */}
<FloorPlanGroup
floorPlanGroup={floorPlanGroup}
@@ -276,7 +277,9 @@ export default function Builder() {
<LayoutImage />
</Bvh>
{/* <WallGroup /> */}
<WallGroup />
<FloorGroup />
</>
);
}

View File

@@ -0,0 +1,10 @@
import React from 'react'
function FloorInstance({ floor }: { floor: Floor }) {
return (
<>
</>
)
}
export default FloorInstance

View File

@@ -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<string>();
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 && (
<mesh name='Floors-Group'>
{floors.map((floor) => (
<FloorInstance key={floor.floorUuid} floor={floor} />
))}
</mesh>
)}
{toggleView && (
<>
<group name='Floor-Points-Group'>
{allPoints.map((point) => (
<Point key={point.pointUuid} point={point} />
))}
</group>
<group name='Floor-Lines-Group'>
{allLines.map(({ start, end, key }) => (
<Line key={key} points={[start, end]} />
))}
{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 (
<React.Fragment key={key}>
{toggleView &&
<Html
key={`${start.pointUuid}_${end.pointUuid}`}
userData={line}
position={[textPosition.x, 1, textPosition.z]}
wrapperClass="distance-text-wrapper"
className="distance-text"
zIndexRange={[1, 0]}
prepend
sprite
>
<div
key={key}
className={`distance ${key}`}
>
{distance.toFixed(2)} m
</div>
</Html>
}
</React.Fragment>
)
})}
</group>
</>
)}
</>
)
}
export default FloorInstances;

View File

@@ -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<Point[]>([]);
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 &&
<>
<group name='Wall-Reference-Points-Group'>
{tempPoints.map((point) => (
<ReferencePoint key={point.pointUuid} point={point} />
))}
</group>
{tempPoints.length > 0 &&
<ReferenceFloor tempPoints={tempPoints} />
}
</>
}
</>
)
}
export default FloorCreator

View File

@@ -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<ReferenceFloorProps>) {
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<Floor | null>(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 (
<group name="Floor-Reference-Group">
{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 (
<group key={`${p1.pointUuid}-${p2.pointUuid}`}>
<ReferenceLine points={[p1, p2]} />
{toggleView && (
<Html
position={[mid.x, 1, mid.z]}
wrapperClass="distance-text-wrapper"
className="distance-text"
zIndexRange={[1, 0]}
prepend
sprite
>
<div className={`distance ref-line-${idx}`}>{dist.toFixed(2)} m</div>
</Html>
)}
</group>
);
})}
{tempPoints.length >= 3 && (
<Floor floor={tempFloor.points} />
)}
</group>
);
}
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 (
<group name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
<Extrude
args={[shape, { depth: 0.001, bevelEnabled: false }]}
position={[0, 0, 0]}
receiveShadow
>
<meshStandardMaterial color={savedTheme === "dark" ? "#d2baff" : "#6f42c1"} depthWrite={false} transparent opacity={0.3} side={THREE.DoubleSide} />
</Extrude>
</group>
);
}

View File

@@ -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 (
<>
<FloorCreator />
<FloorInstances />
</>
)
}
export default FloorGroup

View File

@@ -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 || '',);
}
}

View File

@@ -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<LineProps>) {
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<THREE.Vector3 | null>(null);
const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore();
@@ -79,7 +90,29 @@ function Line({ points }: Readonly<LineProps>) {
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<LineProps>) {
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<LineProps>) {
};
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
}
}

View File

@@ -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,
};
};

View File

@@ -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<THREE.ShaderMaterial>(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<THREE.Vector3 | null>(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';

View File

@@ -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 (
<mesh
position={new THREE.Vector3(...point.position)}
name={pointName}
>
<boxGeometry args={boxScale} />
<shaderMaterial

View File

@@ -28,8 +28,8 @@ function WallInstances() {
const points: Point[] = [];
const seenUuids = new Set<string>();
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);

View File

@@ -36,46 +36,45 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
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);
}

View File

@@ -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<Point[]>([]);
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 (
<>

View File

@@ -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 (
<>

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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 <access_token>",

View File

@@ -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 <access_token>",
"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");
}
}
};

View File

@@ -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 <access_token>",
"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");
}
}
};

View File

@@ -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 <access_token>",
"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");
}
}
};

View File

@@ -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<BuilderState>()(
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<BuilderState>()(
});
},
// === 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) => {

View File

@@ -7,14 +7,29 @@ interface FloorStore {
addFloor: (floor: Floor) => void;
updateFloor: (uuid: string, updated: Partial<Floor>) => 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;
}
}))
);
};

View File

@@ -5,7 +5,7 @@ interface WallStore {
walls: Wall[];
setWalls: (walls: Wall[]) => void;
addWall: (wall: Wall) => void;
updateWall: (uuid: string, updated: Partial<Wall>) => void;
updateWall: (uuid: string, updated: Partial<Wall>) => 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) => {