2025-06-10 15:28:23 +05:30
|
|
|
import * as THREE from 'three';
|
2025-06-25 15:26:53 +05:30
|
|
|
import { useThree } from '@react-three/fiber';
|
2025-06-23 13:38:26 +05:30
|
|
|
import { useEffect, useMemo, useState } from "react";
|
|
|
|
|
import { DragControls, Tube } from '@react-three/drei';
|
2025-06-26 17:47:32 +05:30
|
|
|
import { useSocketStore, useToolMode } from '../../../store/builder/store';
|
2025-06-23 13:38:26 +05:30
|
|
|
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
|
2025-06-25 15:26:53 +05:30
|
|
|
import { useSceneContext } from '../../scene/sceneContext';
|
|
|
|
|
import * as Constants from '../../../types/world/worldConstants';
|
2025-06-26 17:47:32 +05:30
|
|
|
import { useVersionContext } from '../version/versionContext';
|
|
|
|
|
import { useParams } from 'react-router-dom';
|
|
|
|
|
import { getUserData } from '../../../functions/getUserData';
|
2025-07-11 12:31:00 +05:30
|
|
|
import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors';
|
2025-07-11 16:27:55 +05:30
|
|
|
import { useSelectedPoints } from '../../../store/simulation/useSimulationStore';
|
2025-08-23 17:02:21 +05:30
|
|
|
import { calculateAssetTransformationOnWall } from '../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall';
|
2025-06-10 15:28:23 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
|
|
|
|
|
import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
|
|
|
|
|
import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
|
|
|
|
|
import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi';
|
|
|
|
|
import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
|
|
|
|
|
import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi';
|
|
|
|
|
import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
|
|
|
|
|
import { deleteWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
interface LineProps {
|
|
|
|
|
points: [Point, Point];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Line({ points }: Readonly<LineProps>) {
|
2025-06-23 13:38:26 +05:30
|
|
|
const [isHovered, setIsHovered] = useState(false);
|
2025-07-11 12:31:00 +05:30
|
|
|
const { raycaster, camera, pointer } = useThree();
|
2025-06-23 13:38:26 +05:30
|
|
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
|
|
|
|
const [isDeletable, setIsDeletable] = useState(false);
|
2025-06-26 17:47:32 +05:30
|
|
|
const { socket } = useSocketStore();
|
2025-06-23 13:38:26 +05:30
|
|
|
const { toolMode } = useToolMode();
|
2025-08-23 17:02:21 +05:30
|
|
|
const { wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore } = useSceneContext();
|
2025-07-29 17:20:34 +05:30
|
|
|
const { push2D } = undoRedo2DStore();
|
2025-08-23 17:02:21 +05:30
|
|
|
const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore();
|
2025-08-23 15:27:17 +05:30
|
|
|
const { removeWallByPoints, setPosition: setWallPosition, getWallByPoints, getConnectedWallsByWallId } = wallStore();
|
2025-07-29 17:20:34 +05:30
|
|
|
const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore();
|
|
|
|
|
const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore();
|
2025-06-26 17:47:32 +05:30
|
|
|
const { userId, organization } = getUserData();
|
|
|
|
|
const { selectedVersionStore } = useVersionContext();
|
|
|
|
|
const { selectedVersion } = selectedVersionStore();
|
|
|
|
|
const { projectId } = useParams();
|
2025-06-23 13:38:26 +05:30
|
|
|
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
|
|
|
|
const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore();
|
2025-07-11 16:27:55 +05:30
|
|
|
const { selectedPoints } = useSelectedPoints();
|
2025-06-23 13:38:26 +05:30
|
|
|
|
2025-07-30 13:51:30 +05:30
|
|
|
const [initialPositions, setInitialPositions] = useState<{
|
|
|
|
|
aisles?: Aisle[],
|
|
|
|
|
walls?: Wall[],
|
|
|
|
|
floors?: Floor[],
|
|
|
|
|
zones?: Zone[]
|
|
|
|
|
}>({});
|
|
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
const path = useMemo(() => {
|
|
|
|
|
const [start, end] = points.map(p => new THREE.Vector3(...p.position));
|
|
|
|
|
return new THREE.LineCurve3(start, end);
|
|
|
|
|
}, [points]);
|
|
|
|
|
|
|
|
|
|
const colors = getColor(points[0]);
|
|
|
|
|
|
|
|
|
|
function getColor(point: Point) {
|
|
|
|
|
if (point.pointType === 'Aisle') {
|
|
|
|
|
return {
|
|
|
|
|
defaultLineColor: Constants.lineConfig.aisleColor,
|
|
|
|
|
defaultDeleteColor: Constants.lineConfig.deleteColor,
|
|
|
|
|
}
|
|
|
|
|
} else if (point.pointType === 'Floor') {
|
|
|
|
|
return {
|
|
|
|
|
defaultLineColor: Constants.lineConfig.floorColor,
|
|
|
|
|
defaultDeleteColor: Constants.lineConfig.deleteColor,
|
|
|
|
|
}
|
|
|
|
|
} else if (point.pointType === 'Wall') {
|
|
|
|
|
return {
|
|
|
|
|
defaultLineColor: Constants.lineConfig.wallColor,
|
|
|
|
|
defaultDeleteColor: Constants.lineConfig.deleteColor,
|
|
|
|
|
}
|
|
|
|
|
} else if (point.pointType === 'Zone') {
|
|
|
|
|
return {
|
|
|
|
|
defaultLineColor: Constants.lineConfig.zoneColor,
|
|
|
|
|
defaultDeleteColor: Constants.lineConfig.deleteColor,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return {
|
|
|
|
|
defaultLineColor: Constants.lineConfig.defaultColor,
|
|
|
|
|
defaultDeleteColor: Constants.lineConfig.deleteColor,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 13:38:26 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (toolMode === '2D-Delete') {
|
|
|
|
|
if (isHovered && !hoveredPoint) {
|
|
|
|
|
setIsDeletable(true);
|
|
|
|
|
} else {
|
|
|
|
|
setIsDeletable(false);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setIsDeletable(false);
|
|
|
|
|
}
|
|
|
|
|
}, [isHovered, colors.defaultLineColor, colors.defaultDeleteColor, toolMode, hoveredPoint]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (hoveredLine && (hoveredLine[0].pointUuid !== points[0].pointUuid || hoveredLine[1].pointUuid !== points[1].pointUuid)) {
|
|
|
|
|
setIsHovered(false);
|
|
|
|
|
}
|
|
|
|
|
}, [hoveredLine])
|
|
|
|
|
|
|
|
|
|
const handlePointClick = (points: [Point, Point]) => {
|
|
|
|
|
if (toolMode === '2D-Delete') {
|
|
|
|
|
if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
|
2025-06-26 17:47:32 +05:30
|
|
|
const removedWall = removeWallByPoints(points);
|
|
|
|
|
if (removedWall && projectId) {
|
|
|
|
|
|
2025-08-23 17:02:21 +05:30
|
|
|
const assetsOnWall = getWallAssetsByWall(removedWall.wallUuid);
|
|
|
|
|
|
|
|
|
|
assetsOnWall.forEach((asset) => {
|
|
|
|
|
if (projectId && asset) {
|
|
|
|
|
|
|
|
|
|
removeWallAsset(asset.modelUuid);
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
|
|
|
|
|
} else {
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization,
|
|
|
|
|
modelUuid: asset.modelUuid,
|
|
|
|
|
wallUuid: asset.wallUuid
|
|
|
|
|
}
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:wall-asset:delete', data);
|
|
|
|
|
}
|
2025-08-23 17:02:21 +05:30
|
|
|
}
|
|
|
|
|
})
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
deleteWallApi(projectId, selectedVersion?.versionId || '', removedWall.wallUuid);
|
|
|
|
|
} else {
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
wallUuid: removedWall.wallUuid,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:model-Wall:delete', data);
|
|
|
|
|
}
|
2025-07-29 17:20:34 +05:30
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [
|
|
|
|
|
{
|
|
|
|
|
actionType: 'Line-Delete',
|
|
|
|
|
point: {
|
|
|
|
|
type: 'Wall',
|
|
|
|
|
lineData: removedWall,
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
2025-06-26 17:47:32 +05:30
|
|
|
}
|
2025-07-29 17:20:34 +05:30
|
|
|
|
2025-06-26 17:47:32 +05:30
|
|
|
setHoveredLine(null);
|
|
|
|
|
}
|
|
|
|
|
if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
|
2025-07-29 17:20:34 +05:30
|
|
|
const Floors = getFloorsByPoints(points);
|
2025-06-27 10:48:48 +05:30
|
|
|
const { removedFloors, updatedFloors } = removeFloorByPoints(points);
|
|
|
|
|
if (removedFloors.length > 0) {
|
|
|
|
|
removedFloors.forEach(floor => {
|
|
|
|
|
if (projectId) {
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
|
|
|
|
|
} else {
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
floorUuid: floor.floorUuid,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:model-Floor:delete', data);
|
|
|
|
|
}
|
2025-06-27 10:48:48 +05:30
|
|
|
}
|
|
|
|
|
});
|
2025-07-29 17:20:34 +05:30
|
|
|
|
|
|
|
|
const removedFloorsData = removedFloors.map((floor) => ({
|
|
|
|
|
type: "Floor" as const,
|
|
|
|
|
lineData: floor,
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [
|
|
|
|
|
{
|
|
|
|
|
actionType: 'Lines-Delete',
|
|
|
|
|
points: removedFloorsData
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
2025-06-27 10:48:48 +05:30
|
|
|
}
|
|
|
|
|
if (updatedFloors.length > 0) {
|
|
|
|
|
updatedFloors.forEach(floor => {
|
|
|
|
|
if (projectId) {
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
|
|
|
|
|
} else {
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
floorData: floor,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:model-Floor:add', data);
|
|
|
|
|
}
|
2025-06-27 10:48:48 +05:30
|
|
|
}
|
|
|
|
|
});
|
2025-07-29 17:20:34 +05:30
|
|
|
|
|
|
|
|
const updatedFloorsData = updatedFloors.map((floor) => ({
|
|
|
|
|
type: "Floor" as const,
|
|
|
|
|
lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
|
|
|
|
|
newData: floor,
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [
|
|
|
|
|
{
|
|
|
|
|
actionType: 'Lines-Update',
|
|
|
|
|
points: updatedFloorsData
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
2025-06-27 10:48:48 +05:30
|
|
|
}
|
|
|
|
|
|
2025-06-25 15:26:53 +05:30
|
|
|
setHoveredLine(null);
|
2025-06-23 13:38:26 +05:30
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
|
2025-07-29 17:20:34 +05:30
|
|
|
const Zones = getZonesByPoints(points);
|
2025-06-30 12:22:42 +05:30
|
|
|
const { removedZones, updatedZones } = removeZoneByPoints(points);
|
|
|
|
|
if (removedZones.length > 0) {
|
|
|
|
|
removedZones.forEach(zone => {
|
|
|
|
|
if (projectId) {
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
|
|
|
|
|
} else {
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
zoneUuid: zone.zoneUuid,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:zone:delete', data);
|
|
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
}
|
|
|
|
|
});
|
2025-07-29 17:20:34 +05:30
|
|
|
|
|
|
|
|
const removedZonesData = removedZones.map((zone) => ({
|
|
|
|
|
type: "Zone" as const,
|
|
|
|
|
lineData: zone,
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [
|
|
|
|
|
{
|
|
|
|
|
actionType: 'Lines-Delete',
|
|
|
|
|
points: removedZonesData
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
2025-06-30 12:22:42 +05:30
|
|
|
}
|
|
|
|
|
if (updatedZones.length > 0) {
|
|
|
|
|
updatedZones.forEach(zone => {
|
|
|
|
|
if (projectId) {
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-09-01 17:36:40 +05:30
|
|
|
|
|
|
|
|
// API
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
|
|
|
|
|
} else {
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
zoneData: zone,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:zone:add', data);
|
2025-06-30 12:22:42 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-07-29 17:20:34 +05:30
|
|
|
|
|
|
|
|
const updatedZonesData = updatedZones.map((zone) => ({
|
|
|
|
|
type: "Zone" as const,
|
|
|
|
|
lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
|
|
|
|
|
newData: zone,
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [
|
|
|
|
|
{
|
|
|
|
|
actionType: 'Lines-Update',
|
|
|
|
|
points: updatedZonesData
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
2025-06-30 12:22:42 +05:30
|
|
|
}
|
2025-07-29 17:20:34 +05:30
|
|
|
|
|
|
|
|
setHoveredLine(null);
|
2025-06-30 12:22:42 +05:30
|
|
|
}
|
2025-07-11 12:31:00 +05:30
|
|
|
handleCanvasCursors('default');
|
2025-06-23 13:38:26 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleDrag = (points: [Point, Point]) => {
|
|
|
|
|
if (toolMode === 'move' && isHovered && dragOffset) {
|
|
|
|
|
raycaster.setFromCamera(pointer, camera);
|
|
|
|
|
const intersectionPoint = new THREE.Vector3();
|
|
|
|
|
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
|
|
|
|
|
|
|
|
|
if (hit) {
|
2025-07-11 12:31:00 +05:30
|
|
|
handleCanvasCursors('grabbing');
|
2025-06-23 13:38:26 +05:30
|
|
|
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
|
|
|
|
|
|
|
|
|
const start = new THREE.Vector3(...points[0].position);
|
|
|
|
|
const end = new THREE.Vector3(...points[1].position);
|
|
|
|
|
const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
|
|
|
|
|
|
|
|
|
|
const delta = new THREE.Vector3().subVectors(positionWithOffset, midPoint);
|
|
|
|
|
|
|
|
|
|
const newStart = new THREE.Vector3().addVectors(start, delta);
|
|
|
|
|
const newEnd = new THREE.Vector3().addVectors(end, delta);
|
|
|
|
|
|
2025-06-26 17:47:32 +05:30
|
|
|
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]);
|
|
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
|
|
|
|
|
setZonePosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
|
|
|
|
|
setZonePosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
|
|
|
|
|
}
|
2025-06-23 13:38:26 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDragStart = (points: [Point, Point]) => {
|
|
|
|
|
raycaster.setFromCamera(pointer, camera);
|
|
|
|
|
const intersectionPoint = new THREE.Vector3();
|
|
|
|
|
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
|
|
|
|
|
|
|
|
|
if (hit && !hoveredPoint) {
|
|
|
|
|
const start = new THREE.Vector3(...points[0].position);
|
|
|
|
|
const end = new THREE.Vector3(...points[1].position);
|
|
|
|
|
const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
|
|
|
|
|
|
|
|
|
|
const offset = new THREE.Vector3().subVectors(midPoint, hit);
|
|
|
|
|
setDragOffset(offset);
|
2025-07-30 13:51:30 +05:30
|
|
|
|
2025-08-23 15:27:17 +05:30
|
|
|
if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
|
|
|
|
|
const wall = getWallByPoints(points);
|
|
|
|
|
if (wall) {
|
|
|
|
|
const walls = getConnectedWallsByWallId(wall.wallUuid, false);
|
|
|
|
|
setInitialPositions({ walls });
|
|
|
|
|
}
|
2025-09-02 15:21:13 +05:30
|
|
|
} else if (points[0].pointType === 'Floor') {
|
2025-07-30 13:51:30 +05:30
|
|
|
const floors = getFloorsByPointId(points[0].pointUuid);
|
|
|
|
|
setInitialPositions({ floors });
|
2025-09-02 15:21:13 +05:30
|
|
|
} else if (points[0].pointType === 'Zone') {
|
2025-07-30 13:51:30 +05:30
|
|
|
const zones = getZonesByPointId(points[0].pointUuid);
|
|
|
|
|
setInitialPositions({ zones });
|
|
|
|
|
}
|
2025-06-23 13:38:26 +05:30
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-23 17:02:21 +05:30
|
|
|
const handleDragEnd = (points: [Point, Point]) => {
|
2025-06-26 17:47:32 +05:30
|
|
|
if (toolMode !== 'move' || !dragOffset) return;
|
2025-07-11 12:31:00 +05:30
|
|
|
handleCanvasCursors('default');
|
2025-06-23 13:38:26 +05:30
|
|
|
setDragOffset(null);
|
2025-08-23 15:27:17 +05:30
|
|
|
|
2025-06-23 13:38:26 +05:30
|
|
|
if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
|
2025-08-23 15:27:17 +05:30
|
|
|
const wall = getWallByPoints(points);
|
|
|
|
|
if (!wall) return;
|
|
|
|
|
const updatedWalls = getConnectedWallsByWallId(wall.wallUuid, false);
|
2025-06-26 17:47:32 +05:30
|
|
|
|
|
|
|
|
if (updatedWalls.length > 0 && projectId) {
|
|
|
|
|
updatedWalls.forEach(updatedWall => {
|
|
|
|
|
|
2025-08-23 17:02:21 +05:30
|
|
|
const initialWall = initialPositions.walls?.find(w => w.wallUuid === updatedWall.wallUuid);
|
|
|
|
|
|
|
|
|
|
if (initialWall) {
|
|
|
|
|
const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid);
|
|
|
|
|
|
|
|
|
|
assetsOnWall.forEach(asset => {
|
|
|
|
|
const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall);
|
|
|
|
|
|
|
|
|
|
const updatedWallAsset = updateWallAsset(asset.modelUuid, {
|
|
|
|
|
position: [position[0], asset.position[1], position[2]],
|
|
|
|
|
rotation: rotation
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (projectId && updatedWallAsset) {
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
|
|
|
|
|
} else {
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
wallAssetData: updatedWallAsset,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-08-23 17:02:21 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:wall-asset:add', data);
|
|
|
|
|
}
|
2025-08-23 17:02:21 +05:30
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
|
|
|
|
|
} else {
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-26 17:47:32 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
wallData: updatedWall,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
socket.emit('v1:model-Wall:add', data);
|
|
|
|
|
}
|
2025-06-26 17:47:32 +05:30
|
|
|
})
|
2025-07-30 13:51:30 +05:30
|
|
|
|
|
|
|
|
if (initialPositions.walls && initialPositions.walls.length > 0) {
|
|
|
|
|
const updatedPoints = initialPositions.walls.map((wall) => ({
|
|
|
|
|
type: "Wall" as const,
|
|
|
|
|
lineData: wall,
|
|
|
|
|
newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid),
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [{
|
|
|
|
|
actionType: 'Lines-Update',
|
|
|
|
|
points: updatedPoints,
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-26 17:47:32 +05:30
|
|
|
}
|
|
|
|
|
} else if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
|
2025-06-27 10:48:48 +05:30
|
|
|
const updatedFloors1 = getFloorsByPointId(points[0].pointUuid);
|
|
|
|
|
const updatedFloors2 = getFloorsByPointId(points[1].pointUuid);
|
|
|
|
|
const updatedFloors = [...updatedFloors1, ...updatedFloors2].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid));
|
|
|
|
|
|
|
|
|
|
if (updatedFloors.length > 0 && projectId) {
|
|
|
|
|
updatedFloors.forEach(updatedFloor => {
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
|
|
|
|
|
} else {
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
floorData: updatedFloor,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-06-27 10:48:48 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:model-Floor:add', data);
|
|
|
|
|
}
|
2025-06-27 10:48:48 +05:30
|
|
|
})
|
2025-07-30 13:51:30 +05:30
|
|
|
|
|
|
|
|
if (initialPositions.floors && initialPositions.floors.length > 0) {
|
|
|
|
|
const updatedPoints = initialPositions.floors.map((floor) => ({
|
|
|
|
|
type: "Floor" as const,
|
|
|
|
|
lineData: floor,
|
|
|
|
|
newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid),
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [{
|
|
|
|
|
actionType: 'Lines-Update',
|
|
|
|
|
points: updatedPoints,
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-27 10:48:48 +05:30
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
} else if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
|
|
|
|
|
const updatedZones1 = getZonesByPointId(points[0].pointUuid);
|
|
|
|
|
const updatedZones2 = getZonesByPointId(points[1].pointUuid);
|
|
|
|
|
const updatedZones = [...updatedZones1, ...updatedZones2].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid));
|
|
|
|
|
|
|
|
|
|
if (updatedZones.length > 0 && projectId) {
|
|
|
|
|
updatedZones.forEach(updatedZone => {
|
2025-09-02 10:48:58 +05:30
|
|
|
if (!socket?.connected) {
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// API
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
|
|
|
|
|
} else {
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
// SOCKET
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
const data = {
|
|
|
|
|
zoneData: updatedZone,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
userId: userId,
|
|
|
|
|
organization: organization
|
|
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
|
2025-09-01 17:36:40 +05:30
|
|
|
socket.emit('v1:zone:add', data);
|
|
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
})
|
2025-07-30 13:51:30 +05:30
|
|
|
|
|
|
|
|
if (initialPositions.zones && initialPositions.zones.length > 0) {
|
|
|
|
|
const updatedPoints = initialPositions.zones.map((zone) => ({
|
|
|
|
|
type: "Zone" as const,
|
|
|
|
|
lineData: zone,
|
|
|
|
|
newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid),
|
|
|
|
|
timeStamp: new Date().toISOString(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
push2D({
|
|
|
|
|
type: 'Draw',
|
|
|
|
|
actions: [{
|
|
|
|
|
actionType: 'Lines-Update',
|
|
|
|
|
points: updatedPoints,
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-30 12:22:42 +05:30
|
|
|
}
|
2025-06-23 13:38:26 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
return (
|
2025-06-23 13:38:26 +05:30
|
|
|
<DragControls
|
|
|
|
|
axisLock="y"
|
|
|
|
|
autoTransform={false}
|
|
|
|
|
onDragStart={() => handleDragStart(points)}
|
|
|
|
|
onDrag={() => handleDrag(points)}
|
|
|
|
|
onDragEnd={() => handleDragEnd(points)}
|
2025-06-10 15:28:23 +05:30
|
|
|
>
|
2025-06-23 13:38:26 +05:30
|
|
|
<Tube
|
|
|
|
|
name={`${points[0].pointType}-Line`}
|
|
|
|
|
key={`${points[0].pointUuid}-${points[1].pointUuid}`}
|
|
|
|
|
uuid={`${points[0].pointUuid}-${points[1].pointUuid}`}
|
|
|
|
|
userData={{ points, path }}
|
|
|
|
|
args={[path, Constants.lineConfig.tubularSegments, Constants.lineConfig.radius, Constants.lineConfig.radialSegments, false]}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
handlePointClick(points);
|
|
|
|
|
}}
|
2025-07-11 16:27:55 +05:30
|
|
|
onPointerOver={(e) => {
|
2025-07-14 09:48:02 +05:30
|
|
|
if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) {
|
2025-06-23 13:38:26 +05:30
|
|
|
setHoveredLine(points);
|
|
|
|
|
setIsHovered(true)
|
|
|
|
|
if (toolMode === 'move' && !hoveredPoint) {
|
2025-07-11 12:31:00 +05:30
|
|
|
handleCanvasCursors('grab');
|
2025-06-23 13:38:26 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onPointerOut={() => {
|
2025-07-14 09:48:02 +05:30
|
|
|
if (hoveredLine && isHovered) {
|
2025-06-23 13:38:26 +05:30
|
|
|
setHoveredLine(null);
|
2025-07-14 09:48:02 +05:30
|
|
|
if (!hoveredPoint) {
|
2025-07-11 18:18:27 +05:30
|
|
|
handleCanvasCursors('default');
|
|
|
|
|
}
|
2025-06-23 13:38:26 +05:30
|
|
|
}
|
|
|
|
|
setIsHovered(false)
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<meshStandardMaterial color={isDeletable ? colors.defaultDeleteColor : colors.defaultLineColor} />
|
|
|
|
|
</Tube>
|
|
|
|
|
</DragControls >
|
2025-06-10 15:28:23 +05:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Line;
|