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:
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
function FloorInstance({ floor }: { floor: Floor }) {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FloorInstance
|
||||
121
app/src/modules/builder/floor/Instances/floorInstances.tsx
Normal file
121
app/src/modules/builder/floor/Instances/floorInstances.tsx
Normal 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;
|
||||
175
app/src/modules/builder/floor/floorCreator/floorCreator.tsx
Normal file
175
app/src/modules/builder/floor/floorCreator/floorCreator.tsx
Normal 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
|
||||
164
app/src/modules/builder/floor/floorCreator/referenceFloor.tsx
Normal file
164
app/src/modules/builder/floor/floorCreator/referenceFloor.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
31
app/src/modules/builder/floor/floorGroup.tsx
Normal file
31
app/src/modules/builder/floor/floorGroup.tsx
Normal 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
|
||||
Reference in New Issue
Block a user