old dwinzo files updated

This commit is contained in:
Vishnu 2025-03-22 18:33:02 +05:30
parent 111a2e6581
commit 6987e38750
186 changed files with 19940 additions and 389 deletions

View File

@ -0,0 +1,11 @@
# Port on which the Docsify documentation server will run.
DOCSIFY_PORT=8201
# Base URL for the server socket API, used for real-time communication (e.g., WebSockets).
REACT_APP_SERVER_SOCKET_API_BASE_URL=185.100.212.76:8000
# Base URL for the server REST API, used for HTTP requests to the backend server.
REACT_APP_SERVER_REST_API_BASE_URL=185.100.212.76:5000
# Base URL for the server marketplace API.
REACT_APP_SERVER_MARKETPLACE_URL=185.100.212.76:50011

4571
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,18 +10,34 @@
"preview": "vite preview"
},
"dependencies": {
"@react-three/csg": "^4.0.0",
"@react-three/drei": "^10.0.4",
"@react-three/fiber": "^9.1.0",
"@react-three/postprocessing": "^3.0.4",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"@turf/turf": "^7.2.0",
"@types/jest": "^29.5.14",
"@use-gesture/react": "^10.3.1",
"chart.js": "^4.4.8",
"gsap": "^3.12.7",
"leva": "^0.10.0",
"mqtt": "^5.10.4",
"path": "^0.12.7",
"postprocessing": "^6.37.1",
"react": "^19.0.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.3.0",
"react-toastify": "^11.0.5",
"socket.io-client": "^4.8.1",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/node": "^22.13.10",
"@types/react": "^19.0.10",
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",

View File

@ -1,19 +1,24 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Dashboard from './pages/Dashboard';
import Project from './pages/Project';
import UserAuth from './pages/UserAuth';
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Dashboard from "./pages/Dashboard";
import Project from "./pages/Project";
import UserAuth from "./pages/UserAuth";
import ToastProvider from "./components/templates/ToastProvider";
const App: React.FC = () => {
console.log("");
return (
<Router>
<Routes>
<Route path="/" element={<UserAuth />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/project" element={<Project />} />
</Routes>
</Router>
<ToastProvider>
<Router>
<Routes>
<Route
path="/"
element={<UserAuth />}
/>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/project" element={<Project />} />
</Routes>
</Router>
</ToastProvider>
);
};

View File

@ -326,22 +326,42 @@ export function FilterIcon() {
);
}
export function EyeDroperIcon() {
export function EyeDroperIcon({ isActive }: { isActive: boolean }) {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.00002 2.00003L6.25002 3.75003L5.8468 3.34681C5.65731 3.15732 5.34844 3.16283 5.16583 3.35896C4.99437 3.54312 4.99701 3.82926 5.17184 4.01023L7.93895 6.87443C8.12606 7.0681 8.43585 7.06999 8.6253 6.8786L8.68128 6.82205C8.86021 6.64129 8.86708 6.35233 8.69694 6.16327L8.28001 5.7L10 4.00003C10.5523 3.44774 10.5523 2.55231 10 2.00003C9.44772 1.44774 8.55232 1.44774 8.00002 2.00003Z"
fill="var(--text-color)"
d="M6.5625 3.5L10.0625 7"
stroke={
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
}
strokeWidth="0.875"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 5.00005L3.20294 6.7971C2.94351 7.0565 2.8138 7.18625 2.72104 7.3376C2.63879 7.4718 2.57819 7.61815 2.54144 7.7712C2.5 7.9438 2.5 8.12725 2.5 8.49415V9.00005L1.5 10.5L3 9.50005H3.50589C3.87277 9.50005 4.05621 9.50005 4.22885 9.4586C4.3819 9.42185 4.52822 9.36125 4.66242 9.279C4.8138 9.18625 4.94351 9.0565 5.20295 8.7971L6.9 7.02"
stroke="var(--text-color)"
d="M7.4375 4.37266L10.0625 1.74766C10.5437 1.26641 11.3312 1.26641 11.8125 1.74766C12.2937 2.22891 12.2937 3.01641 11.8125 3.49766L9.1875 6.12266"
stroke={
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
}
strokeWidth="0.875"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.30625 4.50391L2.49375 9.31641C2.23125 9.57891 2.14375 9.88516 2.14375 10.2352C1.925 10.3664 1.75 10.6289 1.75 10.9352C1.75 11.4164 2.14375 11.8102 2.625 11.8102C2.93125 11.8102 3.19375 11.6352 3.36875 11.4164C3.675 11.4164 4.025 11.2852 4.2875 11.0664L9.1 6.25391"
stroke={
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
}
strokeWidth="0.875"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -457,10 +477,37 @@ export function InfoIcon() {
<path
d="M6.00006 10.3175C8.45219 10.3175 10.4401 8.32963 10.4401 5.8775C10.4401 3.42536 8.45219 1.4375 6.00006 1.4375C3.54792 1.4375 1.56006 3.42536 1.56006 5.8775C1.56006 8.32963 3.54792 10.3175 6.00006 10.3175Z"
stroke="var(--text-color)"
stroke-width="0.72"
strokeWidth="0.72"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export function AI_Icon() {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.90908 14.9119C7.70892 12.2881 5.62382 10.203 3 10.0028C5.62382 9.80267 7.70892 7.71753 7.90908 5.09375C8.10925 7.71753 10.1944 9.80267 12.8182 10.0028C10.1944 10.203 8.10925 12.2881 7.90908 14.9119Z"
fill="var(--primary-color)"
/>
<path
d="M14.0454 18.5888C13.9383 17.809 13.4611 16.3226 11.5908 16.1342C13.4611 15.9458 13.9383 14.4595 14.0454 13.6797C14.2337 15.5499 15.7201 16.0272 16.4999 16.1342C15.7201 16.2412 14.2337 16.7185 14.0454 18.5888Z"
fill="var(--primary-color)"
/>
<path
d="M14.0454 1.40625C14.2337 3.27654 15.7201 3.7538 16.4999 3.86079C15.7201 3.9678 14.2337 4.44505 14.0454 6.31533C13.9383 5.53553 13.4611 4.04915 11.5908 3.86079C13.4611 3.67243 13.9383 2.18606 14.0454 1.40625Z"
fill="var(--primary-color)"
/>
</svg>
);
}

View File

@ -136,7 +136,7 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
d="M12.6101 7.17339H6.72171V10.7064H12.6101V7.17339Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -144,7 +144,7 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
d="M18.4986 7.17339H12.6102V10.7064H18.4986V7.17339Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -152,7 +152,7 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
d="M6.72172 7.17339H0.833313V10.7064H6.72172V7.17339Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -160,7 +160,7 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
d="M15.5544 10.7064H9.66595V14.2395H15.5544V10.7064Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -168,7 +168,7 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
d="M9.66594 10.7064H3.77753V14.2395H9.66594V10.7064Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -176,7 +176,7 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
d="M15.5544 3.64035H9.66595V7.1734H15.5544V3.64035Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -184,7 +184,7 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
d="M9.66594 3.64035H3.77753V7.1734H9.66594V3.64035Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -213,49 +213,49 @@ export function WallIcon({ isActive }: { isActive: boolean }) {
<path
d="M12.6101 7.17339H6.72173V10.7064H12.6101V7.17339Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M18.4986 7.17339H12.6102V10.7064H18.4986V7.17339Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6.72174 7.17339H0.833328V10.7064H6.72174V7.17339Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.5544 10.7064H9.66596V14.2395H15.5544V10.7064Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.66594 10.7064H3.77753V14.2395H9.66594V10.7064Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.5544 3.64035H9.66596V7.1734H15.5544V3.64035Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.66594 3.64035H3.77753V7.1734H9.66594V3.64035Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
@ -380,7 +380,7 @@ export function PillerIcon({ isActive }: { isActive: boolean }) {
d="M14.7545 1.94309H5.22935C4.9007 1.94074 4.57782 2.04074 4.29614 2.23213C4.01447 2.42352 3.78489 2.69889 3.63259 3.02804C3.4803 3.35718 3.41117 3.72738 3.4328 4.098C3.45444 4.46862 3.56599 4.82535 3.75522 5.12904C3.94445 5.43272 4.20405 5.67163 4.50553 5.81955C4.807 5.96747 5.13871 6.01868 5.46425 5.96756C5.78978 5.91644 6.09657 5.76497 6.35094 5.52976C6.60531 5.29456 6.79744 4.98471 6.90624 4.63423H13.1088C13.2185 4.98209 13.4107 5.2892 13.6644 5.522C13.918 5.75479 14.2234 5.90432 14.5472 5.95425C14.871 6.00417 15.2007 5.95258 15.5003 5.80509C15.7999 5.65761 16.0578 5.41991 16.246 5.11797C16.4341 4.81603 16.5452 4.46146 16.5671 4.093C16.589 3.72454 16.5209 3.35636 16.3702 3.02869C16.2195 2.70102 15.992 2.42646 15.7125 2.235C15.4331 2.04355 15.1125 1.94257 14.7857 1.94309H14.7545Z"
fill="var(--highlight-accent-color)"
stroke="var(--accent-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
/>
</svg>
) : (
@ -394,17 +394,17 @@ export function PillerIcon({ isActive }: { isActive: boolean }) {
<path
d="M14.7545 1.9431H5.22935C4.9007 1.94074 4.57782 2.04075 4.29614 2.23214C4.01447 2.42352 3.78489 2.6989 3.63259 3.02804C3.4803 3.35719 3.41117 3.72738 3.4328 4.098C3.45444 4.46863 3.56599 4.82535 3.75522 5.12904C3.94445 5.43273 4.20405 5.67164 4.50553 5.81955C4.807 5.96747 5.13871 6.01868 5.46425 5.96756C5.78978 5.91644 6.09657 5.76497 6.35094 5.52977C6.60531 5.29456 6.79744 4.98472 6.90624 4.63423H13.1088C13.2185 4.9821 13.4107 5.2892 13.6644 5.522C13.918 5.7548 14.2234 5.90433 14.5472 5.95425C14.871 6.00418 15.2007 5.95258 15.5003 5.8051C15.7999 5.65761 16.0578 5.41992 16.246 5.11798C16.4341 4.81604 16.5452 4.46147 16.5671 4.093C16.589 3.72454 16.5209 3.35636 16.3702 3.02869C16.2195 2.70102 15.992 2.42646 15.7125 2.23501C15.4331 2.04355 15.1125 1.94257 14.7857 1.9431H14.7545Z"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
/>
<path
d="M14.3445 18.7347V5.86805"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
/>
<path
d="M5.65552 18.7347V5.86805"
stroke="var(--text-color)"
stroke-miterlimit="10"
strokeMiterlimit="10"
/>
</svg>
);

View File

@ -114,3 +114,102 @@ export function LogoIcon() {
</svg>
);
}
export function LogoIconLarge() {
return (
<svg
width="90"
height="90"
viewBox="0 0 90 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="45" cy="45" r="45" fill="#FCFDFD" />
<circle
cx="45.1957"
cy="45.1957"
r="31.2826"
stroke="var(--accent-color)"
strokeWidth="2"
/>
<path
d="M40.7635 42.6069L40.7992 42.8543L41.0466 42.8187C55.1005 40.7931 62.8733 32.7466 65.5682 29.3739L65.7243 29.1786L65.5289 29.0225L65.0855 28.6684L64.8902 28.5124L64.7342 28.7077C62.1284 31.9696 54.5784 39.79 40.8943 41.7624L40.6468 41.798L40.6825 42.0455L40.7635 42.6069Z"
fill="var(--accent-color)"
stroke="var(--accent-color)"
strokeWidth="0.5"
/>
<path
d="M41.0707 44.3747C42.2491 44.3747 43.2043 43.4194 43.2043 42.2411C43.2043 41.0627 42.2491 40.1074 41.0707 40.1074C39.8923 40.1074 38.9371 41.0627 38.9371 42.2411C38.9371 43.4194 39.8923 44.3747 41.0707 44.3747Z"
fill="var(--accent-color)"
/>
<path
d="M65.071 31.2502C66.2494 31.2502 67.2046 30.2949 67.2046 29.1165C67.2046 27.9382 66.2494 26.9829 65.071 26.9829C63.8926 26.9829 62.9374 27.9382 62.9374 29.1165C62.9374 30.2949 63.8926 31.2502 65.071 31.2502Z"
fill="var(--accent-color)"
/>
<path
d="M32.6135 68.7896L32.7693 68.9857L32.9651 68.8296L33.4085 68.4763L33.6037 68.3207L33.4485 68.1253C30.8927 64.9067 26.8518 58.2123 28.3971 49.4438L28.4404 49.1979L28.1946 49.1542L27.6362 49.0551L27.3898 49.0114L27.3463 49.2579C25.7268 58.4481 29.957 65.4445 32.6135 68.7896Z"
fill="var(--accent-color)"
stroke="var(--accent-color)"
strokeWidth="0.5"
/>
<path
d="M27.8694 51.592C29.0478 51.592 30.003 50.6367 30.003 49.4583C30.003 48.28 29.0478 47.3247 27.8694 47.3247C26.691 47.3247 25.7358 48.28 25.7358 49.4583C25.7358 50.6367 26.691 51.592 27.8694 51.592Z"
fill="var(--accent-color)"
/>
<path
d="M32.9652 70.5041C34.1435 70.5041 35.0988 69.5488 35.0988 68.3704C35.0988 67.1921 34.1435 66.2368 32.9652 66.2368C31.7868 66.2368 30.8315 67.1921 30.8315 68.3704C30.8315 69.5488 31.7868 70.5041 32.9652 70.5041Z"
fill="var(--accent-color)"
/>
<path
d="M66.6067 51.6991L66.8567 51.7061L66.8636 51.4561C67.0676 44.1377 64.271 37.1369 58.994 31.7455C53.5053 26.1369 45.8325 22.9707 37.957 23.0012L37.7063 23.0022L37.708 23.2529L37.7118 23.8203L37.7135 24.0701L37.9632 24.0686C45.5642 24.0244 52.9448 27.0906 58.2312 32.4925L58.2312 32.4925C63.3084 37.6792 65.9926 44.4033 65.7964 51.4263L65.7894 51.6762L66.0393 51.6832L66.6067 51.6991Z"
fill="var(--accent-color)"
stroke="var(--accent-color)"
strokeWidth="0.5"
/>
<path
d="M66.2922 53.467C67.4705 53.467 68.4258 52.5117 68.4258 51.3333C68.4258 50.155 67.4705 49.1997 66.2922 49.1997C65.1138 49.1997 64.1585 50.155 64.1585 51.3333C64.1585 52.5117 65.1138 53.467 66.2922 53.467Z"
fill="var(--accent-color)"
/>
<path
d="M38.0639 25.7077C39.2423 25.7077 40.1976 24.7524 40.1976 23.5741C40.1976 22.3957 39.2423 21.4404 38.0639 21.4404C36.8856 21.4404 35.9303 22.3957 35.9303 23.5741C35.9303 24.7524 36.8856 25.7077 38.0639 25.7077Z"
fill="var(--accent-color)"
/>
<path
d="M13.468 25.6899C12.0099 27.8846 11.446 30.8409 11.7162 34.1762C11.9865 37.5136 13.093 41.2489 14.9988 45.024L15.222 44.9113L14.9988 45.024C20.182 55.2899 31.8559 66.569 49.0829 67.4189L49.3326 67.4312L49.3449 67.1815L49.3729 66.6141L49.3853 66.3644L49.1356 66.352C32.3736 65.5252 21.002 54.548 15.9513 44.5429L15.7295 44.6549L15.9513 44.5429C14.1144 40.9044 13.0501 37.3314 12.7802 34.1684C12.5101 31.0029 13.0366 28.2674 14.3566 26.2805C15.1989 25.0129 16.086 24.0714 17.2249 23.4752C18.3626 22.8796 19.7779 22.6151 21.6999 22.7494L21.8136 22.7574L21.8942 22.6768L22.4811 22.0899L22.8692 21.7018L22.3217 21.6637C20.2328 21.5184 18.4886 21.8123 17.0275 22.5047C15.5655 23.1975 14.4036 24.2815 13.468 25.6899ZM13.468 25.6899L13.6762 25.8282L13.468 25.6899Z"
fill="var(--accent-color)"
stroke="var(--accent-color)"
strokeWidth="0.5"
/>
<path
d="M25.003 71.2683L25.8544 71.4079L25.2096 70.8347L23.4487 69.2695L23.414 69.2386L23.3704 69.2223C22.7326 68.9831 22.3525 68.8627 22.0397 68.7635C21.9653 68.7399 21.8947 68.7175 21.8253 68.6951C21.4708 68.5802 21.1376 68.4592 20.4524 68.1438C14.7739 65.5298 12.4176 60.3578 12.1857 55.7717L12.1857 55.7717C11.8358 48.8628 16.101 40.6225 26.4705 37.8248L26.7118 37.7597L26.6468 37.5184L26.4993 36.9706L26.4342 36.7291L26.1927 36.7943C15.2798 39.7386 10.7471 48.4657 11.1189 55.8257L11.1189 55.8257C11.5061 63.4757 16.9748 69.952 25.003 71.2683Z"
fill="var(--accent-color)"
stroke="var(--accent-color)"
strokeWidth="0.5"
/>
<path
d="M26.2306 39.4767C27.409 39.4767 28.3642 38.5215 28.3642 37.3431C28.3642 36.1647 27.409 35.2095 26.2306 35.2095C25.0522 35.2095 24.097 36.1647 24.097 37.3431C24.097 38.5215 25.0522 39.4767 26.2306 39.4767Z"
fill="var(--accent-color)"
/>
<path
d="M61.2074 79.1064L61.2074 79.1064C61.4678 79.1305 61.7244 79.1422 61.978 79.1422C64.4196 79.1422 66.5696 78.04 67.6795 76.1331L67.6795 76.133C68.2962 75.0725 68.6727 74.0249 68.6539 72.9029C68.6351 71.7796 68.2207 70.6132 67.3137 69.3137L67.1429 69.069L66.9319 69.28L66.345 69.8669L66.1969 70.015L66.3168 70.1868C67.1247 71.3442 67.4824 72.2492 67.5231 73.0742C67.5638 73.8967 67.2914 74.6784 66.7571 75.5963L66.757 75.5963C65.7608 77.3086 63.6885 78.2699 61.3064 78.0434L61.3058 78.0433C56.8839 77.6324 52.7763 73.4613 50.384 66.736L50.3002 66.5003L50.0645 66.5843L49.5296 66.775L49.2943 66.8589L49.378 67.0943C51.8719 74.1037 56.2694 78.6473 61.2074 79.1064Z"
fill="var(--accent-color)"
stroke="var(--accent-color)"
strokeWidth="0.5"
/>
<path
d="M49.0029 68.9821C50.1813 68.9821 51.1366 68.0268 51.1366 66.8485C51.1366 65.6701 50.1813 64.7148 49.0029 64.7148C47.8246 64.7148 46.8693 65.6701 46.8693 66.8485C46.8693 68.0268 47.8246 68.9821 49.0029 68.9821Z"
fill="var(--accent-color)"
/>
<path
d="M55.0569 21.9844L54.8197 21.9066L54.2803 21.7295L54.0424 21.6515L54.1208 21.4137C55.4226 17.4661 57.749 14.8415 60.5635 14.3374C62.2955 14.0248 64.0152 14.6136 65.0364 15.8938M55.0569 21.9844L64.203 16.5598C64.2457 16.6134 64.2872 16.6652 64.3275 16.7154C64.6827 17.1587 64.939 17.4786 65.0681 17.8299C65.2013 18.1927 65.2025 18.6117 64.9221 19.2718L64.8561 19.427L64.9754 19.5463L65.5624 20.1332L65.8243 20.3952L65.9692 20.0542C66.3303 19.2044 66.3477 18.464 66.1387 17.7763C65.9332 17.1 65.5135 16.4918 65.0364 15.8938M55.0569 21.9844L55.1351 21.7473M55.0569 21.9844L55.1351 21.7473M65.0364 15.8938L64.841 16.0497M65.0364 15.8938L64.841 16.0497M64.841 16.0497C65.7364 17.172 66.3488 18.2377 65.8379 19.7016M64.841 16.0497C63.8831 14.849 62.2595 14.2853 60.6078 14.5834L65.8379 19.7016M65.8379 19.7016L65.9159 19.7797L65.7391 19.9565L65.5091 19.8587C65.5224 19.8272 65.5353 19.7959 65.5476 19.7649M65.8379 19.7016L65.5476 19.7649M65.5476 19.7649L65.1522 19.3695C65.715 18.0447 65.2644 17.4833 64.5263 16.5635L65.5476 19.7649ZM55.1351 21.7473C56.3456 18.077 58.4148 15.8065 60.7524 15.3887L60.7525 15.3886M55.1351 21.7473L60.7525 15.3886M60.7525 15.3886C62.1223 15.1432 63.4396 15.6032 64.2029 16.5597L60.7525 15.3886Z"
fill="var(--accent-color)"
stroke="var(--accent-color)"
strokeWidth="0.5"
/>
<path
d="M54.673 23.6159C55.8513 23.6159 56.8066 22.6606 56.8066 21.4823C56.8066 20.3039 55.8513 19.3486 54.673 19.3486C53.4946 19.3486 52.5393 20.3039 52.5393 21.4823C52.5393 22.6606 53.4946 23.6159 54.673 23.6159Z"
fill="var(--accent-color)"
/>
</svg>
);
}

View File

@ -9,6 +9,9 @@ import {
import useToggleStore from "../../../store/useUIToggleStore";
import MachineMechanics from "./mechanics/MachineMechanics";
import Visualization from "./visualization/Visualization";
import GlobalProperties from "./properties/GlobalProperties";
import AsstePropertiies from "./properties/AssetProperties";
import Analysis from "./analysis/Analysis";
const SideBarRight: React.FC = () => {
const { activeModule } = useModuleStore();
@ -56,12 +59,36 @@ const SideBarRight: React.FC = () => {
</div>
)}
{/* process builder */}
{toggleUI && activeModule === "simulation" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<MachineMechanics />
{toggleUI &&
activeList === "properties" &&
activeModule !== "visualization" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<GlobalProperties />
{/* <AsstePropertiies /> */}
</div>
</div>
</div>
)}
{/* simulation */}
{toggleUI && activeModule === "simulation" && (
<>
{activeList === "mechanics" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<MachineMechanics />
</div>
</div>
)}
{activeList === "analysis" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<Analysis />
</div>
</div>
)}
</>
)}
{/* realtime visualization */}

View File

@ -0,0 +1,143 @@
import React, { useState } from "react";
import { AI_Icon } from "../../../icons/ExportCommonIcons";
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
import { AnalysisPresetsType } from "../../../../types/analysis";
import RenderAnalysisInputs from "./RenderAnalysisInputs";
const Analysis: React.FC = () => {
const [selectedOption, setSelectedOption] = useState("Throughput time");
const handleSelect = (option: string) => {
setSelectedOption(option); // Normalize for key matching
};
const AnalysisPresets: AnalysisPresetsType = {
"Throughput time": [
{ type: "default", inputs: { label: "Height", activeOption: "mm" } },
{ type: "default", inputs: { label: "Width", activeOption: "mm" } },
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } },
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } },
{
type: "default",
inputs: { label: "Raw Material", activeOption: "mm" },
},
{
type: "range",
inputs: { label: "Material flow", activeOption: "m/min" },
},
],
"Production capacity": [
{ type: "default", inputs: { label: "Height", activeOption: "mm" } },
{ type: "default", inputs: { label: "Width", activeOption: "mm" } },
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } },
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } },
{
type: "default",
inputs: { label: "Raw Material", activeOption: "mm" },
},
{
type: "range",
inputs: { label: "Material flow", activeOption: "m/min" },
},
{
type: "range",
inputs: { label: "Shift 1", activeOption: "hr(s)" },
},
{
type: "range",
inputs: { label: "Shift 2", activeOption: "hr(s)" },
},
{
type: "range",
inputs: { label: "Over time", activeOption: "hr(s)" },
},
],
ROI: [
{
type: "default",
inputs: { label: "Equipment Cost", activeOption: "INR" },
},
{
type: "default",
inputs: { label: "Employee Salary", activeOption: "INR" },
},
{
type: "default",
inputs: { label: "Labor Cost", activeOption: "INR" },
},
{
type: "default",
inputs: { label: "Cost per unit", activeOption: "INR" },
},
{
type: "default",
inputs: { label: "Electricity cost", activeOption: "INR" },
},
{
type: "default",
inputs: { label: "Upkeep Cost", activeOption: "INR" },
},
{
type: "default",
inputs: { label: "Working Hours", activeOption: "Hrs" },
},
{
type: "default",
inputs: { label: "Power Usage", activeOption: "KWH" },
},
{ type: "default", inputs: { label: "KWH", activeOption: "Mos" } },
{
type: "default",
inputs: { label: "Man Power", activeOption: "Person" },
},
],
};
return (
<div className="analysis-main-wrapper">
<div className="analysis-main-container">
<div className="header">Object</div>
<div className="generate-report-button">
<AI_Icon /> Generate Report
</div>
<div className="analysis-content-container">
<div className="dropdown-header-container">
<div className="value">Create Analysis</div>
</div>
<div className="dropdown-content-container">
<RegularDropDown
header={selectedOption}
options={["Throughput time", "Production capacity", "ROI"]}
onSelect={handleSelect}
search={false}
/>
</div>
{/* Render only the selected option */}
<RenderAnalysisInputs
keyName={selectedOption}
presets={
AnalysisPresets[selectedOption as keyof AnalysisPresetsType]
}
/>
<div className="buttons-container">
<input type="button" value={"Clear"} className="cancel" />
<input type="button" value={"Calculate"} className="submit" />
</div>
<div className="create-custom-analysis-container">
<div className="custom-analysis-header">Create Custom Analysis</div>
<div className="content">
Click <span>'Create'</span> to enhances decision-making by
providing actionable insights, optimizing operations that adapts
to the unique challenges.
</div>
<div className="input">
<input type="button" value={"Calculate"} className="submit" />
</div>
</div>
</div>
</div>
</div>
);
};
export default Analysis;

View File

@ -0,0 +1,38 @@
import React from "react";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import InputRange from "../../../ui/inputs/InputRange";
import { AnalysisPresetsType } from "../../../../types/analysis";
interface InputRendererProps {
keyName: string;
presets: AnalysisPresetsType[keyof AnalysisPresetsType];
}
const RenderAnalysisInputs: React.FC<InputRendererProps> = ({
keyName,
presets,
}) => {
return (
<div key={`main-${keyName}`} className="analysis-inputs">
{presets.map((preset, index) => {
if (preset.type === "default") {
return (
<InputWithDropDown
key={index}
label={preset.inputs.label}
value=""
activeOption={preset.inputs.activeOption}
onChange={() => {}}
/>
);
}
if (preset.type === "range") {
return <InputRange key={index} label={preset.inputs.label} />;
}
return null;
})}
</div>
);
};
export default RenderAnalysisInputs;

View File

@ -0,0 +1,41 @@
import React from "react";
interface PositionInputProps {
onChange: (value: string) => void; // Callback for value change
placeholder?: string; // Optional placeholder
type?: string; // Input type (e.g., text, number, email)
}
const PositionInput: React.FC<PositionInputProps> = ({
onChange,
placeholder = "Enter value", // Default placeholder
type = "number", // Default type
}) => {
return (
<div className="custom-input-container">
<div className="header">Position</div>
<div className="inputs-container">
<div className="input-container">
<div className="custom-input-label">X : </div>
<input
className="custom-input-field"
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
/>
</div>
<div className="input-container">
<div className="custom-input-label">Y : </div>
<input
className="custom-input-field"
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
/>
</div>
</div>
</div>
);
};
export default PositionInput;

View File

@ -0,0 +1,32 @@
import React from "react";
interface RotationInputProps {
onChange: (value: string) => void; // Callback for value change
placeholder?: string; // Optional placeholder
type?: string; // Input type (e.g., text, number, email)
}
const RotationInput: React.FC<RotationInputProps> = ({
onChange,
placeholder = "Enter value", // Default placeholder
type = "number", // Default type
}) => {
return (
<div className="custom-input-container">
<div className="header">Rotation</div>
<div className="inputs-container" style={{display: "block"}}>
<div className="input-container">
<div className="custom-input-label">Rotate : </div>
<input
className="custom-input-field"
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
/>
</div>
</div>
</div>
);
};
export default RotationInput;

View File

@ -10,6 +10,7 @@ import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import LabledDropdown from "../../../ui/inputs/LabledDropdown";
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
import { handleResize } from "../../../../functions/handleResizePannel";
import EyeDropInput from "../../../ui/inputs/EyeDropInput";
const MachineMechanics: React.FC = () => {
const [actionList, setActionList] = useState<string[]>([]);
@ -54,16 +55,30 @@ const MachineMechanics: React.FC = () => {
setSelectedItem({ type, name });
};
const [processes, setProcesses] = useState<string[]>([]);
const [activeProcess, setActiveProcesses] = useState<string>();
const handleSelect = (option: string) => {
setActiveProcesses(option); // Update the active option state
};
const handleAddProcess = () => {
const newProcess = `Process ${processes.length + 1}`; // Generate new process name dynamically
setProcesses((prevProcesses) => [...prevProcesses, newProcess]); // Update the state with the new process
};
return (
<div className="machine-mechanics-container">
<div className="machine-mechanics-header">Selected Object</div>
<div className="process-list-container">
<div className="label">Process:</div>
<RegularDropDown
header={"activeOption"}
options={["options"]}
onSelect={() => {}}
header={activeProcess || "add process ->"}
options={processes}
onSelect={handleSelect}
/>
<div className="add-new-process" onClick={handleAddProcess}>
<AddIcon />
</div>
</div>
<div className="machine-mechanics-content-container">
<div className="actions">
@ -168,7 +183,13 @@ const MachineMechanics: React.FC = () => {
defaultOption="On-hit"
options={["On-hit", "Buffer"]}
/>
<InputWithDropDown label="Speed" value="" activeOption=".mm" />
<InputWithDropDown
label="Speed"
value=""
activeOption=".mm"
onChange={() => {}}
/>
<EyeDropInput />
</>
)}
</div>

View File

@ -0,0 +1,88 @@
import React, { useState } from "react";
import InputToggle from "../../../ui/inputs/InputToggle";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { RemoveIcon } from "../../../icons/ExportCommonIcons";
import PositionInput from "../customInput/PositionInputs";
import RotationInput from "../customInput/RotationInput";
interface UserData {
id: number; // Unique identifier for the user data
label: string; // Label of the user data field
value: string; // Value of the user data field
}
const AssetProperties: React.FC = () => {
const [userData, setUserData] = useState<UserData[]>([]); // State to track user data
const [nextId, setNextId] = useState(1); // Unique ID for new entries
// Function to handle adding new user data
const handleAddUserData = () => {
const newUserData: UserData = {
id: nextId,
label: `Property ${nextId}`,
value: "",
};
setUserData([...userData, newUserData]);
setNextId(nextId + 1); // Increment the ID for the next entry
};
// Function to update the value of a user data entry
const handleUserDataChange = (id: number, newValue: string) => {
setUserData((prevUserData) =>
prevUserData.map((data) =>
data.id === id ? { ...data, value: newValue } : data
)
);
};
// Remove user data
const handleRemoveUserData = (id: number) => {
setUserData((prevUserData) =>
prevUserData.filter((data) => data.id !== id)
);
};
return (
<div className="asset-properties-container">
<div className="header">Selected Object</div>
<div className="split"></div>
<PositionInput onChange={() => {}} />
<RotationInput onChange={() => {}} />
<div className="split"></div>
<InputToggle inputKey="visible" label="Visible" />
<InputToggle inputKey="frustumCull" label="Frustum cull" />
<div className="header">User Data</div>
{/* Map through userData and render InputWithDropDown for each entry */}
{userData.map((data) => (
<div className="input-container">
<InputWithDropDown
key={data.id}
label={data.label}
value={data.value}
editableLabel
onChange={(newValue) => handleUserDataChange(data.id, newValue)} // Pass the change handler
/>
<div
className="remove-button"
onClick={() => handleRemoveUserData(data.id)}
>
<RemoveIcon />
</div>
</div>
))}
{/* Add new user data */}
<div className="optimize-button" onClick={handleAddUserData}>
+ Add
</div>
</div>
);
};
export default AssetProperties;

View File

@ -0,0 +1,81 @@
import React, { useState } from "react";
import InputRange from "../../../ui/inputs/InputRange";
import InputToggle from "../../../ui/inputs/InputToggle";
import { AI_Icon } from "../../../icons/ExportCommonIcons";
import LabeledButton from "../../../ui/inputs/LabledButton";
const GlobalProperties: React.FC = () => {
const [limitDistance, setLimitDistance] = useState(false);
const [distance, setDistance] = useState<number>(5);
const [limitGridDistance, setLimitGridDistance] = useState(false);
const [gridDistance, setGridDistance] = useState<number>(5);
function optimizeScene() {
setLimitDistance(true);
setDistance(5);
}
function updateDistance(value: number) {
setDistance(value);
}
function updateGridDistance(value: number) {
setGridDistance(value);
}
return (
<div className="global-properties-container">
<div className="header">Environment</div>
<div className="optimize-button" onClick={optimizeScene}>
<AI_Icon />
Optimize
</div>
<div className="split"></div>
<InputToggle inputKey="1" label="Roof Visibility" />
<InputToggle inputKey="2" label="Wall Visibility" />
<InputToggle inputKey="3" label="Shadows Visibility" />
<LabeledButton label="Reset Camera" onClick={() => {}} value="Reset"/>
<div className="split"></div>
<InputToggle
inputKey="4"
label="Limit Render Distance"
value={limitDistance}
onClick={() => {
setLimitDistance(!limitDistance);
}}
/>
<InputRange
label="Distance"
disabled={!limitDistance}
value={distance}
key={"5"}
onChange={(value: number) => updateDistance(value)}
/>
<div className="split"></div>
<InputToggle
inputKey="6"
label="Display Grid"
value={limitGridDistance}
onClick={() => {
setLimitGridDistance(!limitGridDistance);
}}
/>
<InputRange
label="Tile Distance"
disabled={!limitGridDistance}
value={gridDistance}
key={"7"}
onChange={(value: number) => updateGridDistance(value)}
/>
</div>
);
};
export default GlobalProperties;

View File

@ -1,9 +0,0 @@
import React from "react";
const GlobalProperties: React.FC = () => {
return (
<div>GlobalProperties</div>
);
};
export default GlobalProperties;

View File

@ -0,0 +1,128 @@
import React, { useState } from "react";
import RenderOverlay from "./Overlay";
import { ArrowIcon, CloseIcon } from "../icons/ExportCommonIcons";
import { AccessOption, User } from "../../types/users";
import RegularDropDown from "../ui/inputs/RegularDropDown";
import { access } from "fs";
import MultiEmailInvite from "../ui/inputs/MultiEmailInvite";
interface UserListTemplateProps {
user: User;
}
const UserListTemplate: React.FC<UserListTemplateProps> = ({ user }) => {
const [accessSelection, setAccessSelection] = useState<string>(user.access);
function accessUpdate({ option }: AccessOption) {
setAccessSelection(option);
}
return (
<div className="user-list-container">
<div className="user-details">
<div className="profile-image">
{user.profileImage ? (
<img
src={user.profileImage || "https://via.placeholder.com/150"}
alt={`${user.name}'s profile`}
/>
) : (
<div
className="no-profile-container"
style={{ background: user.color }}
>
{user.name[0]}
</div>
)}
</div>
<div className="user-name">{user.name}</div>
</div>
<div className="user-access">
<RegularDropDown
header={accessSelection}
options={["Admin", "User", "Viewer"]}
onSelect={(option) => accessUpdate({ option })}
search={false}
/>
{/* <ArrowIcon /> */}
</div>
</div>
);
};
const CollaborationPopup: React.FC = () => {
const users = [
{
name: "Alice Johnson",
email: "alice.johnson@example.com",
profileImage: "",
color: "#FF6600",
access: "Admin",
},
{
name: "Bob Smith",
email: "bob.smith@example.com",
profileImage: "",
color: "#488EF6",
access: "Viewer",
},
{
name: "Charlie Brown",
email: "charlie.brown@example.com",
profileImage: "",
color: "#48AC2A",
access: "Viewer",
},
{
name: "Diana Prince",
email: "diana.prince@example.com",
profileImage: "",
color: "#D44242",
access: "Viewer",
},
];
return (
<RenderOverlay>
<div className="collaboration-popup-wrapper">
<div className="collaboration-popup-container">
<div className="header">
<div className="content">Share this file</div>
<div className="content">
<div className="copy-link-button">copy link</div>
<div className="close-button">
<CloseIcon />
</div>
</div>
</div>
<div className="invite-input-container">
<MultiEmailInvite />
</div>
<div className="split"></div>
<div className="access-and-user-control-container">
<div className="user-header">Who has access</div>
<div className="general-access-container">
<div className="team-container">
<div className="project-name">Untitled</div>
<div className="number-of-peoples-have-access">
{users.length} persons
</div>
</div>
</div>
<div className="users-list-container">
<div className="you-container">
<div className="your-name">User 1</div>
<div className="indicater">you</div>
</div>
{users.map((user, index) => (
<UserListTemplate key={index} user={user} />
))}
</div>
</div>
</div>
</div>
</RenderOverlay>
);
};
export default CollaborationPopup;

View File

@ -0,0 +1,39 @@
import React from "react";
import RenderOverlay from "./Overlay";
import { LogoIconLarge } from "../icons/Logo";
interface LoadingPageProps {
progress: number; // Expect progress as a percentage (0-100)
}
const LoadingPage: React.FC<LoadingPageProps> = ({ progress }) => {
// Ensure progress stays within 0-100 range
const validatedProgress = Math.min(100, Math.max(0, progress));
return (
<RenderOverlay>
<div className="loading-wrapper">
<div className="loading-container">
<div className="project-name">Untitled</div>
<div className="loading-hero-container">
<div className="logo">
<LogoIconLarge />
</div>
<div className="content">Entering A New World of Dwinzo</div>
</div>
<div className="progress-container">
<div className="progress-value">{validatedProgress}%</div>
<div className="progress-indicator-container">
<div
className="progress-bar"
style={{ width: `${validatedProgress}%` }} // Dynamic width
></div>
</div>
</div>
</div>
</div>
</RenderOverlay>
);
};
export default LoadingPage;

View File

@ -0,0 +1,74 @@
import React, { createContext, useContext, useState, ReactNode } from "react";
type Toast = {
id: string;
message: string;
type: "success" | "error" | "info" | "warning";
position?: ToastPosition; // Optional position for each toast
};
type ToastPosition =
| "top-left"
| "top-center"
| "top-right"
| "bottom-left"
| "bottom-center"
| "bottom-right";
type ToastContextType = {
addToast: (
message: string,
type: Toast["type"],
position?: ToastPosition
) => void;
removeToast: (id: string) => void;
};
const ToastContext = createContext<ToastContextType | undefined>(undefined);
const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [toasts, setToasts] = useState<Toast[]>([]);
const addToast = (
message: string,
type: Toast["type"],
position: ToastPosition = "bottom-center" // Default position
) => {
const id = Math.random().toString(36).substr(2, 9); // Generate a unique ID
setToasts((prev) => [...prev, { id, message, type, position }]);
// Auto-remove toast after 3 seconds
setTimeout(() => removeToast(id), 3000);
};
const removeToast = (id: string) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
};
return (
<ToastContext.Provider value={{ addToast, removeToast }}>
{children}
<div className={`toast-container ${"bottom-center"}`}>
{toasts.map((toast) => (
<div
key={toast.id}
className={`toast ${toast.type}`}
onClick={() => removeToast(toast.id)} // Allow manual dismissal
>
{toast.message}
</div>
))}
</div>
</ToastContext.Provider>
);
};
export const useToast = (): ToastContextType => {
const context = useContext(ToastContext);
if (!context) {
throw new Error("useToast must be used within a ToastProvider");
}
return context;
};
export default ToastProvider;

View File

@ -0,0 +1,89 @@
import { useEffect, useState } from "react"
import { getLines } from "../../services/factoryBuilder/lines/getLinesApi";
import * as THREE from "three";
import { useActiveLayer, useDeletedLines, useNewLines, useToggleView } from "../../store/store";
import objectLinesToArray from "../scene/geomentries/lines/lineConvertions/objectLinesToArray";
import { Html } from "@react-three/drei";
const DistanceText = () => {
const [lines, setLines] = useState([]);
const { activeLayer } = useActiveLayer();
const { toggleView } = useToggleView();
const { newLines, setNewLines } = useNewLines();
const { deletedLines, setDeletedLines } = useDeletedLines();
useEffect(() => {
const email = localStorage.getItem('email')
const organization = (email.split("@")[1]).split(".")[0];
getLines(organization).then((data) => {
data = objectLinesToArray(data);
const lines = data.filter(line => line[0][2] === activeLayer)
.map(line => {
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
const distance = point1.distanceTo(point2);
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
return {
distance: distance.toFixed(1),
position: midpoint,
userData: line,
layer: activeLayer,
};
});
setLines(lines)
})
}, [activeLayer])
useEffect(() => {
if (newLines.length > 0) {
if (newLines[0][0][2] !== activeLayer ) return;
const newLinesData = newLines.map((line) => {
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
const distance = point1.distanceTo(point2);
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
return {
distance: distance.toFixed(1),
position: midpoint,
userData: line,
layer: activeLayer,
};
});
setLines((prevLines) => [...prevLines, ...newLinesData]);
setNewLines([]);
}
}, [newLines, activeLayer]);
useEffect(() => {
if (deletedLines.length > 0) {
setLines((prevLines) =>
prevLines.filter(
(line) => !deletedLines.some((deletedLine) => deletedLine[0][1] === line.userData[0][1] && deletedLine[1][1] === line.userData[1][1])
)
);
setDeletedLines([]);
}
}, [deletedLines]);
return (
<>
{toggleView && (
<group name='Distance_Text'>
{lines.map((text) => (
<Html key={`${text.userData[0][1]}_${text.userData[1][1]}`} transform sprite userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }} >
<div key={`${text.userData[0][1]}_${text.userData[1][1]}`} className={`Distance line-${text.userData[0][1]}_${text.userData[1][1]}_${text.layer}`} >{text.distance} m</div>
</Html>
))}
</group>
)}
</>
)
}
export default DistanceText;

View File

@ -0,0 +1,42 @@
import * as THREE from 'three';
import * as Types from "../../scene/world/worldTypes";
function updateDistanceText(
scene: THREE.Scene,
floorPlanGroupLine: Types.RefGroup,
affectedLines: Types.NumberArray
): void {
////////// Updating the Distance Texts of the lines that are affected during drag //////////
const DistanceGroup = scene.children.find((child) => child.name === "Distance_Text") as THREE.Group;
affectedLines.forEach((lineIndex) => {
const mesh = floorPlanGroupLine.current.children[lineIndex] as THREE.Mesh;
const linePoints = mesh.userData.linePoints;
if (linePoints) {
const distance = linePoints[0][0].distanceTo(linePoints[1][0]).toFixed(1);
const position = new THREE.Vector3().addVectors(linePoints[0][0], linePoints[1][0]).divideScalar(2);
if (!DistanceGroup || !linePoints) {
return
}
DistanceGroup.children.forEach((text) => {
const textMesh = text as THREE.Mesh;
if (textMesh.userData[0][1] === linePoints[0][1] && textMesh.userData[1][1] === linePoints[1][1]) {
textMesh.position.set(position.x, 1, position.z);
const className = `Distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`;
const element = document.getElementsByClassName(className)[0] as HTMLElement;
if (element) {
element.innerHTML = `${distance} m`;
}
}
});
}
});
}
export default updateDistanceText;

View File

@ -0,0 +1,39 @@
import * as THREE from 'three';
import { Html } from '@react-three/drei';
import { useState, useEffect } from 'react';
import { useActiveLayer } from '../../store/store';
const ReferenceDistanceText = ({ line }) => {
const [text, setTexts] = useState(null);
const { activeLayer } = useActiveLayer();
useEffect(() => {
if (line === undefined || line.parent === null) {
setTexts(null);
return;
}
const distance = line.userData.linePoints.cursorPosition.distanceTo(line.userData.linePoints.startPoint);
const midpoint = new THREE.Vector3().addVectors(line.userData.linePoints.cursorPosition, line.userData.linePoints.startPoint).divideScalar(2);
const newTexts = {
distance: distance.toFixed(1),
position: midpoint,
userData: line,
layer: activeLayer
};
setTexts(newTexts);
});
return (
<group name='Reference_Distance_Text'>
<mesh>
{text !== null &&
< Html transform sprite key={text.length} userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }}>
<div className={`Reference_Distance line-${text.userData.userData}`}>{text.distance} m</div>
</Html>
}
</mesh>
</group >
);
};
export default ReferenceDistanceText;

View File

@ -0,0 +1,23 @@
import React from "react";
import RegularDropDown from "./RegularDropDown";
import { EyeDroperIcon } from "../../icons/ExportCommonIcons";
const EyeDropInput: React.FC = () => {
return (
<div className="eye-dropper-input-container">
<div className="label">Object</div>
<div className="input-container">
<RegularDropDown
header="select object"
options={[]}
onSelect={() => {}}
/>
<div className="eye-picker-button">
<EyeDroperIcon isActive={false} />
</div>
</div>
</div>
);
};
export default EyeDropInput;

View File

@ -0,0 +1,69 @@
import React, { useEffect, useState } from "react";
interface InputToggleProps {
label: string; // Represents the toggle state (on/off)
min?: number;
max?: number;
onClick?: () => void; // Function to handle toggle clicks
onChange?: (value: number) => void; // Function to handle toggle clicks
disabled?: boolean;
value?: number;
}
const InputRange: React.FC<InputToggleProps> = ({
label,
onClick,
onChange,
min = 0,
max = 10,
disabled,
value = 5,
}) => {
const [rangeValue, setRangeValue] = useState<number>(value);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const newValue = parseInt(e.target.value); // Parse the value to an integer
setRangeValue(newValue); // Update the local state
if (onChange) {
onChange(newValue); // Call the onChange function if it exists
}
}
useEffect(() => {
setRangeValue(value);
}, [value]);
return (
<div className="input-range-container">
<label
htmlFor={`range-input ${value}`}
className="label"
onClick={onClick}
>
{label}
</label>
<div className="input-container">
<input
id={`range-input ${value}`}
type="range"
min={min}
max={max}
onChange={handleChange}
disabled={disabled}
value={rangeValue}
/>
<input
type="number"
min={min}
className="input-value"
max={max}
value={rangeValue}
onChange={handleChange}
disabled={disabled}
/>
</div>
</div>
);
};
export default InputRange;

View File

@ -0,0 +1,51 @@
import React, { useEffect, useState } from "react";
interface InputToggleProps {
label: string; // Represents the toggle state (on/off)
onClick?: () => void; // Function to handle toggle clicks
value?: boolean;
inputKey: string;
}
const InputToggle: React.FC<InputToggleProps> = ({
label,
onClick,
value = false,
inputKey,
}) => {
const [activeValue, setActiveValue] = useState<boolean>(value);
function handleOnClick() {
setActiveValue(!activeValue);
if (onClick) onClick();
}
useEffect(() => {
setActiveValue(value);
}, [value]);
return (
<div className="input-toggle-container">
<label htmlFor={`toogle-input-${inputKey}`} className="label">
{label}
</label>
<div className={"check-box"} onClick={handleOnClick}>
<div
className="check-box-style"
style={{
left: activeValue ? "50%" : "2px",
background: activeValue ? "" : "var(--text-disabled)",
}}
></div>
<input
type="checkbox"
name=""
id={`toogle-input-${inputKey}`}
defaultChecked={value}
/>
</div>
</div>
);
};
export default InputToggle;

View File

@ -1,10 +1,14 @@
import React, { useState } from "react";
import RenameInput from "./RenameInput";
type InputWithDropDownProps = {
label: string;
value: string;
options?: string[]; // Array of dropdown options
activeOption?: string; // The currently active dropdown option
onClick?: () => void;
onChange: (newValue: string) => void;
editableLabel?: boolean;
};
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
@ -12,6 +16,9 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
value,
options,
activeOption,
onClick,
onChange,
editableLabel = false,
}) => {
const separatedWords = label
.split(/(?=[A-Z])/)
@ -22,29 +29,45 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
return (
<div className="value-field-container">
<label htmlFor={separatedWords} className="label">
{label}
</label>
{editableLabel ? (
<RenameInput value={label} />
) : (
<label htmlFor={separatedWords} className="label">
{label}
</label>
)}
<div className="input default" id={separatedWords}>
<input type="text" defaultValue={value} />
<div
className="dropdown"
onClick={() => {
setOpenDropdown(true);
<input
type="text"
defaultValue={value}
onChange={(e) => {
onChange(e.target.value);
}}
>
<div className="active-option">{activeOption}</div>
{options && openDropdown && (
<div className="dropdown-options-list">
{options.map((option, index) => (
<div key={index} className={"dropdown-option"}>
{option}
</div>
))}
</div>
)}
</div>
/>
{activeOption && (
<div
className="dropdown"
onClick={() => {
setOpenDropdown(true);
}}
>
<div className="active-option">{activeOption}</div>
{options && openDropdown && (
<div className="dropdown-options-list">
{options.map((option, index) => (
<div
key={index}
className={"dropdown-option"}
onClick={onClick}
>
{option}
</div>
))}
</div>
)}
</div>
)}
</div>
</div>
);

View File

@ -0,0 +1,26 @@
import React from "react";
interface LabeledButtonProps {
label: string; // Label for the button
onClick?: () => void; // Function to call when the button is clicked
disabled?: boolean; // Optional prop to disable the button
value?: string;
}
const LabeledButton: React.FC<LabeledButtonProps> = ({
label,
onClick,
disabled = false,
value = "Click here",
}) => {
return (
<div className="labeled-button-container">
<div className="label">{label}</div>
<button className="button" onClick={onClick} disabled={disabled}>
{value}
</button>
</div>
);
};
export default LabeledButton;

View File

@ -20,6 +20,7 @@ const LabledDropdown: React.FC<LabledDropdownProps> = ({ defaultOption, options
header={activeOption} // Display the current active option
options={options} // Use the options from props
onSelect={handleSelect} // Handle option selection
search = {false}
/>
</div>
);

View File

@ -0,0 +1,71 @@
import React, { useState } from "react";
const MultiEmailInvite: React.FC = () => {
const [emails, setEmails] = useState<string[]>([]);
const [inputValue, setInputValue] = useState("");
const handleAddEmail = () => {
const trimmedEmail = inputValue.trim();
// Validate email
if (!trimmedEmail || !validateEmail(trimmedEmail)) {
alert("Please enter a valid email address.");
return;
}
// Check for duplicates
if (emails.includes(trimmedEmail)) {
alert("This email has already been added.");
return;
}
// Add email to the list
setEmails([...emails, trimmedEmail]);
setInputValue(""); // Clear the input field after adding
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === ",") {
e.preventDefault();
handleAddEmail();
}
};
const handleRemoveEmail = (emailToRemove: string) => {
setEmails(emails.filter((email) => email !== emailToRemove));
};
const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const [inputFocus, setInputFocus] = useState(false);
return (
<div className="multi-email-invite-input-container">
<div className={`multi-email-invite-input${inputFocus ? " active" : ""}`}>
{emails.map((email, index) => (
<div key={index} className="entered-emails">
{email}
<span onClick={() => handleRemoveEmail(email)}>&times;</span>
</div>
))}
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onFocus={() => setInputFocus(true)}
onBlur={() => setInputFocus(false)}
onKeyDown={handleKeyDown}
placeholder="Enter email and press Enter or comma"
/>
</div>
<div onClick={handleAddEmail} className="invite-button">
Invite
</div>
</div>
);
};
export default MultiEmailInvite;

View File

@ -4,53 +4,81 @@ interface DropdownProps {
header: string;
options: string[];
onSelect: (option: string) => void;
search?: boolean;
onClick?: () => void;
onChange?: () => void;
}
const RegularDropDown: React.FC<DropdownProps> = ({
header,
options,
onSelect,
search = true,
onClick,
onChange,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState<string | null>(null);
const dropdownRef = useRef<HTMLDivElement>(null); // Create a ref for the dropdown container
const [searchTerm, setSearchTerm] = useState(""); // State to store search term
const [filteredOptions, setFilteredOptions] = useState<string[]>(options); // State for filtered options
const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
// Reset selectedOption when the dropdown closes
useEffect(() => {
if (!isOpen) {
setSelectedOption(null); // Clear local state when the dropdown closes
setSelectedOption(null);
setSearchTerm(""); // Clear the search term when the dropdown closes
setFilteredOptions(options); // Reset filtered options when the dropdown closes
}
}, [isOpen]);
}, [isOpen, options]);
// Reset selectedOption when the header prop changes
useEffect(() => {
setSelectedOption(null); // Ensure the dropdown reflects the updated header
}, [header]);
setSelectedOption(null);
setSearchTerm(""); // Reset search term if header changes
setFilteredOptions(options); // Reset options if header changes
}, [header, options]);
// Close dropdown if clicked outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false); // Close the dropdown if clicked outside
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener("click", handleClickOutside);
// Cleanup the event listener on component unmount
return () => {
document.removeEventListener("click", handleClickOutside);
};
}, []);
// Toggle the dropdown
const toggleDropdown = () => {
setIsOpen((prev) => !prev);
};
// Handle option selection
const handleOptionClick = (option: string) => {
setSelectedOption(option);
onSelect(option); // Call the onSelect function passed from the parent
setIsOpen(false); // Close the dropdown after selection
onSelect(option);
setIsOpen(false);
};
// Handle search input change
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const term = event.target.value;
setSearchTerm(term);
// Filter options based on the search term
const filtered = options.filter((option) =>
option.toLowerCase().includes(term.toLowerCase())
);
setFilteredOptions(filtered);
};
return (
@ -64,15 +92,32 @@ const RegularDropDown: React.FC<DropdownProps> = ({
{/* Dropdown Options */}
{isOpen && (
<div className="dropdown-options">
{options.map((option, index) => (
<div
className="option"
key={index}
onClick={() => handleOptionClick(option)}
>
{option}
{/* Search Bar */}
{search && (
<div className="dropdown-search">
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
))}
)}
{/* Filtered Options */}
{filteredOptions.length > 0 ? (
filteredOptions.map((option, index) => (
<div
className="option"
key={index}
onClick={() => handleOptionClick(option)}
>
{option}
</div>
))
) : (
<div className="no-options">No options found</div>
)}
</div>
)}
</div>

View File

@ -81,12 +81,6 @@ const DropDownList: React.FC<DropDownListProps> = ({
showAddIcon={false}
items={[]}
/>
<DropDownList
value="Paths"
showKebabMenu={false}
showAddIcon={false}
items={[]}
/>
</>
)}
</div>

View File

@ -0,0 +1,45 @@
const DB_NAME = 'GLTFStorage';
const STORE_NAME = 'models';
const DB_VERSION = 1;
export function initializeDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME);
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
export async function storeGLTF(key: string, file: Blob): Promise<void> {
const db = await initializeDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const request = store.put(file, key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
export async function retrieveGLTF(key: string): Promise<Blob | undefined> {
const db = await initializeDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.get(key);
request.onsuccess = () => resolve(request.result as Blob | undefined);
request.onerror = () => reject(request.error);
});
}

View File

@ -0,0 +1,54 @@
import * as THREE from "three";
import { Geometry, Base, Subtraction } from "@react-three/csg";
import { useDeleteModels } from "../../../store/store";
import { useRef } from "react";
export interface CsgProps {
position: THREE.Vector3 | [number, number, number];
scale: THREE.Vector3 | [number, number, number];
model: THREE.Object3D;
hoveredDeletableWallItem: { current: THREE.Mesh | null };
}
export const Csg: React.FC<CsgProps> = (props) => {
const { deleteModels } = useDeleteModels();
const modelRef = useRef<THREE.Object3D>();
const originalMaterials = useRef<Map<THREE.Mesh, THREE.Material>>(new Map());
const handleHover = (hovered: boolean, object: THREE.Mesh | null) => {
if (modelRef.current && deleteModels) {
modelRef.current.traverse((child) => {
if (child instanceof THREE.Mesh) {
if (!originalMaterials.current.has(child)) {
originalMaterials.current.set(child, child.material);
}
child.material = child.material.clone();
child.material.color.set(hovered && deleteModels ? 0xff0000 : (originalMaterials.current.get(child) as any).color);
}
});
}
props.hoveredDeletableWallItem.current = hovered ? object : null;
};
return (
<Geometry>
<Subtraction {...props}>
<Geometry>
<Base geometry={new THREE.BoxGeometry()} />
</Geometry>
</Subtraction>
<primitive
object={props.model}
ref={modelRef}
onPointerOver={(e: any) => {
e.stopPropagation();
handleHover(true, e.object.parent);
}}
onPointerOut={(e: any) => {
e.stopPropagation();
handleHover(false, null);
}}
/>
</Geometry>
);
};

View File

@ -0,0 +1,80 @@
import * as THREE from 'three';
import { DragControls } from 'three/examples/jsm/controls/DragControls';
import * as CONSTANTS from '../world/worldConstants';
import DragPoint from '../geomentries/points/dragPoint';
import * as Types from "../world/worldTypes";
import { updatePoint } from '../../../services/factoryBuilder/lines/updatePointApi';
import { Socket } from 'socket.io-client';
export default async function addDragControl(
dragPointControls: Types.RefDragControl,
currentLayerPoint: Types.RefMeshArray,
state: Types.ThreeState,
floorPlanGroupPoint: Types.RefGroup,
floorPlanGroupLine: Types.RefGroup,
lines: Types.RefLines,
onlyFloorlines: Types.RefOnlyFloorLines,
socket: Socket<any>
) {
////////// Dragging Point and also change the size to indicate during hover //////////
dragPointControls.current = new DragControls(currentLayerPoint.current, state.camera, state.gl.domElement);
dragPointControls.current.enabled = false;
dragPointControls.current.addEventListener('drag', function (event) {
const object = event.object;
if (object.visible) {
(state.controls as any).enabled = false;
DragPoint(event as any, floorPlanGroupPoint, floorPlanGroupLine, state.scene, lines, onlyFloorlines)
} else {
(state.controls as any).enabled = true;
}
});
dragPointControls.current.addEventListener('dragstart', function (event) {
});
dragPointControls.current.addEventListener('dragend', async function (event) {
if (!dragPointControls.current) return;
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// await updatePoint(
// organization,
// { "x": event.object.position.x, "y": 0.01, "z": event.object.position.z },
// event.object.uuid,
// )
//SOCKET
const data = {
organization: organization,
position: { "x": event.object.position.x, "y": 0.01, "z": event.object.position.z },
uuid: event.object.uuid,
socketId: socket.id
}
socket.emit('v1:Line:update', data);
if (state.controls) {
(state.controls as any).enabled = true;
}
});
dragPointControls.current.addEventListener('hoveron', function (event: any) {
if ((event.object as Types.Mesh).name === "point") {
event.object.material.uniforms.uInnerColor.value.set(event.object.userData.color)
}
});
dragPointControls.current.addEventListener('hoveroff', function (event: any) {
if ((event.object as Types.Mesh).name === "point") {
event.object.material.uniforms.uInnerColor.value.set(new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor))
}
});
}

View File

@ -0,0 +1,8 @@
import * as Types from "../world/worldTypes";
export default function handleContextMenu(
menuVisible: Types.Boolean,
setMenuVisible: Types.BooleanState
): void {
// setMenuVisible(true)
}

View File

@ -0,0 +1,64 @@
import * as THREE from 'three';
import * as Types from "../world/worldTypes";
function handleMeshDown(
event: Types.MeshEvent,
currentWallItem: Types.RefMesh,
setSelectedWallItem: Types.setSelectedWallItemSetState,
setSelectedItemsIndex: Types.setSelectedItemsIndexSetState,
wallItems: Types.wallItems,
toggleView: Types.Boolean
): void {
////////// To select which of the Wall item and CSG is selected to be dragged //////////
if (!toggleView) {
if (currentWallItem.current) {
currentWallItem.current.children.forEach((child) => {
if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
const material = (child as THREE.Mesh).material;
if (Array.isArray(material)) {
material.forEach(mat => {
if (mat instanceof THREE.MeshStandardMaterial) {
mat.emissive = new THREE.Color("black");
}
});
} else if (material instanceof THREE.MeshStandardMaterial) {
material.emissive = new THREE.Color("black");
}
}
});
currentWallItem.current = null;
setSelectedWallItem(null);
setSelectedItemsIndex(null);
}
if (event.intersections.length > 0) {
const clickedIndex = wallItems.findIndex((item) => item.model === event.intersections[0]?.object?.parent?.parent);
if (clickedIndex !== -1) {
setSelectedItemsIndex(clickedIndex);
const wallItemModel = wallItems[clickedIndex]?.model;
if (wallItemModel && wallItemModel.parent && wallItemModel.parent.parent) {
currentWallItem.current = (wallItemModel.parent.parent.children[0]?.children[1]?.children[0] as Types.Mesh) || null;
setSelectedWallItem(wallItemModel.parent);
// currentWallItem.current?.children.forEach((child) => {
// if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
// const material = (child as THREE.Mesh).material;
// if (Array.isArray(material)) {
// material.forEach(mat => {
// if (mat instanceof THREE.MeshStandardMaterial) {
// mat.emissive = new THREE.Color("green");
// }
// });
// } else if (material instanceof THREE.MeshStandardMaterial) {
// material.emissive = new THREE.Color("green");
// }
// }
// });
}
}
}
}
}
export default handleMeshDown;

View File

@ -0,0 +1,34 @@
import * as THREE from 'three';
import * as Types from "../world/worldTypes";
function handleMeshMissed(
currentWallItem: Types.RefMesh,
setSelectedWallItem: Types.setSelectedWallItemSetState,
setSelectedItemsIndex: Types.setSelectedItemsIndexSetState
): void {
////////// If an item is selected and then clicked outside other than the selected object, this runs and removes the color of the selected object and sets setSelectedWallItem and setSelectedItemsIndex as null //////////
if (currentWallItem.current) {
currentWallItem.current.children.forEach((child) => {
if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
const material = (child as THREE.Mesh).material;
if (Array.isArray(material)) {
material.forEach(mat => {
if (mat instanceof THREE.MeshStandardMaterial) {
mat.emissive = new THREE.Color("black");
}
});
} else if (material instanceof THREE.MeshStandardMaterial) {
material.emissive = new THREE.Color("black");
}
}
});
currentWallItem.current = null;
setSelectedWallItem(null);
setSelectedItemsIndex(null);
}
}
export default handleMeshMissed;

View File

@ -0,0 +1,87 @@
import * as THREE from 'three';
import * as CONSTANTS from '../world/worldConstants';
import * as Types from "../world/worldTypes";
function DeletableLineorPoint(
state: Types.ThreeState,
plane: Types.RefMesh,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroupPoint: Types.RefGroup,
hoveredDeletableLine: Types.RefMesh,
hoveredDeletablePoint: Types.RefMesh
): void {
////////// Altering the color of the hovered line or point during the deletion time //////////
if (!plane.current) return;
let intersects = state.raycaster.intersectObject(plane.current, true);
let visibleIntersectLines;
if (floorPlanGroupLine.current) { visibleIntersectLines = state.raycaster?.intersectObjects(floorPlanGroupLine.current.children, true); }
const visibleIntersectLine = visibleIntersectLines?.find(intersect => intersect.object.visible) as THREE.Line | undefined || null;
let visibleIntersectPoints;
if (floorPlanGroupPoint.current) {
visibleIntersectPoints = state.raycaster?.intersectObjects(floorPlanGroupPoint.current.children, true);
}
const visibleIntersectPoint = visibleIntersectPoints?.find(intersect => intersect.object.visible) as THREE.Mesh | undefined;
function getLineColor(lineType: string | undefined): string {
switch (lineType) {
case CONSTANTS.lineConfig.wallName: return CONSTANTS.lineConfig.wallColor;
case CONSTANTS.lineConfig.floorName: return CONSTANTS.lineConfig.floorColor;
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.lineConfig.aisleColor;
default: return CONSTANTS.lineConfig.defaultColor;
}
}
if (intersects.length > 0) {
if (visibleIntersectPoint) {
if (hoveredDeletableLine.current) {
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
const color = getLineColor(lineType);
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
hoveredDeletableLine.current = null;
}
hoveredDeletablePoint.current = (visibleIntersectPoint as any).object;
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(new THREE.Color("red"));
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set(new THREE.Color("red"));
// (hoveredDeletablePoint.current as THREE.Mesh).scale.set(1.5, 1.5, 1.5);
} else if (hoveredDeletablePoint.current) {
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(CONSTANTS.pointConfig.defaultInnerColor);
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set((hoveredDeletablePoint.current as any).userData.color);
// hoveredDeletablePoint.current.scale.set(1, 1, 1);
hoveredDeletablePoint.current = null;
}
if (visibleIntersectLine && !visibleIntersectPoint) {
if (hoveredDeletableLine.current) {
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
const color = getLineColor(lineType);
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
hoveredDeletableLine.current = null;
}
if (hoveredDeletablePoint.current) {
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(CONSTANTS.pointConfig.defaultInnerColor);
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set((hoveredDeletablePoint.current as any).userData.color);
// hoveredDeletablePoint.current.scale.set(1, 1, 1);
hoveredDeletablePoint.current = null;
}
hoveredDeletableLine.current = (visibleIntersectLine as any).object;
if (hoveredDeletableLine.current) {
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color("red");
}
} else if (hoveredDeletableLine.current) {
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
const color = getLineColor(lineType);
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
hoveredDeletableLine.current = null;
}
}
}
export default DeletableLineorPoint;

View File

@ -0,0 +1,97 @@
import * as Types from "../world/worldTypes";
import * as CONSTANTS from '../world/worldConstants';
import createAndMoveReferenceLine from "../geomentries/lines/createAndMoveReferenceLine";
async function Draw(
state: Types.ThreeState,
plane: Types.RefMesh,
cursorPosition: Types.Vector3,
floorPlanGroupPoint: Types.RefGroup,
floorPlanGroupLine: Types.RefGroup,
snappedPoint: Types.RefVector3,
isSnapped: Types.RefBoolean,
isSnappedUUID: Types.RefString,
line: Types.RefLine,
lines: Types.RefLines,
ispreSnapped: Types.RefBoolean,
floorPlanGroup: Types.RefGroup,
ReferenceLineMesh: Types.RefMesh,
LineCreated: Types.RefBoolean,
setRefTextUpdate: Types.NumberIncrementState,
Tube: Types.RefTubeGeometry,
anglesnappedPoint: Types.RefVector3,
isAngleSnapped: Types.RefBoolean,
toolMode: Types.String,
): Promise<void> {
////////// Snapping the cursor during the drawing time and also changing the color of the intersected lines //////////
if (!plane.current) return;
const intersects = state.raycaster.intersectObject(plane.current, true);
if (intersects.length > 0 && (toolMode === "Wall" || toolMode === "Aisle" || toolMode === "Floor")) {
const intersectionPoint = intersects[0].point;
cursorPosition.copy(intersectionPoint);
const snapThreshold = 1;
if (line.current.length === 0) {
for (const point of floorPlanGroupPoint.current.children) {
const pointType = point.userData.type;
const canSnap =
((toolMode === "Wall") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
((toolMode === "Floor") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
((toolMode === "Aisle") && pointType === CONSTANTS.lineConfig.aisleName);;
if (canSnap && cursorPosition.distanceTo(point.position) < snapThreshold + 0.5 && point.visible) {
cursorPosition.copy(point.position);
snappedPoint.current = point.position;
ispreSnapped.current = true;
isSnapped.current = false;
isSnappedUUID.current = point.uuid;
break;
} else {
ispreSnapped.current = false;
}
}
} else if (line.current.length > 0 && line.current[0]) {
for (const point of floorPlanGroupPoint.current.children) {
const pointType = point.userData.type;
let canSnap =
((toolMode === "Wall") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
((toolMode === "Floor") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
((toolMode === "Aisle") && pointType === CONSTANTS.lineConfig.aisleName);
if (canSnap && cursorPosition.distanceTo(point.position) < snapThreshold && point.visible) {
cursorPosition.copy(point.position);
snappedPoint.current = point.position;
isSnapped.current = true;
ispreSnapped.current = false;
isSnappedUUID.current = point.uuid;
break;
} else {
isSnapped.current = false;
}
}
createAndMoveReferenceLine(
line.current[0][0],
cursorPosition,
isSnapped,
ispreSnapped,
line,
setRefTextUpdate,
floorPlanGroup,
ReferenceLineMesh,
LineCreated,
Tube,
anglesnappedPoint,
isAngleSnapped
);
}
}
}
export default Draw;

View File

@ -0,0 +1,56 @@
import * as THREE from 'three';
import * as Types from '../../world/worldTypes';
import * as CONSTANTS from '../../world/worldConstants';
export default async function addAisleToScene(
aisle: Types.Line,
floorGroupAisle: Types.RefGroup,
): Promise<void> {
if (aisle.length >= 2 && aisle[0] && aisle[1]) {
const start: Types.Vector3 = aisle[0][0];
const end: Types.Vector3 = aisle[1][0];
const direction = new THREE.Vector3(
end.x - start.x,
end.y - start.y,
end.z - start.z
).normalize();
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
const offsetDistance = CONSTANTS.aisleConfig.width;
const leftStart = new THREE.Vector3().copy(start).addScaledVector(perp, offsetDistance);
const rightStart = new THREE.Vector3().copy(start).addScaledVector(perp, -offsetDistance);
const leftEnd = new THREE.Vector3().copy(end).addScaledVector(perp, offsetDistance);
const rightEnd = new THREE.Vector3().copy(end).addScaledVector(perp, -offsetDistance);
const stripShape = new THREE.Shape();
stripShape.moveTo(leftStart.x, leftStart.z);
stripShape.lineTo(leftEnd.x, leftEnd.z);
stripShape.lineTo(rightEnd.x, rightEnd.z);
stripShape.lineTo(rightStart.x, rightStart.z);
stripShape.lineTo(leftStart.x, leftStart.z);
const extrudeSettings = {
depth: CONSTANTS.aisleConfig.height,
bevelEnabled: false,
};
const stripGeometry = new THREE.ExtrudeGeometry(stripShape, extrudeSettings);
const stripMaterial = new THREE.MeshStandardMaterial({
color: CONSTANTS.aisleConfig.defaultColor,
polygonOffset: true,
polygonOffsetFactor: -1,
polygonOffsetUnits: -1,
});
const stripMesh = new THREE.Mesh(stripGeometry, stripMaterial);
stripMesh.receiveShadow = true;
stripMesh.castShadow = true;
stripMesh.position.y = (aisle[0][2] - 1) * CONSTANTS.wallConfig.height + 0.01;
stripMesh.rotateX(Math.PI / 2);
floorGroupAisle.current.add(stripMesh);
}
}

View File

@ -0,0 +1,19 @@
import * as Types from '../../world/worldTypes';
import addAisleToScene from './addAilseToScene';
import * as CONSTANTS from '../../world/worldConstants';
export default async function loadAisles(
lines: Types.RefLines,
floorGroupAisle: Types.RefGroup
) {
// console.log('lines: ', lines.current[0][0][0]);
if (!floorGroupAisle.current) return
floorGroupAisle.current.children = [];
const aisles = lines.current.filter((line) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName);
if (aisles.length > 0) {
aisles.forEach((aisle: Types.Line) => {
addAisleToScene(aisle, floorGroupAisle)
})
}
}

View File

@ -0,0 +1,186 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import gsap from 'gsap';
import { toast } from 'react-toastify';
import TempLoader from './tempLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import * as Types from "../../world/worldTypes";
import { retrieveGLTF, storeGLTF } from '../../indexDB/idbUtils';
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { Socket } from 'socket.io-client';
import * as CONSTANTS from '../../world/worldConstants';
async function addAssetModel(
raycaster: THREE.Raycaster,
camera: THREE.Camera,
pointer: THREE.Vector2,
floorGroup: Types.RefGroup,
setFloorItems: Types.setFloorItemSetState,
itemsGroup: Types.RefGroup,
isTempLoader: Types.RefBoolean,
tempLoader: Types.RefMesh,
socket: Socket<any>,
selectedItem: any,
setSelectedItem: any,
plane: Types.RefMesh,
): Promise<void> {
////////// Load Floor GLtf's and set the positions, rotation, type etc. in state and store in localstorage //////////
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
try {
isTempLoader.current = true;
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
raycaster.setFromCamera(pointer, camera);
const floorIntersections = raycaster.intersectObjects(floorGroup.current.children, true);
const intersectedFloor = floorIntersections.find(intersect => intersect.object.name.includes("Floor"));
const planeIntersections = raycaster.intersectObject(plane.current!, true);
const intersectedPlane = planeIntersections[0];
let intersectPoint: THREE.Vector3 | null = null;
if (intersectedFloor && intersectedPlane) {
intersectPoint = intersectedFloor.distance < intersectedPlane.distance ? (new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z)) : (new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z));
} else if (intersectedFloor) {
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z);
} else if (intersectedPlane) {
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
}
if (intersectPoint) {
if (intersectPoint.y < 0) {
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
}
const cachedModel = THREE.Cache.get(selectedItem.id);
if (cachedModel) {
// console.log(`[Cache] Fetching ${selectedItem.name}`);
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
return;
} else {
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
if (cachedModelBlob) {
// console.log(`Added ${selectedItem.name} from indexDB`);
const blobUrl = URL.createObjectURL(cachedModelBlob);
loader.load(blobUrl, (gltf) => {
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(selectedItem.id, gltf);
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
},
() => {
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
});
} else {
// console.log(`Added ${selectedItem.name} from Backend`);
loader.load(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`, async (gltf) => {
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob());
await storeGLTF(selectedItem.id, modelBlob);
THREE.Cache.add(selectedItem.id, gltf);
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
},
() => {
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
});
}
}
}
} catch (error) {
console.error('Error fetching asset model:', error);
} finally {
setSelectedItem({});
}
}
async function handleModelLoad(
gltf: any,
intersectPoint: THREE.Vector3,
selectedItem: any,
itemsGroup: Types.RefGroup,
tempLoader: Types.RefMesh,
isTempLoader: Types.RefBoolean,
setFloorItems: Types.setFloorItemSetState,
socket: Socket<any>
) {
const model = gltf.scene.clone();
model.userData = { name: selectedItem.name, modelId: selectedItem.id };
model.position.set(intersectPoint!.x, 3 + intersectPoint!.y, intersectPoint!.z);
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
model.traverse((child: any) => {
if (child) {
child.castShadow = true;
child.receiveShadow = true;
}
});
itemsGroup.current.add(model);
if (tempLoader.current) {
(<any>tempLoader.current.material).dispose();
(<any>tempLoader.current.geometry).dispose();
itemsGroup.current.remove(tempLoader.current);
tempLoader.current = undefined;
}
const newFloorItem: Types.FloorItemType = {
modeluuid: model.uuid,
modelname: selectedItem.name,
modelfileID: selectedItem.id,
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// newFloorItem.modeluuid,
// newFloorItem.modelname,
// newFloorItem.position,
// { "x": model.rotation.x, "y": model.rotation.y, "z": model.rotation.z },
// newFloorItem.modelfileID!,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v1:FloorItems:set", data);
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
}
export default addAssetModel;

View File

@ -0,0 +1,153 @@
import * as THREE from "three";
import gsap from "gsap";
import * as Types from "../../world/worldTypes";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { initializeDB, retrieveGLTF, storeGLTF } from "../../indexDB/idbUtils";
import * as CONSTANTS from '../../world/worldConstants';
import { toast } from 'react-toastify';
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
let currentTaskId = 0; // Track the active task
let activePromises = new Map<number, boolean>(); // Map to track task progress
export default async function assetManager(
data: any,
itemsGroup: Types.RefGroup,
loader: GLTFLoader,
) {
const taskId = ++currentTaskId; // Increment taskId for each call
activePromises.set(taskId, true); // Mark task as active
// console.log("Received message from worker:", data);
if (data.toRemove.length > 0) {
data.toRemove.forEach((uuid: string) => {
const item = itemsGroup.current.getObjectByProperty("uuid", uuid);
if (item) {
// Traverse and dispose of resources
// item.traverse((child: THREE.Object3D) => {
// if (child instanceof THREE.Mesh) {
// if (child.geometry) child.geometry.dispose();
// if (Array.isArray(child.material)) {
// child.material.forEach((material) => {
// if (material.map) material.map.dispose();
// material.dispose();
// });
// } else if (child.material) {
// if (child.material.map) child.material.map.dispose();
// child.material.dispose();
// }
// }
// });
// Remove the object from the scene
itemsGroup.current.remove(item);
}
});
}
if (data.toAdd.length > 0) {
await initializeDB();
for (const item of data.toAdd) {
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
await new Promise<void>(async (resolve) => {
const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`;
// Check Three.js Cache
const cachedModel = THREE.Cache.get(item.modelfileID!);
if (cachedModel) {
// console.log(`[Cache] Fetching ${item.modelname}`);
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve);
return;
}
// Check IndexedDB
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
if (indexedDBModel) {
// console.log(`[IndexedDB] Fetching ${item.modelname}`);
const blobUrl = URL.createObjectURL(indexedDBModel);
loader.load(
blobUrl,
(gltf) => {
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(item.modelfileID!, gltf); // Add to cache
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
},
undefined,
(error) => {
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
resolve();
}
);
return;
}
// Fetch from Backend
// console.log(`[Backend] Fetching ${item.modelname}`);
loader.load(
modelUrl,
async (gltf) => {
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
await storeGLTF(item.modelfileID!, modelBlob); // Store in IndexedDB
THREE.Cache.add(item.modelfileID!, gltf); // Add to cache
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
},
undefined,
(error) => {
toast.error(`[Backend] Error loading ${item.modelname}:`);
resolve();
}
);
});
}
function processLoadedModel(
gltf: any,
item: Types.FloorItemType,
itemsGroup: Types.RefGroup,
resolve: () => void
) {
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
const existingModel = itemsGroup.current.getObjectByProperty("uuid", item.modeluuid);
if (existingModel) {
// console.log(`Model ${item.modelname} already exists in the scene.`);
resolve();
return;
}
const model = gltf;
model.uuid = item.modeluuid;
model.userData = { name: item.modelname, modelId: item.modelfileID };
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
model.position.set(...item.position);
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
model.traverse((child: any) => {
if (child.isMesh) {
// Clone the material to ensure changes are independent
// child.material = child.material.clone();
child.castShadow = true;
child.receiveShadow = true;
}
});
itemsGroup?.current?.add(model);
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: "power2.out" });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 0.5, ease: "power2.out", onStart: resolve, });
}
}
activePromises.delete(taskId); // Mark task as complete
}
// Cancel ongoing task when new call arrives
export function cancelOngoingTasks() {
activePromises.clear(); // Clear all ongoing tasks
}

View File

@ -0,0 +1,25 @@
import * as Types from "../../world/worldTypes";
let lastUpdateTime = 0;
export default function assetVisibility(
itemsGroup: Types.RefGroup,
cameraPosition: Types.Vector3,
renderDistance: Types.Number,
throttleTime = 100
): void {
const now = performance.now();
if (now - lastUpdateTime < throttleTime) return;
lastUpdateTime = now;
if (!itemsGroup?.current || !cameraPosition) return;
itemsGroup.current.children.forEach((child) => {
const Distance = cameraPosition.distanceTo(child.position);
if (Distance <= renderDistance) {
child.visible = true;
} else {
child.visible = false;
}
});
}

View File

@ -0,0 +1,43 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function DeletableHoveredFloorItems(
state: Types.ThreeState,
itemsGroup: Types.RefGroup,
hoveredDeletableFloorItem: Types.RefMesh,
setDeletableFloorItem: any
): void {
////////// Altering the color of the hovered GLTF item during the Deletion time //////////
state.raycaster.setFromCamera(state.pointer, state.camera);
const intersects = state.raycaster.intersectObjects(itemsGroup.current.children, true);
if (intersects.length > 0) {
if (intersects[0].object.name === "Pole") {
return;
}
if (hoveredDeletableFloorItem.current) {
hoveredDeletableFloorItem.current = undefined;
setDeletableFloorItem(null);
}
let currentObject = intersects[0].object;
while (currentObject) {
if (currentObject.name === "Scene") {
hoveredDeletableFloorItem.current = currentObject as THREE.Mesh;
setDeletableFloorItem(currentObject);
break;
}
currentObject = currentObject.parent as THREE.Object3D;
}
} else {
if (hoveredDeletableFloorItem.current) {
hoveredDeletableFloorItem.current = undefined;
setDeletableFloorItem(null);
}
}
}
export default DeletableHoveredFloorItems;

View File

@ -0,0 +1,82 @@
import { toast } from 'react-toastify';
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
import { getFloorItems } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
import { Socket } from 'socket.io-client';
async function DeleteFloorItems(
itemsGroup: Types.RefGroup,
hoveredDeletableFloorItem: Types.RefMesh,
setFloorItems: Types.setFloorItemSetState,
socket: Socket<any>
): Promise<void> {
////////// Deleting the hovered Floor GLTF from the scene (itemsGroup.current) and from the floorItems and also update it in the localstorage //////////
if (hoveredDeletableFloorItem.current) {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
const items = await getFloorItems(organization);
const removedItem = items.find(
(item: { modeluuid: string }) => item.modeluuid === hoveredDeletableFloorItem.current?.uuid
);
if (!removedItem) {
return
}
//REST
// const response = await deleteFloorItem(organization, removedItem.modeluuid, removedItem.modelname);
//SOCKET
const data = {
organization: organization,
modeluuid: removedItem.modeluuid,
modelname: removedItem.modelname,
socketId: socket.id
}
const response = socket.emit('v1:FloorItems:delete', data)
if (response) {
const updatedItems = items.filter(
(item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid
);
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || '[]');
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid);
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
if (hoveredDeletableFloorItem.current) {
// Traverse and dispose of resources
hoveredDeletableFloorItem.current.traverse((child: THREE.Object3D) => {
if (child instanceof THREE.Mesh) {
if (child.geometry) child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach((material) => {
if (material.map) material.map.dispose();
material.dispose();
});
} else if (child.material) {
if (child.material.map) child.material.map.dispose();
child.material.dispose();
}
}
});
// Remove the object from the scene
itemsGroup.current.remove(hoveredDeletableFloorItem.current);
}
setFloorItems(updatedItems);
toast.success("Model Removed!");
}
}
}
export default DeleteFloorItems;

View File

@ -0,0 +1,29 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function TempLoader(
intersectPoint: Types.Vector3,
isTempLoader: Types.RefBoolean,
tempLoader: Types.RefMesh,
itemsGroup: Types.RefGroup
): void {
////////// Temporary Loader that indicates the gltf is being loaded //////////
////////// Bug: Can't Load More than one TempLoader if done, it won't leave the scene //////////
if (tempLoader.current) {
itemsGroup.current.remove(tempLoader.current);
}
if (isTempLoader.current) {
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: "white" });
tempLoader.current = new THREE.Mesh(cubeGeometry, cubeMaterial);
tempLoader.current.position.set(intersectPoint.x, 0.5 + intersectPoint.y, intersectPoint.z);
itemsGroup.current.add(tempLoader.current);
isTempLoader.current = false;
}
}
export default TempLoader;

View File

@ -0,0 +1,64 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
import * as CONSTANTS from "../../world/worldConstants";
import texturePath from "../../../../assets/textures/floor/concreteFloorWorn001Diff2k.jpg";
import normalPath from "../../../../assets/textures/floor/concreteFloorWorn001NorGl2k.jpg";
// Cache for materials
const materialCache = new Map<string, THREE.Material>();
export default function addFloorToScene(
shape: THREE.Shape,
layer: number,
floorGroup: Types.RefGroup,
userData: any,
) {
const textureLoader = new THREE.TextureLoader();
const textureScale = CONSTANTS.floorConfig.textureScale;
const materialKey = `floorMaterial_${textureScale}`;
let material: THREE.Material;
if (materialCache.has(materialKey)) {
material = materialCache.get(materialKey) as THREE.Material;
} else {
const floorTexture = textureLoader.load(texturePath);
const normalMap = textureLoader.load(normalPath);
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(textureScale, textureScale);
floorTexture.colorSpace = THREE.SRGBColorSpace;
normalMap.wrapS = normalMap.wrapT = THREE.RepeatWrapping;
normalMap.repeat.set(textureScale, textureScale);
material = new THREE.MeshStandardMaterial({
map: floorTexture,
normalMap: normalMap,
side: THREE.DoubleSide,
});
materialCache.set(materialKey, material);
}
const extrudeSettings = {
depth: CONSTANTS.floorConfig.height,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const mesh = new THREE.Mesh(geometry, material);
mesh.receiveShadow = true;
mesh.position.y = layer;
mesh.rotateX(Math.PI / 2);
mesh.name = `Floor_Layer_${layer}`;
// Store UUIDs for debugging or future processing
mesh.userData.uuids = userData;
floorGroup.current.add(mesh);
}

View File

@ -0,0 +1,179 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
import * as CONSTANTS from '../../world/worldConstants';
import addPointToScene from '../points/addPointToScene';
import addLineToScene from '../lines/addLineToScene';
import splitLine from '../lines/splitLine';
import removeReferenceLine from '../lines/removeReferenceLine';
import getClosestIntersection from '../lines/getClosestIntersection';
import arrayLineToObject from '../lines/lineConvertions/arrayLineToObject';
import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
import { Socket } from 'socket.io-client';
async function drawOnlyFloor(
raycaster: THREE.Raycaster,
state: Types.ThreeState,
camera: THREE.Camera,
plane: Types.RefMesh,
floorPlanGroupPoint: Types.RefGroup,
snappedPoint: Types.RefVector3,
isSnapped: Types.RefBoolean,
isSnappedUUID: Types.RefString,
line: Types.RefLine,
ispreSnapped: Types.RefBoolean,
anglesnappedPoint: Types.RefVector3,
isAngleSnapped: Types.RefBoolean,
onlyFloorline: Types.RefOnlyFloorLine,
onlyFloorlines: Types.RefOnlyFloorLines,
lines: Types.RefLines,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroup: Types.RefGroup,
ReferenceLineMesh: Types.RefMesh,
LineCreated: Types.RefBoolean,
currentLayerPoint: Types.RefMeshArray,
dragPointControls: Types.RefDragControl,
setNewLines: any,
setDeletedLines: any,
activeLayer: Types.Number,
socket: Socket<any>
): Promise<void> {
////////// Creating lines Based on the positions clicked //////////
if (!plane.current) return
const intersects = raycaster.intersectObject(plane.current, true);
const intersectsLines = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
const intersectsPoint = raycaster.intersectObjects(floorPlanGroupPoint.current.children, true);
const VisibleintersectsPoint = intersectsPoint.find(intersect => intersect.object.visible);
const visibleIntersect = intersectsLines.find(intersect => intersect.object.visible && intersect.object.name !== CONSTANTS.lineConfig.referenceName);
if ((intersectsPoint.length === 0 || VisibleintersectsPoint === undefined) && intersectsLines.length > 0 && !isSnapped.current && !ispreSnapped.current) {
////////// Clicked on a preexisting Line //////////
if (visibleIntersect && (intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.floorName || intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName)) {
let pointColor, lineColor;
if (intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName) {
pointColor = CONSTANTS.pointConfig.wallOuterColor;
lineColor = CONSTANTS.lineConfig.wallColor;
} else {
pointColor = CONSTANTS.pointConfig.floorOuterColor;
lineColor = CONSTANTS.lineConfig.floorColor;
}
let IntersectsPoint = new THREE.Vector3(intersects[0].point.x, 0.01, intersects[0].point.z);
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
IntersectsPoint = anglesnappedPoint.current;
}
if (visibleIntersect.object instanceof THREE.Mesh) {
const ThroughPoint = (visibleIntersect.object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
let intersectionPoint = getClosestIntersection(ThroughPoint, IntersectsPoint);
if (intersectionPoint) {
const newLines = splitLine(visibleIntersect, intersectionPoint, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lines, setDeletedLines, floorPlanGroupLine, socket, pointColor, lineColor, intersectsLines[0].object.userData.linePoints[0][3]);
setNewLines([newLines[0], newLines[1]]);
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.floorName]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([newLines[0], newLines[1], line.current]);
onlyFloorline.current.push(line.current as Types.Line);
onlyFloorlines.current.push(onlyFloorline.current);
onlyFloorline.current = [];
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.floorColor, line.current, floorPlanGroupLine);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
return;
}
}
}
}
if (intersects.length > 0 && intersectsLines.length === 0) {
////////// Clicked on an empty place or a point //////////
let intersectionPoint = intersects[0].point;
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
intersectionPoint = anglesnappedPoint.current;
}
if (isSnapped.current && line.current.length > 0 && snappedPoint.current) {
intersectionPoint = snappedPoint.current;
}
if (ispreSnapped.current && snappedPoint.current) {
intersectionPoint = snappedPoint.current;
}
if (!isSnapped.current && !ispreSnapped.current) {
addPointToScene(intersectionPoint, CONSTANTS.pointConfig.floorOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, CONSTANTS.lineConfig.floorName);
} else {
ispreSnapped.current = false;
isSnapped.current = false;
}
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.floorName]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
onlyFloorline.current.push(line.current as Types.Line);
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current]);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.floorColor, line.current, floorPlanGroupLine);
const lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
}
if (isSnapped.current) { ////////// Add this to stop the drawing mode after snapping //////////
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
onlyFloorlines.current.push(onlyFloorline.current);
onlyFloorline.current = [];
}
}
}
export default drawOnlyFloor;

View File

@ -0,0 +1,50 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../world/worldConstants';
import addRoofToScene from '../roofs/addRoofToScene';
import * as Types from "../../world/worldTypes";
import loadOnlyFloors from './loadOnlyFloors';
import addFloorToScene from './addFloorToScene';
import getRoomsFromLines from '../lines/getRoomsFromLines';
async function loadFloor(
lines: Types.RefLines,
floorGroup: Types.RefGroup,
): Promise<void> {
if (!floorGroup.current) return;
floorGroup.current.children = [];
if (lines.current.length > 2) {
const linesByLayer = lines.current.reduce((acc: { [key: number]: any[] }, pair) => {
const layer = pair[0][2];
if (!acc[layer]) acc[layer] = [];
acc[layer].push(pair);
return acc;
}, {});
for (const layer in linesByLayer) {
// Only Floor Polygons
loadOnlyFloors(floorGroup, linesByLayer, layer);
const rooms: Types.Rooms = await getRoomsFromLines({ current: linesByLayer[layer] });
rooms.forEach(({ coordinates: room, layer }) => {
const userData = room.map(point => point.uuid);
const shape = new THREE.Shape();
shape.moveTo(room[0].position.x, room[0].position.z);
room.forEach(point => shape.lineTo(point.position.x, point.position.z));
shape.closePath();
// Floor Polygons
addFloorToScene(shape, (layer - 1) * CONSTANTS.wallConfig.height, floorGroup, userData);
// Roof Polygons
addRoofToScene(shape, (layer - 1) * CONSTANTS.wallConfig.height, userData, floorGroup);
});
}
}
}
export default loadFloor;

View File

@ -0,0 +1,183 @@
import * as THREE from 'three';
import * as turf from '@turf/turf';
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
function loadOnlyFloors(
floorGroup: Types.RefGroup,
linesByLayer: any,
layer: any,
): void {
////////// Creating polygon floor based on the onlyFloorlines.current which does not add roof to it, The lines are still stored in Lines.current as well //////////
let floorsInLayer = linesByLayer[layer];
floorsInLayer = floorsInLayer.filter((line: any) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName);
const floorResult = floorsInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
pair.map((point) => ({
position: [point[0].x, point[0].z],
uuid: point[1]
}))
);
const FloorLineFeatures = floorResult.map((line: any) => turf.lineString(line.map((p: any) => p.position)));
function identifyPolygonsAndConnectedLines(FloorLineFeatures: any) {
const floorpolygons = [];
const connectedLines = [];
const unprocessedLines = [...FloorLineFeatures]; // Copy the features
while (unprocessedLines.length > 0) {
const currentLine = unprocessedLines.pop();
const coordinates = currentLine.geometry.coordinates;
// Check if the line is closed (forms a polygon)
if (
coordinates[0][0] === coordinates[coordinates.length - 1][0] &&
coordinates[0][1] === coordinates[coordinates.length - 1][1]
) {
floorpolygons.push(turf.polygon([coordinates])); // Add as a polygon
continue;
}
// Check if the line connects to another line
let connected = false;
for (let i = unprocessedLines.length - 1; i >= 0; i--) {
const otherCoordinates = unprocessedLines[i].geometry.coordinates;
// Check if lines share a start or end point
if (
coordinates[0][0] === otherCoordinates[otherCoordinates.length - 1][0] &&
coordinates[0][1] === otherCoordinates[otherCoordinates.length - 1][1]
) {
// Merge lines
const mergedCoordinates = [...otherCoordinates, ...coordinates.slice(1)];
unprocessedLines[i] = turf.lineString(mergedCoordinates);
connected = true;
break;
} else if (
coordinates[coordinates.length - 1][0] === otherCoordinates[0][0] &&
coordinates[coordinates.length - 1][1] === otherCoordinates[0][1]
) {
// Merge lines
const mergedCoordinates = [...coordinates, ...otherCoordinates.slice(1)];
unprocessedLines[i] = turf.lineString(mergedCoordinates);
connected = true;
break;
}
}
if (!connected) {
connectedLines.push(currentLine); // Add unconnected line as-is
}
}
return { floorpolygons, connectedLines };
}
const { floorpolygons, connectedLines } = identifyPolygonsAndConnectedLines(FloorLineFeatures);
function convertConnectedLinesToPolygons(connectedLines: any) {
return connectedLines.map((line: any) => {
const coordinates = line.geometry.coordinates;
// If the line has more than two points, close the polygon
if (coordinates.length > 2) {
const firstPoint = coordinates[0];
const lastPoint = coordinates[coordinates.length - 1];
// Check if already closed; if not, close it
if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
coordinates.push(firstPoint);
}
// Convert the closed line into a polygon
return turf.polygon([coordinates]);
}
// If not enough points for a polygon, return the line unchanged
return line;
});
}
const convertedConnectedPolygons = convertConnectedLinesToPolygons(connectedLines);
if (convertedConnectedPolygons.length > 0) {
const validPolygons = convertedConnectedPolygons.filter(
(polygon: any) => polygon.geometry?.type === "Polygon"
);
if (validPolygons.length > 0) {
floorpolygons.push(...validPolygons);
}
}
function convertPolygonsToOriginalFormat(floorpolygons: any, originalLines: [THREE.Vector3, string, number, string][][]) {
return floorpolygons.map((polygon: any) => {
const coordinates = polygon.geometry.coordinates[0]; // Extract the coordinates array (assume it's a single polygon)
// Map each coordinate back to its original structure
const mappedPoints = coordinates.map((coord: [number, number]) => {
const [x, z] = coord;
// Find the original point matching this coordinate
const originalPoint = originalLines.flat().find(([point]) => point.x === x && point.z === z);
if (!originalPoint) {
throw new Error(`Original point for coordinate [${x}, ${z}] not found.`);
}
return originalPoint;
});
// Create pairs of consecutive points
const pairs: typeof originalLines = [];
for (let i = 0; i < mappedPoints.length - 1; i++) {
pairs.push([mappedPoints[i], mappedPoints[i + 1]]);
}
return pairs;
});
}
const convertedFloorPolygons: Types.OnlyFloorLines = convertPolygonsToOriginalFormat(floorpolygons, floorsInLayer);
convertedFloorPolygons.forEach((floor) => {
const points: THREE.Vector3[] = [];
floor.forEach((lineSegment) => {
const startPoint = lineSegment[0][0];
points.push(new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z));
});
const lastLine = floor[floor.length - 1];
const endPoint = lastLine[1][0];
points.push(new THREE.Vector3(endPoint.x, endPoint.y, endPoint.z));
const shape = new THREE.Shape();
shape.moveTo(points[0].x, points[0].z);
points.forEach(point => shape.lineTo(point.x, point.z));
shape.closePath();
const extrudeSettings = {
depth: CONSTANTS.floorConfig.height,
bevelEnabled: false
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.floorConfig.defaultColor, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height + 0.03;
mesh.rotateX(Math.PI / 2);
mesh.name = `Only_Floor_Line_${floor[0][0][2]}`;
mesh.userData = floor;
floorGroup?.current?.add(mesh);
});
}
export default loadOnlyFloors;

View File

@ -0,0 +1,24 @@
import * as Types from "../../world/worldTypes";
function updateFloorLines(
onlyFloorlines: Types.RefOnlyFloorLines,
DragedPoint: Types.Mesh | { uuid: string, position: Types.Vector3 }
): void {
////////// Update onlyFloorlines.current if it contains the dragged point //////////
onlyFloorlines.current.forEach((floorline) => {
floorline.forEach((line) => {
line.forEach((point) => {
const [position, uuid] = point;
if (uuid === DragedPoint.uuid) {
position.x = DragedPoint.position.x;
position.y = 0.01;
position.z = DragedPoint.position.z;
}
});
});
});
}
export default updateFloorLines;

View File

@ -0,0 +1,89 @@
import { toast } from 'react-toastify';
import RemoveConnectedLines from '../lines/removeConnectedLines';
import * as Types from '../../world/worldTypes';
import { Socket } from 'socket.io-client';
import { deleteLayer } from '../../../../services/factoryBuilder/lines/deleteLayerApi';
async function DeleteLayer(
removedLayer: Types.Number,
lines: Types.RefLines,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroupPoint: Types.RefGroup,
onlyFloorlines: Types.RefOnlyFloorLines,
floorGroup: Types.RefGroup,
setDeletedLines: any,
setRemovedLayer: Types.setRemoveLayerSetState,
socket: Socket<any>
): Promise<void> {
////////// Remove the Lines from the lines.current based on the removed layer and rearrange the layer number that are higher than the removed layer //////////
const removedLines: Types.Lines = lines.current.filter(line => line[0][2] === removedLayer);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// await deleteLayer(organization, removedLayer);
//SOCKET
const data = {
organization: organization,
layer: removedLayer,
socketId: socket.id
}
socket.emit('v1:Line:delete:layer', data);
////////// Remove Points and lines from the removed layer //////////
removedLines.forEach((line) => {
line.forEach((removedPoint) => {
RemoveConnectedLines(removedPoint[1], floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, lines);
});
});
////////// Update the remaining lines layer values in the userData and in lines.current //////////
let remaining = lines.current.filter(line => line[0][2] !== removedLayer);
let updatedLines: Types.Lines = [];
remaining.forEach(line => {
let newLines: Types.Line = [...line];
if (newLines[0][2] > removedLayer) {
newLines[0][2] -= 1;
newLines[1][2] -= 1;
}
const matchingLine = floorPlanGroupLine.current.children.find(l => l.userData.linePoints[0][1] === line[0][1] && l.userData.linePoints[1][1] === line[1][1]);
if (matchingLine) {
const updatedUserData = matchingLine.userData;
updatedUserData.linePoints[0][2] = newLines[0][2];
updatedUserData.linePoints[1][2] = newLines[1][2];
}
updatedLines.push(newLines);
});
lines.current = updatedLines;
localStorage.setItem("Lines", JSON.stringify(lines.current));
////////// Also remove OnlyFloorLines and update it in localstorage //////////
onlyFloorlines.current = onlyFloorlines.current.filter((floor) => {
return floor[0][0][2] !== removedLayer;
});
const meshToRemove: any = floorGroup.current?.children.find((mesh) =>
mesh.name === `Only_Floor_Line_${removedLayer}`
);
if (meshToRemove) {
(<any>meshToRemove.material).dispose();
(<any>meshToRemove.geometry).dispose();
floorGroup.current?.remove(meshToRemove);
}
toast.success("Layer Removed!");
setRemovedLayer(null);
}
export default DeleteLayer;

View File

@ -0,0 +1,35 @@
import * as Types from "../../world/worldTypes";
function Layer2DVisibility(
activeLayer: Types.Number,
floorPlanGroup: Types.RefGroup,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroupPoint: Types.RefGroup,
currentLayerPoint: Types.RefMeshArray,
dragPointControls: Types.RefDragControl
): void {
if (floorPlanGroup.current && dragPointControls.current) {
currentLayerPoint.current = [];
floorPlanGroupLine.current.children.forEach((line) => {
const linePoints = line.userData.linePoints;
const point1 = floorPlanGroupPoint.current.getObjectByProperty('uuid', linePoints[0][1]) as Types.Mesh;
const point2 = floorPlanGroupPoint.current.getObjectByProperty('uuid', linePoints[1][1]) as Types.Mesh;
if (linePoints[0][2] !== activeLayer && linePoints[1][2] !== activeLayer) {
point1.visible = false;
point2.visible = false;
line.visible = false;
} else {
point1.visible = true;
point2.visible = true;
line.visible = true;
currentLayerPoint.current.push(point1, point2);
}
});
dragPointControls.current!.objects = currentLayerPoint.current;
}
}
export default Layer2DVisibility;

View File

@ -0,0 +1,24 @@
import * as THREE from "three";
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
function addLineToScene(
start: Types.Vector3,
end: Types.Vector3,
colour: Types.Color,
userData: Types.UserData,
floorPlanGroupLine: Types.RefGroup
): void {
////////// A function that creates and adds lines based on the start, end, and colour from the params, Also adds the userData in the mesh userData //////////
const path = new THREE.CatmullRomCurve3([start, end]);
const geometry = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
const material = new THREE.MeshBasicMaterial({ color: colour });
const mesh = new THREE.Mesh(geometry, material);
floorPlanGroupLine.current.add(mesh);
mesh.userData.linePoints = userData;
}
export default addLineToScene;

View File

@ -0,0 +1,98 @@
import * as THREE from "three";
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
function createAndMoveReferenceLine(
point: Types.Vector3,
cursorPosition: Types.Vector3,
isSnapped: Types.RefBoolean,
ispreSnapped: Types.RefBoolean,
line: Types.RefLine,
setRefTextUpdate: Types.NumberIncrementState,
floorPlanGroup: Types.RefGroup,
ReferenceLineMesh: Types.RefMesh,
LineCreated: Types.RefBoolean,
Tube: Types.RefTubeGeometry,
anglesnappedPoint: Types.RefVector3,
isAngleSnapped: Types.RefBoolean
): void {
////////// Creating new and maintaining the old reference line and also snap the reference line based on its angle //////////
const startPoint = point;
const dx = cursorPosition.x - startPoint.x;
const dz = cursorPosition.z - startPoint.z;
let angle = Math.atan2(dz, dx);
angle = (angle * 180) / Math.PI;
angle = (angle + 360) % 360;
const snapAngles = [0, 90, 180, 270, 360];
const snapThreshold = 2.5;
const closestSnapAngle = snapAngles.reduce((prev, curr) =>
Math.abs(curr - angle) < Math.abs(prev - angle) ? curr : prev
);
if (!isSnapped.current && !ispreSnapped.current && line.current.length > 0) {
if (Math.abs(closestSnapAngle - angle) <= snapThreshold) {
const snappedAngleRad = (closestSnapAngle * Math.PI) / 180;
const distance = Math.sqrt(dx * dx + dz * dz);
const snappedX = startPoint.x + distance * Math.cos(snappedAngleRad);
const snappedZ = startPoint.z + distance * Math.sin(snappedAngleRad);
if (
cursorPosition.distanceTo(
new THREE.Vector3(snappedX, 0.01, snappedZ)
) < 2
) {
cursorPosition.set(snappedX, 0.01, snappedZ);
isAngleSnapped.current = true;
anglesnappedPoint.current = new THREE.Vector3(
snappedX,
0.01,
snappedZ
);
} else {
isAngleSnapped.current = false;
anglesnappedPoint.current = null;
}
} else {
isAngleSnapped.current = false;
anglesnappedPoint.current = null;
}
} else {
isAngleSnapped.current = false;
anglesnappedPoint.current = null;
}
if (!LineCreated.current) {
setRefTextUpdate((prevUpdate) => prevUpdate - 1);
const path = new THREE.LineCurve3(startPoint, cursorPosition);
Tube.current = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
const material = new THREE.MeshBasicMaterial({ color: CONSTANTS.lineConfig.helperColor });
ReferenceLineMesh.current = new THREE.Mesh(Tube.current, material);
ReferenceLineMesh.current.name = CONSTANTS.lineConfig.referenceName;
ReferenceLineMesh.current.userData = {
linePoints: { startPoint, cursorPosition },
};
floorPlanGroup.current?.add(ReferenceLineMesh.current);
LineCreated.current = true;
} else {
if (ReferenceLineMesh.current) {
const path = new THREE.LineCurve3(startPoint, new THREE.Vector3(cursorPosition.x, 0.01, cursorPosition.z));
Tube.current = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
if (ReferenceLineMesh.current) {
ReferenceLineMesh.current.userData = {
linePoints: { startPoint, cursorPosition },
};
ReferenceLineMesh.current.geometry.dispose();
ReferenceLineMesh.current.geometry = Tube.current;
}
}
}
}
export default createAndMoveReferenceLine;

View File

@ -0,0 +1,88 @@
import { Socket } from "socket.io-client";
import { deleteLineApi } from "../../../../services/factoryBuilder/lines/deleteLineApi";
import * as Types from "../../world/worldTypes";
import { toast } from 'react-toastify';
function deleteLine(
hoveredDeletableLine: Types.RefMesh,
onlyFloorlines: Types.RefOnlyFloorLines,
lines: Types.RefLines,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroupPoint: Types.RefGroup,
setDeletedLines: any,
socket: Socket<any>
): void {
////////// Deleting a line and the points if they are not connected to any other line //////////
if (!hoveredDeletableLine.current) {
return;
}
const linePoints = hoveredDeletableLine.current.userData.linePoints;
const connectedpoints = [linePoints[0][1], linePoints[1][1]];
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// deleteLineApi(
// organization,
// [
// { "uuid": linePoints[0][1] },
// { "uuid": linePoints[1][1] }
// ]
// )
//SOCKET
const data = {
organization: organization,
line: [
{ "uuid": linePoints[0][1] },
{ "uuid": linePoints[1][1] }
],
socketId: socket.id
}
socket.emit('v1:Line:delete', data);
onlyFloorlines.current = onlyFloorlines.current.map(floorline =>
floorline.filter(line => line[0][1] !== connectedpoints[0] && line[1][1] !== connectedpoints[1])
).filter(floorline => floorline.length > 0);
lines.current = lines.current.filter(item => item !== linePoints);
(<any>hoveredDeletableLine.current.material).dispose();
(<any>hoveredDeletableLine.current.geometry).dispose();
floorPlanGroupLine.current.remove(hoveredDeletableLine.current);
setDeletedLines([linePoints]);
connectedpoints.forEach((pointUUID) => {
let isConnected = false;
floorPlanGroupLine.current.children.forEach((line) => {
const linePoints = line.userData.linePoints;
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === pointUUID || uuid2 === pointUUID) {
isConnected = true;
}
});
if (!isConnected) {
floorPlanGroupPoint.current.children.forEach((point: any) => {
if (point.uuid === pointUUID) {
(<any>point.material).dispose();
(<any>point.geometry).dispose();
floorPlanGroupPoint.current.remove(point);
}
});
}
});
toast.success("Line Removed!");
}
export default deleteLine;

View File

@ -0,0 +1,167 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../world/worldConstants';
import addPointToScene from '../points/addPointToScene';
import addLineToScene from './addLineToScene';
import splitLine from './splitLine';
import removeReferenceLine from './removeReferenceLine';
import getClosestIntersection from './getClosestIntersection';
import * as Types from "../../world/worldTypes";
import arrayLineToObject from './lineConvertions/arrayLineToObject';
import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
import { Socket } from 'socket.io-client';
async function drawWall(
raycaster: THREE.Raycaster,
plane: Types.RefMesh,
floorPlanGroupPoint: Types.RefGroup,
snappedPoint: Types.RefVector3,
isSnapped: Types.RefBoolean,
isSnappedUUID: Types.RefString,
line: Types.RefLine,
ispreSnapped: Types.RefBoolean,
anglesnappedPoint: Types.RefVector3,
isAngleSnapped: Types.RefBoolean,
lines: Types.RefLines,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroup: Types.RefGroup,
ReferenceLineMesh: Types.RefMesh,
LineCreated: Types.RefBoolean,
currentLayerPoint: Types.RefMeshArray,
dragPointControls: Types.RefDragControl,
setNewLines: any,
setDeletedLines: any,
activeLayer: Types.Number,
socket: Socket<any>
): Promise<void> {
////////// Creating lines Based on the positions clicked //////////
////////// Allows the user lines that represents walls and roof, floor if forms a polygon //////////
if (!plane.current) return
let intersects = raycaster.intersectObject(plane.current, true);
let intersectsLines = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
let intersectsPoint = raycaster.intersectObjects(floorPlanGroupPoint.current.children, true);
const VisibleintersectsPoint = intersectsPoint.find(intersect => intersect.object.visible);
const visibleIntersect = intersectsLines.find(intersect => intersect.object.visible && intersect.object.name !== CONSTANTS.lineConfig.referenceName && intersect.object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName);
if ((intersectsPoint.length === 0 || VisibleintersectsPoint === undefined) && intersectsLines.length > 0 && !isSnapped.current && !ispreSnapped.current) {
////////// Clicked on a preexisting Line //////////
if (visibleIntersect && intersects) {
let IntersectsPoint = new THREE.Vector3(intersects[0].point.x, 0.01, intersects[0].point.z);
if (isAngleSnapped.current && anglesnappedPoint.current) {
IntersectsPoint = anglesnappedPoint.current;
}
if (visibleIntersect.object instanceof THREE.Mesh) {
const ThroughPoint = (visibleIntersect.object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
let intersectionPoint = getClosestIntersection(ThroughPoint, IntersectsPoint);
if (intersectionPoint) {
const newLines = splitLine(visibleIntersect, intersectionPoint, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lines, setDeletedLines, floorPlanGroupLine, socket, CONSTANTS.pointConfig.wallOuterColor, CONSTANTS.lineConfig.wallColor, CONSTANTS.lineConfig.wallName);
setNewLines([newLines[0], newLines[1]]);
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.wallName,]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([newLines[0], newLines[1], line.current]);
lines.current.push(line.current as Types.Line);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.wallColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
}
return;
}
}
}
}
if (intersects && intersects.length > 0) {
////////// Clicked on a emply place or a point //////////
let intersectionPoint = intersects[0].point;
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
intersectionPoint = anglesnappedPoint.current;
}
if (isSnapped.current && line.current.length > 0 && snappedPoint.current) {
intersectionPoint = snappedPoint.current;
}
if (ispreSnapped.current && snappedPoint.current) {
intersectionPoint = snappedPoint.current;
}
if (!isSnapped.current && !ispreSnapped.current) {
addPointToScene(intersectionPoint, CONSTANTS.pointConfig.wallOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, CONSTANTS.lineConfig.wallName);
} else {
ispreSnapped.current = false;
isSnapped.current = false;
}
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.wallName,]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current])
lines.current.push(line.current as Types.Line);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.wallColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
}
if (isSnapped.current) {
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
}
}
export default drawWall;

View File

@ -0,0 +1,26 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function getClosestIntersection(
intersects: Types.Vector3Array,
point: Types.Vector3
): Types.Vector3 | null {
////////// A function that finds which point is closest from the intersects points that is given, Used in finding which point in a line is closest when clicked on a line during drawing //////////
let closestNewPoint: THREE.Vector3 | null = null;
let minDistance = Infinity;
for (const intersect of intersects) {
const distance = point.distanceTo(intersect);
if (distance < minDistance) {
minDistance = distance;
closestNewPoint = intersect;
}
}
return closestNewPoint;
}
export default getClosestIntersection;

View File

@ -0,0 +1,86 @@
import * as THREE from 'three';
import * as turf from '@turf/turf';
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
async function getRoomsFromLines(lines: Types.RefLines) {
const rooms: Types.Rooms = [];
if (lines.current.length > 2) {
const linesByLayer = lines.current.reduce((acc: { [key: number]: any[] }, pair) => {
const layer = pair[0][2];
if (!acc[layer]) acc[layer] = [];
acc[layer].push(pair);
return acc;
}, {});
////////// Use turf.polygonize to create polygons from the line points //////////
for (const layer in linesByLayer) {
let linesInLayer = linesByLayer[layer];
linesInLayer = linesInLayer.filter(line => line[0][3] && line[1][3] === CONSTANTS.lineConfig.wallName);
const result = linesInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
pair.map((point) => ({
position: [point[0].x, point[0].z],
uuid: point[1]
}))
);
const lineFeatures = result.map(line => turf.lineString(line.map(p => p.position)));
const polygons = turf.polygonize(turf.featureCollection(lineFeatures));
let union: any[] = [];
polygons.features.forEach((feature) => {
union.push(feature);
});
if (union.length > 1) {
const unionResult = turf.union(turf.featureCollection(union));
if (unionResult?.geometry.type === "MultiPolygon") {
unionResult?.geometry.coordinates.forEach((poly) => {
const Coordinates = poly[0].map(([x, z]) => {
const matchingPoint = result.flat().find(r =>
r.position[0].toFixed(10) === x.toFixed(10) &&
r.position[1].toFixed(10) === z.toFixed(10)
);
return {
position: new THREE.Vector3(x, 0, z),
uuid: matchingPoint ? matchingPoint.uuid : ''
};
});
rooms.push({ coordinates: Coordinates.reverse(), layer: parseInt(layer) });
});
} else if (unionResult?.geometry.type === "Polygon") {
const Coordinates = unionResult?.geometry.coordinates[0].map(([x, z]) => {
const matchingPoint = result.flat().find(r =>
r.position[0].toFixed(10) === x.toFixed(10) &&
r.position[1].toFixed(10) === z.toFixed(10)
);
return {
position: new THREE.Vector3(x, 0, z),
uuid: matchingPoint ? matchingPoint.uuid : ''
};
});
rooms.push({ coordinates: Coordinates.reverse(), layer: parseInt(layer) });
}
} else if (union.length === 1) {
const Coordinates = union[0].geometry.coordinates[0].map(([x, z]: [number, number]) => {
const matchingPoint = result.flat().find(r =>
r.position[0].toFixed(10) === x.toFixed(10) &&
r.position[1].toFixed(10) === z.toFixed(10)
);
return {
position: new THREE.Vector3(x, 0, z),
uuid: matchingPoint ? matchingPoint.uuid : ''
};
});
rooms.push({ coordinates: Coordinates, layer: parseInt(layer) });
}
}
}
return rooms;
}
export default getRoomsFromLines;

View File

@ -0,0 +1,24 @@
import * as Types from "../../../world/worldTypes";
export default function arrayLineToObject(array: Types.Line) {
if (!Array.isArray(array)) {
return {};
}
// Extract common properties from the first point
const commonLayer = array[0][2];
const commonType = array[0][3];
// Map points into a structured format
const line = array.map(([position, uuid]) => ({
position,
uuid,
}));
// Create the final structured object
return {
layer: commonLayer,
type: commonType,
line,
};
}

View File

@ -0,0 +1,30 @@
import * as Types from "../../../world/worldTypes";
export default function arrayLinesToObject(array: Array<Types.Line>) {
if (!Array.isArray(array)) {
return [];
}
return array.map((lineArray) => {
if (!Array.isArray(lineArray)) {
return null;
}
// Extract common properties from the first point
const commonLayer = lineArray[0][2];
const commonType = lineArray[0][3];
// Map points into a structured format
const line = lineArray.map(([position, uuid]) => ({
position,
uuid,
}));
// Create the final structured object
return {
layer: commonLayer,
type: commonType,
line,
};
}).filter((item) => item !== null); // Filter out invalid entries
}

View File

@ -0,0 +1,13 @@
import * as THREE from 'three';
export default function objectLineToArray(structuredObject: any) {
if (!structuredObject || !structuredObject.line) {
return [];
}
// Destructure common properties
const { layer, type, line } = structuredObject;
// Map points back to the original array format
return line.map(({ position, uuid }: any) => [new THREE.Vector3(position.x, position.y, position.z), uuid, layer, type]);
}

View File

@ -0,0 +1,20 @@
import * as THREE from 'three';
export default function objectLinesToArray(structuredObjects: any): any {
if (!Array.isArray(structuredObjects)) {
return [];
}
return structuredObjects.map((structuredObject) => {
if (!structuredObject || !structuredObject.line) {
return [];
}
const { layer, type, line } = structuredObject;
return line.map(({ position, uuid }: any) => {
const vector = new THREE.Vector3(position.x, position.y, position.z);
return [vector, uuid, layer, type];
});
});
}

View File

@ -0,0 +1,66 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function RemoveConnectedLines(
DeletedPointUUID: Types.String,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroupPoint: Types.RefGroup,
setDeletedLines: any,
lines: Types.RefLines,
): void {
////////// Check if any and how many lines are connected to the deleted point //////////
const removableLines: THREE.Mesh[] = [];
const connectedpoints: string[] = [];
const removedLinePoints: [number, string, number][][] = []; // Array to hold linePoints of removed lines
floorPlanGroupLine.current.children.forEach((line) => {
const linePoints = line.userData.linePoints as [number, string, number][];
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === DeletedPointUUID || uuid2 === DeletedPointUUID) {
connectedpoints.push(uuid1 === DeletedPointUUID ? uuid2 : uuid1);
removableLines.push(line as THREE.Mesh);
removedLinePoints.push(linePoints);
}
});
if (removableLines.length > 0) {
removableLines.forEach((line) => {
lines.current = lines.current.filter(item => item !== line.userData.linePoints);
(<any>line.material).dispose();
(<any>line.geometry).dispose();
floorPlanGroupLine.current.remove(line);
});
}
setDeletedLines(removedLinePoints)
////////// Check and Remove point that are no longer connected to any lines //////////
connectedpoints.forEach((pointUUID) => {
let isConnected = false;
floorPlanGroupLine.current.children.forEach((line) => {
const linePoints = line.userData.linePoints as [number, string, number][];
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === pointUUID || uuid2 === pointUUID) {
isConnected = true;
}
});
if (!isConnected) {
floorPlanGroupPoint.current.children.forEach((point: any) => {
if (point.uuid === pointUUID) {
(<any>point.material).dispose();
(<any>point.geometry).dispose();
floorPlanGroupPoint.current.remove(point);
}
});
}
});
}
export default RemoveConnectedLines;

View File

@ -0,0 +1,22 @@
import * as Types from "../../world/worldTypes";
function removeReferenceLine(
floorPlanGroup: Types.RefGroup,
ReferenceLineMesh: Types.RefMesh,
LineCreated: Types.RefBoolean,
line: Types.RefLine
): void {
////////// Removes Dangling reference line if the draw mode is ended or any other case //////////
line.current = [];
if (ReferenceLineMesh.current) {
(<any>ReferenceLineMesh.current.material).dispose();
(<any>ReferenceLineMesh.current.geometry).dispose();
floorPlanGroup.current.remove(ReferenceLineMesh.current);
LineCreated.current = false;
ReferenceLineMesh.current = undefined;
}
}
export default removeReferenceLine;

View File

@ -0,0 +1,124 @@
import * as THREE from 'three';
import addLineToScene from './addLineToScene';
import addPointToScene from '../points/addPointToScene';
import * as Types from "../../world/worldTypes";
import { deleteLineApi } from '../../../../services/factoryBuilder/lines/deleteLineApi';
import arrayLineToObject from '../lines/lineConvertions/arrayLineToObject';
import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
import { Socket } from 'socket.io-client';
function splitLine(
visibleIntersect: Types.IntersectionEvent,
intersectionPoint: Types.Vector3,
currentLayerPoint: Types.RefMeshArray,
floorPlanGroupPoint: Types.RefGroup,
dragPointControls: Types.RefDragControl,
isSnappedUUID: Types.RefString,
lines: Types.RefLines,
setDeletedLines: any,
floorPlanGroupLine: { current: THREE.Group },
socket: Socket<any>,
pointColor: Types.String,
lineColor: Types.String,
lineType: Types.String,
): [Types.Line, Types.Line] {
////////// Removing the clicked line and splitting it with the clicked position adding a new point and two new lines //////////
((visibleIntersect.object as any).material).dispose();
((visibleIntersect.object as any).geometry).dispose();
floorPlanGroupLine.current.remove(visibleIntersect.object);
setDeletedLines([visibleIntersect.object.userData.linePoints]);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// deleteLineApi(
// organization,
// [
// { "uuid": visibleIntersect.object.userData.linePoints[0][1] },
// { "uuid": visibleIntersect.object.userData.linePoints[1][1] }
// ]
// )
//SOCKET
const data = {
organization: organization,
line: [
{ "uuid": visibleIntersect.object.userData.linePoints[0][1] },
{ "uuid": visibleIntersect.object.userData.linePoints[1][1] }
],
socketId: socket.id
}
socket.emit('v1:Line:delete', data);
const point = addPointToScene(intersectionPoint, pointColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lineType);
const oldLinePoints = visibleIntersect.object.userData.linePoints;
lines.current = lines.current.filter(item => item !== oldLinePoints);
const clickedPoint: Types.Point = [
new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z),
point.uuid,
oldLinePoints[0][2],
lineType
];
const start = oldLinePoints[0];
const end = oldLinePoints[1];
const newLine1: Types.Line = [start, clickedPoint];
const newLine2: Types.Line = [clickedPoint, end];
const line1 = arrayLineToObject(newLine1);
const line2 = arrayLineToObject(newLine2);
//REST
// setLine(organization, line1.layer!, line1.line!, line1.type!);
//SOCKET
const input1 = {
organization: organization,
layer: line1.layer,
line: line1.line,
type: line1.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input1);
//REST
// setLine(organization, line2.layer!, line2.line!, line2.type!);
//SOCKET
const input2 = {
organization: organization,
layer: line2.layer,
line: line2.line,
type: line2.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input2);
lines.current.push(newLine1, newLine2);
addLineToScene(newLine1[0][0], newLine1[1][0], lineColor, newLine1, floorPlanGroupLine);
addLineToScene(newLine2[0][0], newLine2[1][0], lineColor, newLine2, floorPlanGroupLine);
return [newLine1, newLine2];
}
export default splitLine;

View File

@ -0,0 +1,24 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
import * as CONSTANTS from '../../world/worldConstants';
function updateLines(
floorPlanGroupLine: Types.RefGroup,
affectedLines: Types.NumberArray
): void {
////////// Updating the positions for the affected lines only based on the updated positions //////////
affectedLines.forEach((lineIndex) => {
const mesh = floorPlanGroupLine.current.children[lineIndex] as Types.Mesh;
const linePoints = mesh.userData.linePoints as Types.Line;
if (linePoints) {
const newPositions = linePoints.map(([pos]) => pos);
const newPath = new THREE.CatmullRomCurve3(newPositions);
mesh.geometry.dispose();
mesh.geometry = new THREE.TubeGeometry(newPath, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
}
});
}
export default updateLines;

View File

@ -0,0 +1,32 @@
import * as Types from "../../world/worldTypes";
function updateLinesPositions(
DragedPoint: Types.Mesh | { uuid: string, position: Types.Vector3 },
lines: Types.RefLines
): Types.NumberArray {
////////// Updating the lines position based on the dragged point's position //////////
const objectUUID = DragedPoint.uuid;
const affectedLines: Types.NumberArray = [];
lines.current.forEach((line, index) => {
let lineUpdated = false;
line.forEach((point) => {
const [position, uuid] = point;
if (uuid === objectUUID) {
position.x = DragedPoint.position.x;
position.y = 0.01;
position.z = DragedPoint.position.z;
lineUpdated = true;
}
});
if (lineUpdated) {
affectedLines.push(index);
}
});
return affectedLines;
}
export default updateLinesPositions;

View File

@ -0,0 +1,18 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function vectorizeLinesCurrent(
lines: Types.Lines
): Types.Lines {
////////// Storing a vector3 array in localstorage makes the prototype functions go puff. This function brings back the prototype functions by creating it again //////////
return lines.map((line) => {
const p1: Types.Point = [new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z), line[0][1], line[0][2], line[0][3],];
const p2: Types.Point = [new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z), line[1][1], line[0][2], line[1][3],];
return [p1, p2];
});
}
export default vectorizeLinesCurrent;

View File

@ -0,0 +1,54 @@
import * as THREE from 'three';
import updateReferencePolesheight from './updateReferencePolesheight';
import * as Types from "../../world/worldTypes";
function addAndUpdateReferencePillar(
raycaster: THREE.Raycaster,
floorGroup: Types.RefGroup,
referencePole: Types.RefMesh
): void {
////////// Find Pillars position and scale based on the pointer interaction //////////
let Roofs = raycaster.intersectObjects(floorGroup.current.children, true);
const intersected = Roofs.find(intersect => intersect.object.name.includes("Roof") || intersect.object.name.includes("Floor"));
if (intersected) {
const intersectionPoint = intersected.point;
raycaster.ray.origin.copy(intersectionPoint);
raycaster.ray.direction.set(0, -1, 0);
const belowIntersections = raycaster.intersectObjects(floorGroup.current.children, true);
const validIntersections = belowIntersections.filter(intersect => intersect.object.name.includes("Floor"));
let distance: Types.Number;
if (validIntersections.length > 1) {
let valid = validIntersections.find(intersectedBelow => intersected.point.distanceTo(intersectedBelow.point) > 3);
if (valid) {
updateReferencePolesheight(intersectionPoint, valid.distance, referencePole, floorGroup);
} else {
const belowPoint = new THREE.Vector3(intersectionPoint.x, 0, intersectionPoint.z);
distance = intersected.point.distanceTo(belowPoint);
if (distance > 3) {
updateReferencePolesheight(intersectionPoint, distance, referencePole, floorGroup);
}
}
} else {
const belowPoint = new THREE.Vector3(intersectionPoint.x, 0, intersectionPoint.z);
distance = intersected.point.distanceTo(belowPoint);
if (distance > 3) {
updateReferencePolesheight(intersectionPoint, distance, referencePole, floorGroup);
}
}
} else {
if (referencePole.current) {
(<any>referencePole.current.material).dispose();
(<any>referencePole.current.geometry).dispose();
floorGroup.current.remove(referencePole.current);
referencePole.current = null;
}
}
}
export default addAndUpdateReferencePillar;

View File

@ -0,0 +1,24 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
function addPillar(
referencePole: Types.RefMesh,
floorGroup: Types.RefGroup
): void {
////////// Add Pillars to the scene based on the reference. current poles position and scale //////////
if (referencePole.current) {
let pole: THREE.Mesh;
const geometry = referencePole.current.userData.geometry.clone();
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.columnConfig.defaultColor });
pole = new THREE.Mesh(geometry, material);
pole.rotateX(Math.PI / 2);
pole.name = "Pole";
pole.position.set(referencePole.current.userData.position.x, referencePole.current.userData.position.y, referencePole.current.userData.position.z);
floorGroup.current.add(pole);
}
}
export default addPillar;

View File

@ -0,0 +1,34 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function DeletableHoveredPillar(
state: Types.ThreeState,
floorGroup: Types.RefGroup,
hoveredDeletablePillar: Types.RefMesh
): void {
////////// Altering the color of the hovered Pillar during the Deletion time //////////
const intersects = state.raycaster.intersectObjects(floorGroup.current.children, true);
const poleIntersect = intersects.find(intersect => intersect.object.name === "Pole");
if (poleIntersect) {
if (poleIntersect.object.name !== "Pole") {
return;
}
if (hoveredDeletablePillar.current) {
(hoveredDeletablePillar.current.material as THREE.MeshStandardMaterial).emissive = new THREE.Color("black");
hoveredDeletablePillar.current = undefined;
}
hoveredDeletablePillar.current = poleIntersect.object as THREE.Mesh; // Type assertion
(hoveredDeletablePillar.current.material as THREE.MeshStandardMaterial).emissive = new THREE.Color("red");
} else {
if (hoveredDeletablePillar.current) {
(hoveredDeletablePillar.current.material as THREE.MeshStandardMaterial).emissive = new THREE.Color("black");
hoveredDeletablePillar.current = undefined;
}
}
}
export default DeletableHoveredPillar;

View File

@ -0,0 +1,21 @@
import { toast } from 'react-toastify';
import * as Types from "../../world/worldTypes";
function DeletePillar(
hoveredDeletablePillar: Types.RefMesh,
floorGroup: Types.RefGroup
): void {
////////// Deleting the hovered Pillar from the itemsGroup //////////
if (hoveredDeletablePillar.current) {
(<any>hoveredDeletablePillar.current.material).dispose();
(<any>hoveredDeletablePillar.current.geometry).dispose();
floorGroup.current.remove(hoveredDeletablePillar.current);
toast.success("Pillar Removed!");
hoveredDeletablePillar.current = undefined;
}
}
export default DeletePillar;

View File

@ -0,0 +1,40 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function updateReferencePolesheight(
intersectionPoint: Types.Vector3,
distance: Types.Number,
referencePole: Types.RefMesh,
floorGroup: Types.RefGroup
): void {
////////// Add a Reference Pillar and update its position and scale based on the pointer interaction //////////
if (referencePole.current) {
(<any>referencePole.current.material).dispose();
(<any>referencePole.current.geometry).dispose();
floorGroup.current.remove(referencePole.current);
referencePole.current.geometry.dispose();
}
const shape = new THREE.Shape();
shape.moveTo(0.5, 0);
shape.absarc(0, 0, 0.5, 0, 2 * Math.PI, false);
const extrudeSettings = {
depth: distance,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshBasicMaterial({ color: "green", transparent: true, opacity: 0.5 });
referencePole.current = new THREE.Mesh(geometry, material);
referencePole.current.rotateX(Math.PI / 2);
referencePole.current.position.set(intersectionPoint.x, intersectionPoint.y - 0.01, intersectionPoint.z);
referencePole.current.userData = { geometry: geometry, distance: distance, position: { x: intersectionPoint.x, y: intersectionPoint.y - 0.01, z: intersectionPoint.z } };
floorGroup.current.add(referencePole.current);
}
export default updateReferencePolesheight;

View File

@ -0,0 +1,65 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
function addPointToScene(
position: Types.Vector3,
colour: Types.Color,
currentLayerPoint: Types.RefMeshArray,
floorPlanGroupPoint: Types.RefGroup,
dragPointControls: Types.RefDragControl | undefined,
uuid: Types.RefString | undefined,
Type: Types.String
): Types.Mesh {
////////// A function that creates and adds a cube (point) with an outline based on the position and colour given as params, It also updates the drag controls objects and sets the box uuid in uuid.current //////////
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
const material = new THREE.ShaderMaterial({
uniforms: {
uColor: { value: new THREE.Color(colour) }, // Blue color for the border
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 uColor;
uniform vec3 uInnerColor;
void main() {
// Define the size of the white square as a proportion of the face
float borderThickness = 0.2; // Adjust this value for border thickness
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
} else {
gl_FragColor = vec4(uColor, 1.0); // Blue border
}
}
`,
});
const point = new THREE.Mesh(geometry, material);
point.name = "point";
point.userData = { type: Type, color: colour };
point.position.set(position.x, 0.01, position.z);
currentLayerPoint.current.push(point);
floorPlanGroupPoint.current.add(point);
if (uuid) {
uuid.current = point.uuid;
}
if (dragPointControls) {
dragPointControls.current!.objects = currentLayerPoint.current;
}
return point;
}
export default addPointToScene;

View File

@ -0,0 +1,57 @@
import * as Types from "../../world/worldTypes";
import { toast } from 'react-toastify';
import RemoveConnectedLines from "../lines/removeConnectedLines";
import { deletePointApi } from "../../../../services/factoryBuilder/lines/deletePointApi";
import { Socket } from "socket.io-client";
function deletePoint(
hoveredDeletablePoint: Types.RefMesh,
onlyFloorlines: Types.RefOnlyFloorLines,
floorPlanGroupPoint: Types.RefGroup,
floorPlanGroupLine: Types.RefGroup,
lines: Types.RefLines,
setDeletedLines: any,
socket: Socket<any>
): void {
////////// Deleting a Point and the lines that are connected to it //////////
if (!hoveredDeletablePoint.current) {
return;
}
(<any>hoveredDeletablePoint.current.material).dispose();
(<any>hoveredDeletablePoint.current.geometry).dispose();
floorPlanGroupPoint.current.remove(hoveredDeletablePoint.current);
const DeletedPointUUID = hoveredDeletablePoint.current.uuid;
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// deletePointApi(organization, DeletedPointUUID);
//SOCKET
const data = {
organization: organization,
uuid: DeletedPointUUID,
socketId: socket.id
}
socket.emit('v1:Line:delete:point', data);
////////// Update onlyFloorlines.current to remove references to the deleted point //////////
onlyFloorlines.current = onlyFloorlines.current.map(floorline =>
floorline.filter(line => line[0][1] !== DeletedPointUUID && line[1][1] !== DeletedPointUUID)
).filter(floorline => floorline.length > 0);
RemoveConnectedLines(DeletedPointUUID, floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, lines);
toast.success("Point Removed!");
}
export default deletePoint;

View File

@ -0,0 +1,44 @@
import * as THREE from "three";
import * as Types from "../../world/worldTypes";
import * as CONSTANTS from '../../world/worldConstants';
import updateLinesPositions from "../lines/updateLinesPositions";
import updateLines from "../lines/updateLines";
import updateDistanceText from "../../../3d-ui/functions/updateDistanceText";
import updateFloorLines from "../floors/updateFloorLines";
function DragPoint(
event: Types.IntersectionEvent,
floorPlanGroupPoint: Types.RefGroup,
floorPlanGroupLine: Types.RefGroup,
scene: THREE.Scene,
lines: Types.RefLines,
onlyFloorlines: Types.RefOnlyFloorLines
): void {
////////// Calling the line updation of the affected lines and Snapping of the point during the drag //////////
const snapThreshold = CONSTANTS.pointConfig.snappingThreshold;
const DragedPoint = event.object as Types.Mesh;
floorPlanGroupPoint.current.children.forEach((point) => {
let canSnap =
((DragedPoint.userData.type === CONSTANTS.lineConfig.wallName) && (point.userData.type === CONSTANTS.lineConfig.wallName || point.userData.type === CONSTANTS.lineConfig.floorName)) ||
((DragedPoint.userData.type === CONSTANTS.lineConfig.floorName) && (point.userData.type === CONSTANTS.lineConfig.wallName || point.userData.type === CONSTANTS.lineConfig.floorName)) ||
((DragedPoint.userData.type === CONSTANTS.lineConfig.aisleName) && point.userData.type === CONSTANTS.lineConfig.aisleName);
if (canSnap && point.uuid !== DragedPoint.uuid && point.visible) {
const distance = DragedPoint.position.distanceTo(point.position);
if (distance < snapThreshold) {
DragedPoint.position.copy(point.position);
}
}
});
const affectedLines = updateLinesPositions(DragedPoint, lines);
updateLines(floorPlanGroupLine, affectedLines);
updateDistanceText(scene, floorPlanGroupLine, affectedLines);
updateFloorLines(onlyFloorlines, DragedPoint);
}
export default DragPoint;

View File

@ -0,0 +1,37 @@
import * as Types from "../../world/worldTypes";
function removeSoloPoint(
line: Types.RefLine,
floorPlanGroupLine: Types.RefGroup,
floorPlanGroupPoint: Types.RefGroup
): void {
////////// Remove the point if there is only one point and if it is not connected to any other line and also the reference line //////////
if (line.current[0]) {
const pointUUID = line.current[0][1];
let isConnected = false;
floorPlanGroupLine.current.children.forEach((line) => {
const linePoints = line.userData.linePoints;
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === pointUUID || uuid2 === pointUUID) {
isConnected = true;
}
});
if (!isConnected) {
floorPlanGroupPoint.current.children.forEach((point: any) => {
if (point.uuid === pointUUID) {
(<any>point.material).dispose();
(<any>point.geometry).dispose();
floorPlanGroupPoint.current.remove(point);
}
});
}
line.current = [];
}
}
export default removeSoloPoint;

View File

@ -0,0 +1,32 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
function addRoofToScene(
shape: Types.Shape,
floor: Types.Number,
userData: Types.UserData,
floorGroup: Types.RefGroup
): void {
////////// Creating a Polygon roof from the shape of the Polygon floor //////////
const extrudeSettings: THREE.ExtrudeGeometryOptions = {
depth: CONSTANTS.roofConfig.height,
bevelEnabled: false
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.roofConfig.defaultColor, side: THREE.DoubleSide, transparent: true, depthWrite: false });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = CONSTANTS.wallConfig.height + floor;
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.rotateX(Math.PI / 2);
mesh.userData.uuids = userData;
mesh.name = `Roof_Layer_${(floor / CONSTANTS.wallConfig.height) + 1}`;
floorGroup.current.add(mesh);
}
export default addRoofToScene;

View File

@ -0,0 +1,47 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function hideRoof(
visibility: Types.Boolean,
floorGroup: Types.RefGroup,
camera: THREE.Camera
): void {
////////// Toggles the visibility of the roof based on the camera position and the Roof visibility button on UI //////////
const v = new THREE.Vector3();
const u = new THREE.Vector3();
if (visibility === true && floorGroup.current) {
for (const child of floorGroup.current.children) {
if (child.name.includes("Roof")) {
const roofChild = child as Types.Mesh;
roofChild.getWorldDirection(v);
camera?.getWorldDirection(u);
if (roofChild.material) {
const materials = Array.isArray(roofChild.material) ? roofChild.material : [roofChild.material];
materials.forEach(material => {
material.visible = v.dot(u) < 0.25;
});
}
}
}
} else {
if (floorGroup.current) {
for (const child of floorGroup.current.children) {
if (child.name.includes("Roof")) {
const roofChild = child as Types.Mesh;
if (roofChild.material) {
const materials = Array.isArray(roofChild.material) ? roofChild.material : [roofChild.material];
materials.forEach(material => {
material.visible = false;
});
}
}
}
}
}
}
export default hideRoof;

View File

@ -0,0 +1,108 @@
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { toast } from 'react-toastify';
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
import * as CONSTANTS from '../../world/worldConstants';
import { setWallItem } from '../../../../services/factoryBuilder/assest/wallAsset/setWallItemApi';
import { Socket } from 'socket.io-client';
async function AddWallItems(
selected: Types.String,
raycaster: THREE.Raycaster,
CSGGroup: Types.RefMesh,
AssetConfigurations: Types.AssetConfigurations,
setWallItems: Types.setWallItemSetState,
socket: Socket<any>
): Promise<void> {
////////// Load Wall GLtf's and set the positions, rotation, type etc. in state and store in localstorage //////////
let intersects = raycaster?.intersectObject(CSGGroup.current!, true);
const wallRaycastIntersection = intersects?.find((child) => child.object.name.includes("WallRaycastReference"));
if (wallRaycastIntersection) {
const intersectionPoint = wallRaycastIntersection;
const loader = new GLTFLoader();
loader.load(AssetConfigurations[selected].modelUrl, async (gltf) => {
const model = gltf.scene;
model.userData = { wall: intersectionPoint.object.parent };
model.children[0].children.forEach((child) => {
if (child.name !== "CSG_REF") {
child.castShadow = true;
child.receiveShadow = true;
}
});
const config = AssetConfigurations[selected];
let positionY = typeof config.positionY === 'function' ? config.positionY(intersectionPoint) : config.positionY;
if (positionY === 0) {
positionY = Math.floor(intersectionPoint.point.y / CONSTANTS.wallConfig.height) * CONSTANTS.wallConfig.height;
}
const newWallItem = {
type: config.type,
model: model,
modelname: selected,
scale: config.scale,
csgscale: config.csgscale,
csgposition: config.csgposition,
position: [intersectionPoint.point.x, positionY, intersectionPoint.point.z] as [number, number, number],
quaternion: intersectionPoint.object.quaternion.clone() as Types.QuaternionType
};
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// await setWallItem(
// organization,
// model.uuid,
// newWallItem.modelname,
// newWallItem.type!,
// newWallItem.csgposition!,
// newWallItem.csgscale!,
// newWallItem.position,
// newWallItem.quaternion,
// newWallItem.scale!,
// )
//SOCKET
const data = {
organization: organization,
modeluuid: model.uuid,
modelname: newWallItem.modelname,
type: newWallItem.type!,
csgposition: newWallItem.csgposition!,
csgscale: newWallItem.csgscale!,
position: newWallItem.position,
quaternion: newWallItem.quaternion,
scale: newWallItem.scale!,
socketId: socket.id
}
socket.emit('v1:wallItems:set', data);
setWallItems((prevItems) => {
const updatedItems = [...prevItems, newWallItem];
const WallItemsForStorage = updatedItems.map(item => {
const { model, ...rest } = item;
return {
...rest,
modeluuid: model?.uuid,
};
});
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
toast.success("Model Added!");
return updatedItems;
});
});
}
}
export default AddWallItems;

View File

@ -0,0 +1,59 @@
import { toast } from 'react-toastify';
import * as Types from "../../world/worldTypes";
import { deleteWallItem } from '../../../../services/factoryBuilder/assest/wallAsset/deleteWallItemApi';
import { Socket } from 'socket.io-client';
function DeleteWallItems(
hoveredDeletableWallItem: Types.RefMesh,
setWallItems: Types.setWallItemSetState,
wallItems: Types.wallItems,
socket: Socket<any>
): void {
////////// Deleting the hovered Wall GLTF from thewallItems and also update it in the localstorage //////////
if (hoveredDeletableWallItem.current && hoveredDeletableWallItem.current.parent) {
setWallItems([]);
let WallItemsRef = wallItems;
const removedItem = WallItemsRef.find((item) => item.model?.uuid === hoveredDeletableWallItem.current?.parent?.uuid);
const Items = WallItemsRef.filter((item) => item.model?.uuid !== hoveredDeletableWallItem.current?.parent?.uuid);
setTimeout(async () => {
WallItemsRef = Items;
setWallItems(WallItemsRef);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// await deleteWallItem(organization, removedItem?.model?.uuid!, removedItem?.modelname!)
//SOCKET
const data = {
organization: organization,
modeluuid: removedItem?.model?.uuid!,
modelname: removedItem?.modelname!,
socketId: socket.id
}
socket.emit('v1:wallItems:delete', data);
const WallItemsForStorage = WallItemsRef.map(item => {
const { model, ...rest } = item;
return {
...rest,
modeluuid: model?.uuid,
};
});
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
toast.success("Model Removed!");
hoveredDeletableWallItem.current = null;
}, 50);
}
}
export default DeleteWallItems;

View File

@ -0,0 +1,45 @@
import * as THREE from 'three';
import * as Types from "../../world/worldTypes";
function hideWalls(
visibility: Types.Boolean,
scene: THREE.Scene,
camera: THREE.Camera
): void {
////////// Altering the visibility of the Walls when the world direction of the wall is facing the camera //////////
const v = new THREE.Vector3();
const u = new THREE.Vector3();
if (visibility === true) {
for (const children of scene.children) {
if (children.name === "Walls" && children.children[0]?.children.length > 0) {
children.children[0].children.forEach((child: any) => {
if (child.children[0]?.userData.WallType === "RoomWall") {
child.children[0].getWorldDirection(v);
camera.getWorldDirection(u);
if (child.children[0].material) {
child.children[0].material.visible = (2 * v.dot(u)) >= -0.5;
}
}
});
}
}
} else {
for (const children of scene.children) {
if (children.name === "Walls" && children.children[0]?.children.length > 0) {
children.children[0].children.forEach((child: any) => {
if (child.children[0]?.userData.WallType === "RoomWall") {
if (child.children[0].material) {
child.children[0].material.visible = true;
}
}
});
}
}
}
}
export default hideWalls;

View File

@ -0,0 +1,129 @@
import * as THREE from 'three';
import * as turf from '@turf/turf';
import * as CONSTANTS from '../../world/worldConstants';
import * as Types from "../../world/worldTypes";
import getRoomsFromLines from '../lines/getRoomsFromLines';
async function loadWalls(
lines: Types.RefLines,
setWalls: any,
): Promise<void> {
////////// Removes the old walls if any, Checks if there is any overlapping in lines if any updates it , starts function that creates floor and roof //////////
const Walls: Types.Walls = [];
const Rooms: Types.Rooms = [];
localStorage.setItem("Lines", JSON.stringify(lines.current));
if (lines.current.length > 1) {
////////// Add Walls that are forming a room //////////
const wallSet = new Set<string>();
const rooms: Types.Rooms = await getRoomsFromLines(lines);
Rooms.push(...rooms);
Rooms.forEach(({ coordinates: room, layer }) => {
for (let i = 0; i < room.length - 1; i++) {
const uuid1 = room[i].uuid;
const uuid2 = room[(i + 1) % room.length].uuid;
const wallId = `${uuid1}_${uuid2}`;
if (!wallSet.has(wallId)) {
const p1 = room[i].position;
const p2 = room[(i + 1) % room.length].position;
const shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.lineTo(0, CONSTANTS.wallConfig.height);
shape.lineTo(p2.distanceTo(p1), CONSTANTS.wallConfig.height);
shape.lineTo(p2.distanceTo(p1), 0);
shape.lineTo(0, 0);
const extrudeSettings = {
depth: CONSTANTS.wallConfig.width,
bevelEnabled: false
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const angle = Math.atan2(p2.z - p1.z, p2.x - p1.x);
Walls.push([geometry, [0, -angle, 0], [p1.x, (layer - 1) * CONSTANTS.wallConfig.height, p1.z], "RoomWall", layer]);
wallSet.add(wallId);
}
}
});
////////// Add Walls that are not forming any room //////////
lines.current.forEach(line => {
if (line[0][3] && line[1][3] !== CONSTANTS.lineConfig.wallName) {
return;
}
const [uuid1, uuid2] = line.map(point => point[1]);
let isInRoom = false;
const lineLayer = line[0][2];
for (let room of Rooms) {
const roomLayer = room.layer;
if (roomLayer !== lineLayer) continue;
for (let i = 0; i < room.coordinates.length - 1; i++) {
const roomUuid1 = room.coordinates[i].uuid;
const roomUuid2 = room.coordinates[(i + 1) % room.coordinates.length].uuid;
if (
(uuid1 === roomUuid1 && uuid2 === roomUuid2) ||
(uuid1 === roomUuid2 && uuid2 === roomUuid1)
) {
isInRoom = true;
break;
}
}
if (isInRoom) break;
}
if (!isInRoom) {
const p1 = new THREE.Vector3(line[0][0].x, 0, line[0][0].z);
const p2 = new THREE.Vector3(line[1][0].x, 0, line[1][0].z);
let isCollinear = false;
for (let room of Rooms) {
if (room.layer !== lineLayer) continue;
for (let i = 0; i < room.coordinates.length - 1; i++) {
const roomP1 = room.coordinates[i].position;
const roomP2 = room.coordinates[(i + 1) % room.coordinates.length].position;
const lineFeature = turf.lineString([[p1.x, p1.z], [p2.x, p2.z]]);
const roomFeature = turf.lineString([[roomP1.x, roomP1.z], [roomP2.x, roomP2.z]]);
if (turf.booleanOverlap(lineFeature, roomFeature)) {
isCollinear = true;
break;
}
}
if (isCollinear) break;
}
if (!isCollinear) {
const shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.lineTo(0, CONSTANTS.wallConfig.height);
shape.lineTo(p2.distanceTo(p1), CONSTANTS.wallConfig.height);
shape.lineTo(p2.distanceTo(p1), 0);
shape.lineTo(0, 0);
const extrudeSettings = {
depth: CONSTANTS.wallConfig.width,
bevelEnabled: false
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const angle = Math.atan2(p2.z - p1.z, p2.x - p1.x);
Walls.push([geometry, [0, -angle, 0], [p1.x, (lineLayer - 1) * CONSTANTS.wallConfig.height, p1.z], "SegmentWall", lineLayer]);
}
}
});
setWalls(Walls);
}else{
setWalls([]);
}
}
export default loadWalls;

View File

@ -0,0 +1,50 @@
import * as THREE from 'three';
import * as Types from '../../world/worldTypes';
import * as CONSTANTS from '../../world/worldConstants';
const baseMaterial = new THREE.ShaderMaterial({
side: THREE.DoubleSide,
vertexShader: `
varying vec2 vUv;
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vUv = uv;
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 uColor;
void main(){
float alpha = 1.0 - vUv.y;
gl_FragColor = vec4(uColor, alpha);
}
`,
uniforms: {
uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.defaultColor) },
},
transparent: true,
});
export default function addZonesToScene(
line: Types.Line,
floorGroupZone: Types.RefGroup,
color: THREE.Color
) {
const point1 = line[0][0];
const point2 = line[1][0];
const length = (new THREE.Vector3(point2.x, point2.y, point2.z)).distanceTo(new THREE.Vector3(point1.x, point1.y, point1.z));
const angle = Math.atan2(point2.z - point1.z, point2.x - point1.x);
const geometry = new THREE.PlaneGeometry(length, 10);
const material = baseMaterial.clone();
material.uniforms.uColor.value.set(color.r, color.g, color.b);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set((point1.x + point2.x) / 2, ((line[0][2] - 1) * CONSTANTS.wallConfig.height) + 5, (point1.z + point2.z) / 2);
mesh.rotation.y = -angle;
floorGroupZone.current.add(mesh);
}

View File

@ -0,0 +1,19 @@
import * as Types from '../../world/worldTypes';
import * as THREE from 'three';
import * as CONSTANTS from '../../world/worldConstants';
import addZonesToScene from './addZonesToScene';
export default function loadZones(
lines: Types.RefLines,
floorGroupZone: Types.RefGroup
) {
if (!floorGroupZone.current) return
floorGroupZone.current.children = [];
const zones = lines.current.filter((line) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.zoneName);
if (zones.length > 0) {
zones.forEach((zone: Types.Line) => {
addZonesToScene(zone, floorGroupZone, new THREE.Color(CONSTANTS.zoneConfig.color))
})
}
}

View File

@ -0,0 +1,101 @@
import { useFrame, useThree } from "@react-three/fiber";
import { useAddAction, useDeleteModels, useRoofVisibility, useToggleView, useWallVisibility, useUpdateScene } from "../../../store/store";
import hideRoof from "../geomentries/roofs/hideRoof";
import hideWalls from "../geomentries/walls/hideWalls";
import addAndUpdateReferencePillar from "../geomentries/pillars/addAndUpdateReferencePillar";
import { useEffect } from "react";
import addPillar from "../geomentries/pillars/addPillar";
import DeletePillar from "../geomentries/pillars/deletePillar";
import DeletableHoveredPillar from "../geomentries/pillars/deletableHoveredPillar";
import loadFloor from "../geomentries/floors/loadFloor";
const FloorGroup = ({ floorGroup, lines, referencePole, hoveredDeletablePillar }: any) => {
const state = useThree();
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
const { wallVisibility, setWallVisibility } = useWallVisibility();
const { toggleView, setToggleView } = useToggleView();
const { scene, camera, pointer, raycaster, gl } = useThree();
const { addAction, setAddAction } = useAddAction();
const { deleteModels, setDeleteModels } = useDeleteModels();
const { updateScene, setUpdateScene } = useUpdateScene();
useEffect(() => {
if (updateScene) {
loadFloor(lines, floorGroup);
setUpdateScene(false);
}
}, [updateScene])
useEffect(() => {
if (!addAction) {
if (referencePole.current) {
(referencePole.current as any).material.dispose();
(referencePole.current.geometry as any).dispose();
floorGroup.current.remove(referencePole.current);
referencePole.current = undefined;
}
}
}, [addAction]);
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseUp = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
if (!drag) {
if (addAction === "pillar") {
addPillar(referencePole, floorGroup);
}
if (deleteModels) {
DeletePillar(hoveredDeletablePillar, floorGroup);
}
}
}
};
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
};
}, [deleteModels, addAction])
useFrame(() => {
hideRoof(roofVisibility, floorGroup, camera);
hideWalls(wallVisibility, scene, camera);
if (addAction === "pillar") {
addAndUpdateReferencePillar(raycaster, floorGroup, referencePole);
}
if (deleteModels) {
DeletableHoveredPillar(state, floorGroup, hoveredDeletablePillar);
}
})
return (
<group ref={floorGroup} visible={!toggleView} name="floorGroup">
</group>
)
}
export default FloorGroup;

View File

@ -0,0 +1,245 @@
import * as THREE from 'three';
import * as Types from '../world/worldTypes';
import * as CONSTANTS from '../world/worldConstants';
import { useThree } from "@react-three/fiber";
import { useToggleView, useActiveLayer, useSocketStore, useDeletePointOrLine, useMovePoint, useUpdateScene, useNewLines, useToolMode } from "../../../store/store";
import { useEffect } from "react";
import removeSoloPoint from "../geomentries/points/removeSoloPoint";
import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
import getClosestIntersection from "../geomentries/lines/getClosestIntersection";
import addPointToScene from "../geomentries/points/addPointToScene";
import arrayLineToObject from '../geomentries/lines/lineConvertions/arrayLineToObject';
import addLineToScene from "../geomentries/lines/addLineToScene";
import loadAisles from '../geomentries/aisles/loadAisles';
const FloorGroupAilse = ({ floorGroupAisle, plane, floorPlanGroupLine, floorPlanGroupPoint, line, lines, currentLayerPoint, dragPointControls, floorPlanGroup, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
const { toggleView, setToggleView } = useToggleView();
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
const { toolMode, setToolMode } = useToolMode();
const { movePoint, setMovePoint } = useMovePoint();
const { socket } = useSocketStore();
const { activeLayer } = useActiveLayer();
const { gl, raycaster, camera, pointer } = useThree();
const { updateScene, setUpdateScene } = useUpdateScene();
const { newLines, setNewLines } = useNewLines();
useEffect(() => {
if (updateScene) {
loadAisles(lines, floorGroupAisle);
setUpdateScene(false);
}
}, [updateScene])
useEffect(() => {
if (toolMode === "Aisle") {
setDeletePointOrLine(false);
setMovePoint(false);
} else {
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
}, [toolMode]);
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseUp = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
}
}
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
const onContextMenu = (e: any) => {
e.preventDefault();
if (toolMode === "Aisle") {
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
};
const onMouseClick = (evt: any) => {
if (!plane.current || drag) return;
const intersects = raycaster.intersectObject(plane.current, true);
let intersectionPoint = intersects[0].point;
const points = floorPlanGroupPoint.current?.children ?? [];
const intersectsPoint = raycaster.intersectObjects(points, true).find(intersect => intersect.object.visible);
let intersectsLines: any = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
if (intersectsLines.length > 0 && intersects && intersects.length > 0 && !intersectsPoint) {
const lineType = intersectsLines[0].object.userData.linePoints[0][3];
if (lineType === CONSTANTS.lineConfig.aisleName) {
// console.log("intersected a aisle line");
const ThroughPoint = (intersectsLines[0].object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
let intersection = getClosestIntersection(ThroughPoint, intersectionPoint);
if (!intersection) return;
const point = addPointToScene(intersection, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName);
(line.current as Types.Line).push([new THREE.Vector3(intersection.x, 0.01, intersection.z), point.uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current]);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
}
}
} else if (intersectsPoint && intersects && intersects.length > 0) {
if (intersectsPoint.object.userData.type === CONSTANTS.lineConfig.aisleName) {
// console.log("intersected a aisle point");
intersectionPoint = intersectsPoint.object.position;
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), intersectsPoint.object.uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current]);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
ispreSnapped.current = false;
isSnapped.current = false;
}
}
} else if (intersects && intersects.length > 0) {
// console.log("intersected a empty area");
let uuid: string = "";
if (isAngleSnapped.current && anglesnappedPoint.current && line.current.length > 0) {
intersectionPoint = anglesnappedPoint.current;
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName);
uuid = point.uuid;
} else if (isSnapped.current && snappedPoint.current && line.current.length > 0) {
intersectionPoint = snappedPoint.current;
uuid = isSnappedUUID.current!;
} else if (ispreSnapped.current && snappedPoint.current) {
intersectionPoint = snappedPoint.current;
uuid = isSnappedUUID.current!;
} else {
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName);
uuid = point.uuid;
}
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current]);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
ispreSnapped.current = false;
isSnapped.current = false;
}
}
}
if (toolMode === 'Aisle') {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("click", onMouseClick);
canvasElement.addEventListener("contextmenu", onContextMenu);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [toolMode])
return (
<group ref={floorGroupAisle} visible={!toggleView} name="floorGroupAisle">
</group>
)
}
export default FloorGroupAilse;

View File

@ -0,0 +1,279 @@
import { useFrame, useThree } from "@react-three/fiber";
import { useActiveTool, useCamMode, useDeletableFloorItem, useDeleteModels, useFloorItems, useRenderDistance, useselectedFloorItem, useSelectedItem, useSocketStore, useToggleView, useTransformMode } from "../../../store/store";
import assetVisibility from "../geomentries/assets/assetVisibility";
import { useEffect } from "react";
import * as THREE from "three";
import * as Types from "../world/worldTypes";
import assetManager, { cancelOngoingTasks } from "../geomentries/assets/assetManager";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import DeletableHoveredFloorItems from "../geomentries/assets/deletableHoveredFloorItems";
import DeleteFloorItems from "../geomentries/assets/deleteFloorItems";
import loadInitialFloorItems from "../IntialLoad/loadInitialFloorItems";
import addAssetModel from "../geomentries/assets/addAssetModel";
import { getFloorItems } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi";
import { retrieveGLTF } from "../indexDB/idbUtils";
const assetManagerWorker = new Worker(new URL('../../../services/factoryBuilder/webWorkers/assetManagerWorker.js', import.meta.url));
const gltfLoaderWorker = new Worker(new URL('../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js', import.meta.url));
const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject, floorGroup, tempLoader, isTempLoader, plane }: any) => {
const state: Types.ThreeState = useThree();
const { raycaster, camera, controls, pointer }: any = state;
const { renderDistance, setRenderDistance } = useRenderDistance();
const { toggleView, setToggleView } = useToggleView();
const { floorItems, setFloorItems } = useFloorItems();
const { camMode, setCamMode } = useCamMode();
const { deleteModels, setDeleteModels } = useDeleteModels();
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { transformMode, setTransformMode } = useTransformMode();
const { selectedFloorItem, setselectedFloorItem } = useselectedFloorItem();
const { activeTool, setActiveTool } = useActiveTool();
const { selectedItem, setSelectedItem } = useSelectedItem();
const { socket } = useSocketStore();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
useEffect(() => {
// Load initial floor items
// const email = localStorage.getItem('email');
// const organization = (email!.split("@")[1]).split(".")[0];
// getFloorItems(organization).then((data) => {
// gltfLoaderWorker.postMessage({ FloorItems: data })
// })
// gltfLoaderWorker.onmessage = async (event) => {
// if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
// const blobUrl = URL.createObjectURL(event.data.modelBlob);
// loader.load(blobUrl, (gltf) => {
// URL.revokeObjectURL(blobUrl);
// THREE.Cache.remove(blobUrl);
// THREE.Cache.add(event.data.modelID, gltf);
// });
// } else if (event.data.message === "done") {
// loadInitialFloorItems(itemsGroup, setFloorItems);
// }
// }
loadInitialFloorItems(itemsGroup, setFloorItems);
}, []);
useEffect(() => {
assetManagerWorker.onmessage = async (event) => {
cancelOngoingTasks(); // Cancel the ongoing process
await assetManager(event.data, itemsGroup, loader);
};
}, [assetManagerWorker]);
useEffect(() => {
if (toggleView) return
const uuids: string[] = [];
itemsGroup.current?.children.forEach((child: any) => {
uuids.push(child.uuid);
});
const cameraPosition = state.camera.position;
assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance });
}, [camMode, renderDistance]);
useEffect(() => {
const controls: any = state.controls;
const camera: any = state.camera;
if (controls) {
let intervalId: NodeJS.Timeout | null = null;
const handleChange = () => {
if (toggleView) return
const uuids: string[] = [];
itemsGroup.current?.children.forEach((child: any) => {
uuids.push(child.uuid);
});
const cameraPosition = camera.position;
assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance });
};
const startInterval = () => {
if (!intervalId) {
intervalId = setInterval(handleChange, 50);
}
};
const stopInterval = () => {
handleChange();
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
};
controls.addEventListener('rest', handleChange);
controls.addEventListener('rest', stopInterval);
controls.addEventListener('control', startInterval);
controls.addEventListener('controlend', stopInterval);
return () => {
controls.removeEventListener('rest', handleChange);
controls.removeEventListener('rest', stopInterval);
controls.removeEventListener('control', startInterval);
controls.removeEventListener('controlend', stopInterval);
if (intervalId) {
clearInterval(intervalId);
}
};
}
}, [state.controls, floorItems, toggleView, renderDistance]);
useEffect(() => {
const canvasElement = state.gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
const onMouseUp = async (evt: any) => {
if (controls) {
(controls as any).enabled = true;
}
if (evt.button === 0) {
isLeftMouseDown = false;
if (drag) return;
if (deleteModels) {
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, socket);
}
const Mode = transformMode;
if (Mode !== null || activeTool === "Cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
if (intersects.length === 0) {
const target = controls.getTarget(new THREE.Vector3());
await controls.setTarget(target.x, 0, target.z, true);
setselectedFloorItem(null);
}
}
}
};
const onDblClick = async (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
if (drag) return;
const Mode = transformMode;
if (Mode !== null || activeTool === "Cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
let currentObject = intersects[0].object;
while (currentObject) {
if (currentObject.name === "Scene") {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
}
if (currentObject) {
AttachedObject.current = currentObject as any;
// controls.fitToSphere(AttachedObject.current!, true);
const bbox = new THREE.Box3().setFromObject(AttachedObject.current);
const size = bbox.getSize(new THREE.Vector3());
const center = bbox.getCenter(new THREE.Vector3());
const front = new THREE.Vector3(0, 0, 1);
AttachedObject.current.localToWorld(front);
front.sub(AttachedObject.current.position).normalize();
const distance = Math.max(size.x, size.y, size.z) * 2;
const newPosition = center.clone().addScaledVector(front, distance);
controls.setPosition(newPosition.x, newPosition.y, newPosition.z, true);
controls.setTarget(center.x, center.y, center.z, true);
controls.fitToBox(AttachedObject.current!, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5 });
setselectedFloorItem(AttachedObject.current!);
}
} else {
const target = controls.getTarget(new THREE.Vector3());
await controls.setTarget(target.x, 0, target.z, true);
setselectedFloorItem(null);
}
}
}
}
const onDrop = (event: any) => {
if (!event.dataTransfer?.files[0]) return;
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, plane);
}
}
const onDragOver = (event: any) => {
event.preventDefault();
};
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("dblclick", onDblClick);
canvasElement.addEventListener("drop", onDrop);
canvasElement.addEventListener("dragover", onDragOver);
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("dblclick", onDblClick);
canvasElement.removeEventListener("drop", onDrop);
canvasElement.removeEventListener("dragover", onDragOver);
};
}, [deleteModels, transformMode, controls, selectedItem, state.camera, state.pointer, activeTool]);
useFrame(() => {
if (controls)
assetVisibility(itemsGroup, state.camera.position, renderDistance);
if (deleteModels) {
DeletableHoveredFloorItems(state, itemsGroup, hoveredDeletableFloorItem, setDeletableFloorItem);
} else if (!deleteModels) {
if (hoveredDeletableFloorItem.current) {
hoveredDeletableFloorItem.current = undefined;
setDeletableFloorItem(null);
}
}
})
return (
<group ref={itemsGroup} name="itemsGroup">
</group>
)
}
export default FloorItemsGroup;

View File

@ -0,0 +1,197 @@
import { useEffect } from "react";
import * as Types from '../world/worldTypes';
import { useActiveLayer, useDeletedLines, useDeletePointOrLine, useToolMode, useMovePoint, useNewLines, useRemovedLayer, useSocketStore, useToggleView, useUpdateScene } from "../../../store/store";
import Layer2DVisibility from "../geomentries/layers/layer2DVisibility";
import { useFrame, useThree } from "@react-three/fiber";
import DeletableLineorPoint from "../functions/deletableLineOrPoint";
import removeSoloPoint from "../geomentries/points/removeSoloPoint";
import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
import DeleteLayer from "../geomentries/layers/deleteLayer";
import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
import loadInitialPoint from "../IntialLoad/loadInitialPoint";
import loadInitialLine from "../IntialLoad/loadInitialLine";
import deletePoint from "../geomentries/points/deletePoint";
import deleteLine from "../geomentries/lines/deleteLine";
import drawWall from "../geomentries/lines/drawWall";
import drawOnlyFloor from "../geomentries/floors/drawOnlyFloor";
import addDragControl from "../eventDeclaration/dragControlDeclaration";
const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, floorGroup, currentLayerPoint, dragPointControls, hoveredDeletablePoint, hoveredDeletableLine, plane, line, lines, onlyFloorline, onlyFloorlines, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
const state = useThree();
const { scene, camera, gl, raycaster, controls } = state;
const { activeLayer, setActiveLayer } = useActiveLayer();
const { toggleView, setToggleView } = useToggleView();
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
const { toolMode, setToolMode } = useToolMode();
const { movePoint, setMovePoint } = useMovePoint();
const { removedLayer, setRemovedLayer } = useRemovedLayer();
const { updateScene, setUpdateScene } = useUpdateScene();
const { newLines, setNewLines } = useNewLines();
const { deletedLines, setDeletedLines } = useDeletedLines();
const { socket } = useSocketStore();
useEffect(() => {
addDragControl(dragPointControls, currentLayerPoint, state, floorPlanGroupPoint, floorPlanGroupLine, lines, onlyFloorlines, socket);
}, [state]);
useEffect(() => {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
// Load data from localStorage if available
getLines(organization).then((data) => {
const Lines: Types.Lines = objectLinesToArray(data);
// const data = localStorage.getItem("Lines");
if (Lines) {
lines.current = Lines;
loadInitialPoint(lines, floorPlanGroupPoint, currentLayerPoint, dragPointControls);
loadInitialLine(floorPlanGroupLine, lines);
setUpdateScene(true);
}
})
}, []);
useEffect(() => {
if (!toggleView) {
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
}, [toggleView]);
useEffect(() => {
if (toolMode === "Wall" || toolMode === "Floor") {
setDeletePointOrLine(false);
setMovePoint(false);
} else {
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
}, [toolMode]);
useEffect(() => {
if (movePoint) {
setToolMode(null);
setDeletePointOrLine(false);
if (dragPointControls.current) {
dragPointControls.current.enabled = true;
}
} else {
if (dragPointControls.current) {
dragPointControls.current.enabled = false;
}
}
}, [movePoint, toolMode]);
useEffect(() => {
if (deletePointOrLine) {
setToolMode(null);
setMovePoint(false);
}
}, [deletePointOrLine]);
useEffect(() => {
Layer2DVisibility(activeLayer, floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, currentLayerPoint, dragPointControls);
}, [activeLayer]);
useEffect(() => {
if (removedLayer !== null) {
DeleteLayer(removedLayer, lines, floorPlanGroupLine, floorPlanGroupPoint, onlyFloorlines, floorGroup, setDeletedLines, setRemovedLayer, socket);
}
}, [removedLayer]);
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseUp = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
}
if (controls) {
(controls as any).enabled = true;
}
}
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
const onContextMenu = (e: any) => {
e.preventDefault();
if (toolMode === "Wall" || toolMode === "Floor") {
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
};
const onMouseClick = (evt: any) => {
if (!plane.current || drag) return;
if (deletePointOrLine) {
if (hoveredDeletablePoint.current !== null) {
deletePoint(hoveredDeletablePoint, onlyFloorlines, floorPlanGroupPoint, floorPlanGroupLine, lines, setDeletedLines, socket);
}
if (hoveredDeletableLine.current !== null) {
deleteLine(hoveredDeletableLine, onlyFloorlines, lines, floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, socket);
}
}
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);
}
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);
}
}
if (deletePointOrLine || toolMode === "Wall" || toolMode === "Floor") {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("click", onMouseClick);
canvasElement.addEventListener("contextmenu", onContextMenu);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [deletePointOrLine, toolMode, activeLayer])
useFrame(() => {
if (deletePointOrLine) {
DeletableLineorPoint(state, plane, floorPlanGroupLine, floorPlanGroupPoint, hoveredDeletableLine, hoveredDeletablePoint);
}
})
return (
<group ref={floorPlanGroup} visible={toggleView} name="floorPlanGroup">
<group ref={floorPlanGroupLine} name="floorPlanGroupLine"></group>
<group ref={floorPlanGroupPoint} name="floorPlanGroupPoint"></group>
</group>
)
}
export default FloorPlanGroup;

View File

@ -0,0 +1,289 @@
import { useEffect } from "react";
import { useDeleteModels, useDeletePointOrLine, useObjectPosition, useObjectRotation, useObjectScale, useSelectedWallItem, useSocketStore, useWallItems } from "../../../store/store";
import { Csg } from "../csg/csg";
import * as Types from "../world/worldTypes";
import * as CONSTANTS from "../world/worldConstants";
import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
import DeleteWallItems from "../geomentries/walls/deleteWallItems";
import loadInitialWallItems from "../IntialLoad/loadInitialWallItems";
import AddWallItems from "../geomentries/walls/addWallItems";
const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletableWallItem, selectedItemsIndex, setSelectedItemsIndex, CSGGroup }: any) => {
const { deleteModels, setDeleteModels } = useDeleteModels();
const { wallItems, setWallItems } = useWallItems();
const { objectPosition, setObjectPosition } = useObjectPosition();
const { objectScale, setObjectScale } = useObjectScale();
const { objectRotation, setObjectRotation } = useObjectRotation();
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
const { socket } = useSocketStore();
const state = useThree();
const { pointer, camera, raycaster } = state;
useEffect(() => {
// Load Wall Items from the backend
loadInitialWallItems(setWallItems, AssetConfigurations);
}, []);
////////// Update the Scale value changes in thewallItems State //////////
////////// Update the Position value changes in the selected item //////////
////////// Update the Rotation value changes in the selected item //////////
useEffect(() => {
if (objectScale.x && objectScale.y && objectScale.z) {
let ScaledWallItems: Types.wallItems = [];
wallItems.forEach((items: any) => {
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
items.scale = [objectScale.x, objectScale.y, objectScale.z];
}
ScaledWallItems.push(items);
});
setWallItems(ScaledWallItems);
}
}, [objectScale]);
useEffect(() => {
if (objectPosition.x && objectPosition.y && objectPosition.z) {
let ScaledWallItems: Types.wallItems = [];
wallItems.forEach((items: any) => {
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
items.position = [objectPosition.x, objectPosition.y, objectPosition.z];
}
ScaledWallItems.push(items);
});
setWallItems(ScaledWallItems);
}
}, [objectPosition]);
useEffect(() => {
if (objectRotation.x && objectRotation.y && objectRotation.z) {
let ScaledWallItems: Types.wallItems = [];
wallItems.forEach((items: any) => {
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
const radiansX = objectRotation.x * (Math.PI / 180);
const radiansY = objectRotation.y * (Math.PI / 180);
const radiansZ = objectRotation.z * (Math.PI / 180);
const quaternion = new THREE.Quaternion().setFromEuler(
new THREE.Euler(radiansX, radiansY, radiansZ)
);
items.quaternion = [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
}
ScaledWallItems.push(items);
});
setWallItems(ScaledWallItems);
}
}, [objectRotation]);
useEffect(() => {
const canvasElement = state.gl.domElement;
function handlePointerMove(e: any) {
if (selectedItemsIndex !== null && !deletePointOrLine && e.buttons === 1) {
const Raycaster = state.raycaster;
const intersects = Raycaster.intersectObjects(CSGGroup.current?.children[0].children!, true);
const Object = intersects.find((child) => child.object.name.includes("WallRaycastReference"));
if (Object) {
(state.controls as any)!.enabled = false;
setWallItems((prevItems: any) => {
const updatedItems = [...prevItems];
let position: [number, number, number] = [0, 0, 0];
if (updatedItems[selectedItemsIndex].type === "Fixed-Move") {
position = [Object!.point.x, Math.floor(Object!.point.y / CONSTANTS.wallConfig.height) * CONSTANTS.wallConfig.height, Object!.point.z];
} else if (updatedItems[selectedItemsIndex].type === "Free-Move") {
position = [Object!.point.x, Object!.point.y, Object!.point.z];
}
requestAnimationFrame(() => {
setObjectPosition(new THREE.Vector3(...position));
setObjectRotation({
x: THREE.MathUtils.radToDeg(Object!.object.rotation.x),
y: THREE.MathUtils.radToDeg(Object!.object.rotation.y),
z: THREE.MathUtils.radToDeg(Object!.object.rotation.z),
});
});
updatedItems[selectedItemsIndex] = {
...updatedItems[selectedItemsIndex],
position: position,
quaternion: Object!.object.quaternion.clone() as Types.QuaternionType,
};
return updatedItems;
});
}
}
}
async function handlePointerUp() {
const Raycaster = state.raycaster;
const intersects = Raycaster.intersectObjects(CSGGroup.current?.children[0].children!, true);
const Object = intersects.find((child) => child.object.name.includes("WallRaycastReference"));
if (Object) {
if (selectedItemsIndex !== null) {
let currentItem: any = null;
setWallItems((prevItems: any) => {
const updatedItems = [...prevItems];
const WallItemsForStorage = updatedItems.map((item) => {
const { model, ...rest } = item;
return {
...rest,
modeluuid: model?.uuid,
};
});
currentItem = updatedItems[selectedItemsIndex];
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
return updatedItems;
});
setTimeout(async () => {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// await setWallItem(
// organization,
// currentItem?.model?.uuid,
// currentItem.modelname,
// currentItem.type!,
// currentItem.csgposition!,
// currentItem.csgscale!,
// currentItem.position,
// currentItem.quaternion,
// currentItem.scale!,
// )
//SOCKET
const data = {
organization: organization,
modeluuid: currentItem.model?.uuid!,
modelname: currentItem.modelname!,
type: currentItem.type!,
csgposition: currentItem.csgposition!,
csgscale: currentItem.csgscale!,
position: currentItem.position!,
quaternion: currentItem.quaternion,
scale: currentItem.scale!,
socketId: socket.id
}
socket.emit('v1:wallItems:set', data);
}, 0);
(state.controls as any)!.enabled = true;
}
}
}
canvasElement.addEventListener("pointermove", handlePointerMove);
canvasElement.addEventListener("pointerup", handlePointerUp);
return () => {
canvasElement.removeEventListener("pointermove", handlePointerMove);
canvasElement.removeEventListener("pointerup", handlePointerUp);
};
}, [selectedItemsIndex]);
useEffect(() => {
const canvasElement = state.gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseUp = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
if (!drag && deleteModels) {
DeleteWallItems(hoveredDeletableWallItem, setWallItems, wallItems, socket);
}
}
};
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
const onDrop = (event: any) => {
if (!event.dataTransfer?.files[0]) return
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
if (AssetConfigurations[(event.dataTransfer.files[0].name.split('.'))[0]]) {
const selected = (event.dataTransfer.files[0].name.split('.'))[0];
if (AssetConfigurations[selected]?.type) {
AddWallItems(selected, raycaster, CSGGroup, AssetConfigurations, setWallItems, socket);
}
event.preventDefault();
}
}
const onDragOver = (event: any) => {
event.preventDefault();
};
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("drop", onDrop);
canvasElement.addEventListener("dragover", onDragOver);
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("drop", onDrop);
canvasElement.removeEventListener("dragover", onDragOver);
};
}, [deleteModels, wallItems])
useEffect(() => {
if (deleteModels) {
handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex);
setSelectedWallItem(null);
setSelectedItemsIndex(null);
}
}, [deleteModels])
return (
<>
{wallItems.map((item: Types.WallItem, index: number) => (
<group
key={index}
position={item.position}
quaternion={item.quaternion}
scale={item.scale}
>
<Csg
position={item.csgposition!}
scale={item.csgscale!}
model={item.model!}
hoveredDeletableWallItem={hoveredDeletableWallItem}
/>
</group>
))}
</>
)
}
export default WallItemsGroup;

View File

@ -0,0 +1,56 @@
import { Geometry } from "@react-three/csg";
import { useDeleteModels, useSelectedWallItem, useToggleView, useTransformMode, useWallItems, useWalls } from "../../../store/store";
import handleMeshDown from "../eventFunctions/handleMeshDown";
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
import WallsMesh from "./wallsMesh";
import WallItemsGroup from "./wallItemsGroup";
import { useEffect } from "react";
const WallsAndWallItems = ({ CSGGroup, AssetConfigurations, setSelectedItemsIndex, selectedItemsIndex, currentWallItem, csg, lines, hoveredDeletableWallItem }: any) => {
const { walls, setWalls } = useWalls();
const { wallItems, setWallItems } = useWallItems();
const { toggleView, setToggleView } = useToggleView();
const { deleteModels, setDeleteModels } = useDeleteModels();
const { transformMode, setTransformMode } = useTransformMode();
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
useEffect(() => {
if (transformMode === null) {
if (!deleteModels) {
handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex);
setSelectedWallItem(null);
setSelectedItemsIndex(null);
}
}
}, [transformMode])
return (
<mesh
ref={CSGGroup as any}
name="Walls"
key={walls.length}
receiveShadow
visible={!toggleView}
onClick={(event) => {
if (!deleteModels && transformMode !== null) {
handleMeshDown(event, currentWallItem, setSelectedWallItem, setSelectedItemsIndex, wallItems, toggleView);
}
}}
onPointerMissed={() => {
if (!deleteModels) {
handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex);
setSelectedWallItem(null);
setSelectedItemsIndex(null);
}
}}
>
<Geometry ref={csg as any} computeVertexNormals useGroups>
<WallsMesh lines={lines} />
<WallItemsGroup currentWallItem={currentWallItem} AssetConfigurations={AssetConfigurations} hoveredDeletableWallItem={hoveredDeletableWallItem} selectedItemsIndex={selectedItemsIndex} setSelectedItemsIndex={setSelectedItemsIndex} CSGGroup={CSGGroup} />
</Geometry>
</mesh>
)
}
export default WallsAndWallItems;

View File

@ -0,0 +1,65 @@
import * as THREE from 'three';
import * as Types from '../world/worldTypes';
import * as CONSTANTS from '../world/worldConstants';
import { Base } from '@react-three/csg';
import { MeshDiscardMaterial } from '@react-three/drei';
import { useUpdateScene, useWalls } from '../../../store/store';
import { useEffect } from 'react';
import { getLines } from '../../../services/factoryBuilder/lines/getLinesApi';
import objectLinesToArray from '../geomentries/lines/lineConvertions/objectLinesToArray';
import loadWalls from '../geomentries/walls/loadWalls';
const WallsMesh = ({ lines }: any) => {
const { walls, setWalls } = useWalls();
const { updateScene, setUpdateScene } = useUpdateScene();
useEffect(() => {
if (updateScene) {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
getLines(organization).then((data) => {
const Lines: Types.Lines = objectLinesToArray(data);
localStorage.setItem("Lines", JSON.stringify(Lines));
if (Lines) {
loadWalls(lines, setWalls);
}
})
setUpdateScene(false);
}
}, [updateScene])
return (
<>
{walls.map((wall: Types.Wall, index: number) => (
<mesh key={index}>
<Base
name={`Wall${index + 1}`}
geometry={wall[0]}
rotation={wall[1]}
position={wall[2]}
userData={{ WallType: wall[3], Layer: wall[4] }}
>
<meshStandardMaterial
side={THREE.DoubleSide}
color={CONSTANTS.wallConfig.defaultColor}
/>
</Base>
<mesh
castShadow
geometry={wall[0]}
rotation={wall[1]}
position={wall[2]}
name={`WallRaycastReference_${index + 1}`}
>
<MeshDiscardMaterial />
</mesh>
</mesh>
))}
</>
)
}
export default WallsMesh;

View File

@ -0,0 +1,467 @@
import React, { useState, useEffect, useMemo, useRef } from "react";
import { Line, Sphere } from "@react-three/drei";
import { useThree, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { useActiveLayer, useDeleteModels, useDeletePointOrLine, useMovePoint, useSocketStore, useToggleView, useToolMode, useRemovedLayer, useZones, useZonePoints, useUpdateScene } from "../../../store/store";
import { setZonesApi } from "../../../services/factoryBuilder/zones/setZonesApi";
import { deleteZonesApi } from "../../../services/factoryBuilder/zones/deleteZoneApi";
import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi";
import * as CONSTANTS from '../world/worldConstants';
const ZoneGroup: React.FC = () => {
const { camera, pointer, gl, raycaster, scene, controls } = useThree();
const [startPoint, setStartPoint] = useState<THREE.Vector3 | null>(null);
const [endPoint, setEndPoint] = useState<THREE.Vector3 | null>(null);
const { zones, setZones } = useZones();
const { zonePoints, setZonePoints } = useZonePoints();
const [isDragging, setIsDragging] = useState(false);
const [draggedSphere, setDraggedSphere] = useState<THREE.Vector3 | null>(null);
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
const { removedLayer, setRemovedLayer } = useRemovedLayer();
const { toolMode, setToolMode } = useToolMode();
const { movePoint, setMovePoint } = useMovePoint();
const { deleteModels, setDeleteModels } = useDeleteModels();
const { activeLayer, setActiveLayer } = useActiveLayer();
const { socket } = useSocketStore();
const groupsRef = useRef<any>();
const zoneMaterial = useMemo(() => new THREE.ShaderMaterial({
side: THREE.DoubleSide,
vertexShader: `
varying vec2 vUv;
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vUv = uv;
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 uColor;
void main(){
float alpha = 1.0 - vUv.y;
gl_FragColor = vec4(uColor, alpha);
}
`,
uniforms: {
uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) },
},
transparent: true,
}), []);
useEffect(() => {
const fetchZones = async () => {
const email = localStorage.getItem('email');
if (!email) return;
const organization = email.split("@")[1].split(".")[0];
const data = await getZonesApi(organization);
if (data.data && data.data.length > 0) {
const fetchedZones = data.data.map((zone: any) => ({
zoneId: zone.zoneId,
zoneName: zone.zoneName,
points: zone.points,
layer: zone.layer
}));
setZones(fetchedZones);
const fetchedPoints = data.data.flatMap((zone: any) =>
zone.points.slice(0, 4).map((point: [number, number, number]) => new THREE.Vector3(...point))
);
setZonePoints(fetchedPoints);
}
};
fetchZones();
}, []);
useEffect(() => {
const zonesWithVectors = zones.map((zone: any) => (zone.points.map((point: [number, number, number]) => new THREE.Vector3(...point))));
}, [zones])
useEffect(() => {
if (removedLayer) {
const updatedZones = zones.filter((zone: any) => zone.layer !== removedLayer);
setZones(updatedZones);
const updatedzonePoints = zonePoints.filter((_: any, index: any) => {
const zoneIndex = Math.floor(index / 4);
return zones[zoneIndex]?.layer !== removedLayer;
});
setZonePoints(updatedzonePoints);
zones.filter((zone: any) => zone.layer === removedLayer).forEach((zone: any) => {
deleteZoneFromBackend(zone.zoneId);
});
setRemovedLayer(null);
}
}, [removedLayer]);
useEffect(() => {
if (toolMode !== "Zone") {
setStartPoint(null);
setEndPoint(null);
} else {
setDeletePointOrLine(false);
setMovePoint(false);
setDeleteModels(false);
}
if (!toggleView) {
setStartPoint(null);
setEndPoint(null);
}
}, [toolMode, toggleView]);
const addZoneToBackend = async (zone: { zoneId: string; zoneName: string; points: [number, number, number][]; layer: string }) => {
const email = localStorage.getItem('email');
const userId = localStorage.getItem('userId');
const organization = (email!.split("@")[1]).split(".")[0];
const input = {
userId: userId,
organization: organization,
zoneData: {
zoneName: zone.zoneName,
zoneId: zone.zoneId,
points: zone.points,
layer: zone.layer
}
}
socket.emit('v2:zone:set', input);
};
const updateZoneToBackend = async (zone: { zoneId: string; zoneName: string; points: [number, number, number][]; layer: string }) => {
const email = localStorage.getItem('email');
const userId = localStorage.getItem('userId');
const organization = (email!.split("@")[1]).split(".")[0];
const input = {
userId: userId,
organization: organization,
zoneData: {
zoneName: zone.zoneName,
zoneId: zone.zoneId,
points: zone.points,
layer: zone.layer
}
}
socket.emit('v2:zone:set', input);
};
const deleteZoneFromBackend = async (zoneId: string) => {
const email = localStorage.getItem('email');
const userId = localStorage.getItem('userId');
const organization = (email!.split("@")[1]).split(".")[0];
const input = {
userId: userId,
organization: organization,
zoneId: zoneId
}
socket.emit('v2:zone:delete', input);
};
const handleDeleteZone = (zoneId: string) => {
const updatedZones = zones.filter((zone: any) => zone.zoneId !== zoneId);
setZones(updatedZones);
const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === zoneId);
if (zoneIndex !== -1) {
const zonePointsToRemove = zonePoints.slice(zoneIndex * 4, zoneIndex * 4 + 4);
zonePointsToRemove.forEach((point: any) => groupsRef.current.remove(point));
const updatedzonePoints = zonePoints.filter((_: any, index: any) => index < zoneIndex * 4 || index >= zoneIndex * 4 + 4);
setZonePoints(updatedzonePoints);
}
deleteZoneFromBackend(zoneId);
};
useEffect(() => {
if (!camera || !toggleView) return;
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(groupsRef.current.children, true);
if (intersects.length > 0 && movePoint) {
const clickedObject = intersects[0].object;
const sphereIndex = zonePoints.findIndex((point: any) => point.equals(clickedObject.position));
if (sphereIndex !== -1) {
(controls as any).enabled = false;
setDraggedSphere(zonePoints[sphereIndex]);
setIsDragging(true);
}
}
}
};
const onMouseUp = (evt: any) => {
if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) {
isLeftMouseDown = false;
if (!startPoint && !movePoint) {
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
setStartPoint(point);
setEndPoint(null);
}
} else if (startPoint && !movePoint) {
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (!point) return;
const points = [
[startPoint.x, 0.15, startPoint.z],
[point.x, 0.15, startPoint.z],
[point.x, 0.15, point.z],
[startPoint.x, 0.15, point.z],
[startPoint.x, 0.15, startPoint.z],
] as [number, number, number][];
const zoneName = `Zone ${zones.length + 1}`;
const zoneId = THREE.MathUtils.generateUUID();
const newZone = {
zoneId,
zoneName,
points: points,
layer: activeLayer
};
const newZones = [...zones, newZone];
setZones(newZones);
const newzonePoints = [
new THREE.Vector3(startPoint.x, 0.15, startPoint.z),
new THREE.Vector3(point.x, 0.15, startPoint.z),
new THREE.Vector3(point.x, 0.15, point.z),
new THREE.Vector3(startPoint.x, 0.15, point.z),
];
const updatedZonePoints = [...zonePoints, ...newzonePoints];
setZonePoints(updatedZonePoints);
addZoneToBackend(newZone);
setStartPoint(null);
setEndPoint(null);
}
} else if (evt.button === 0 && !drag && !isDragging && deletePointOrLine) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(groupsRef.current.children, true);
if (intersects.length > 0) {
const clickedObject = intersects[0].object;
const sphereIndex = zonePoints.findIndex((point: any) => point.equals(clickedObject.position));
if (sphereIndex !== -1) {
const zoneIndex = Math.floor(sphereIndex / 4);
const zoneId = zones[zoneIndex].zoneId;
handleDeleteZone(zoneId);
return;
}
}
}
if (evt.button === 0) {
if (isDragging && draggedSphere) {
setIsDragging(false);
setDraggedSphere(null);
const sphereIndex = zonePoints.findIndex((point: any) => point === draggedSphere);
if (sphereIndex !== -1) {
const zoneIndex = Math.floor(sphereIndex / 4);
if (zoneIndex !== -1 && zones[zoneIndex]) {
updateZoneToBackend(zones[zoneIndex]);
}
}
}
}
};
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(groupsRef.current.children, true);
if (intersects.length > 0 && intersects[0].object.name.includes('point')) {
gl.domElement.style.cursor = movePoint ? "pointer" : "default";
} else {
gl.domElement.style.cursor = "default";
}
if (isDragging && draggedSphere) {
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
draggedSphere.set(point.x, 0.15, point.z);
const sphereIndex = zonePoints.findIndex((point: any) => point === draggedSphere);
if (sphereIndex !== -1) {
const zoneIndex = Math.floor(sphereIndex / 4);
const cornerIndex = sphereIndex % 4;
const updatedZones = zones.map((zone: any, index: number) => {
if (index === zoneIndex) {
const updatedPoints = [...zone.points];
updatedPoints[cornerIndex] = [point.x, 0.15, point.z];
updatedPoints[4] = updatedPoints[0];
return { ...zone, points: updatedPoints };
}
return zone;
});
setZones(updatedZones);
}
}
}
};
const onContext = (event: any) => {
event.preventDefault();
setStartPoint(null);
setEndPoint(null);
};
if (toolMode === 'Zone' || deletePointOrLine || movePoint) {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("contextmenu", onContext);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("contextmenu", onContext);
};
}, [gl, camera, startPoint, toggleView, scene, toolMode, zones, isDragging, deletePointOrLine, zonePoints, draggedSphere, movePoint, activeLayer]);
useFrame(() => {
if (!startPoint) return;
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
setEndPoint(point);
}
});
return (
<group ref={groupsRef} name='zoneGroup' >
<group name="zones" visible={!toggleView}>
{zones
.map((zone: any) => (
<group key={zone.zoneId} name={zone.zoneName}>
{zone.points.slice(0, -1).map((point: [number, number, number], index: number) => {
const nextPoint = zone.points[index + 1];
const point1 = new THREE.Vector3(point[0], point[1], point[2]);
const point2 = new THREE.Vector3(nextPoint[0], nextPoint[1], nextPoint[2]);
const planeWidth = point1.distanceTo(point2);
const planeHeight = CONSTANTS.wallConfig.height;
const midpoint = new THREE.Vector3((point1.x + point2.x) / 2, (CONSTANTS.wallConfig.height / 2) + ((zone.layer - 1) * CONSTANTS.wallConfig.height), (point1.z + point2.z) / 2);
const angle = Math.atan2(point2.z - point1.z, point2.x - point1.x);
return (
<mesh
key={index}
position={midpoint}
rotation={[0, -angle, 0]}
>
<planeGeometry args={[planeWidth, planeHeight]} />
<primitive
object={zoneMaterial.clone()}
attach="material"
/>
</mesh>
);
})}
</group>
))}
</group>
<group name='zoneLines' visible={toggleView}>
{zones
.filter((zone: any) => zone.layer === activeLayer)
.map((zone: any) => (
<Line
key={zone.zoneId}
points={zone.points}
color="#007BFF"
lineWidth={3}
onClick={(e) => {
e.stopPropagation();
if (deletePointOrLine) {
handleDeleteZone(zone.zoneId);
}
}}
/>
))}
</group>
<group name="zonePoints" visible={toggleView}>
{zones.filter((zone: any) => zone.layer === activeLayer).flatMap((zone: any) => (
zone.points.slice(0, 4).map((point: any, pointIndex: number) => (
<Sphere
key={`${zone.zoneId}-point-${pointIndex}`}
position={new THREE.Vector3(...point)}
args={[0.3, 16, 16]}
name={`point-${zone.zoneId}-${pointIndex}`}
>
<meshBasicMaterial color="red" />
</Sphere>
))
))}
</group>
<group name="tempGroup" visible={toggleView}>
{startPoint && endPoint && (
<Line
points={[
[startPoint.x, 0.15, startPoint.z],
[endPoint.x, 0.15, startPoint.z],
[endPoint.x, 0.15, endPoint.z],
[startPoint.x, 0.15, endPoint.z],
[startPoint.x, 0.15, startPoint.z],
]}
color="#C164FF"
lineWidth={3}
/>
)}
</group>
</group>
);
};
export default ZoneGroup;

View File

@ -0,0 +1,245 @@
import { useEffect } from "react";
import * as THREE from 'three';
import * as Types from '../world/worldTypes';
import * as CONSTANTS from "../world/worldConstants";
import { useActiveLayer, useSocketStore, useDeleteModels, useDeletePointOrLine, useMovePoint, useToggleView, useUpdateScene, useNewLines, useToolMode } from "../../../store/store";
import { useThree } from "@react-three/fiber";
import arrayLineToObject from "../geomentries/lines/lineConvertions/arrayLineToObject";
import addPointToScene from "../geomentries/points/addPointToScene";
import addLineToScene from "../geomentries/lines/addLineToScene";
import removeSoloPoint from "../geomentries/points/removeSoloPoint";
import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
import getClosestIntersection from "../geomentries/lines/getClosestIntersection";
import loadZones from "../geomentries/zones/loadZones";
const ZoneGroup = ({ zoneGroup, plane, floorPlanGroupLine, floorPlanGroupPoint, line, lines, currentLayerPoint, dragPointControls, floorPlanGroup, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
const { toggleView, setToggleView } = useToggleView();
const { deleteModels, setDeleteModels } = useDeleteModels();
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
const { toolMode, setToolMode } = useToolMode();
const { movePoint, setMovePoint } = useMovePoint();
const { socket } = useSocketStore();
const { activeLayer } = useActiveLayer();
const { gl, raycaster, camera, pointer } = useThree();
const { updateScene, setUpdateScene } = useUpdateScene();
const { newLines, setNewLines } = useNewLines();
useEffect(() => {
if (updateScene) {
loadZones(lines, zoneGroup);
setUpdateScene(false);
}
}, [updateScene])
useEffect(() => {
if (toolMode === "Zone") {
setDeletePointOrLine(false);
setMovePoint(false);
setDeleteModels(false);
} else {
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
}, [toolMode])
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseUp = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
}
}
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
const onContextMenu = (e: any) => {
e.preventDefault();
if (toolMode === "Zone") {
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
}
};
const onMouseClick = (evt: any) => {
if (!plane.current || drag) return;
const intersects = raycaster.intersectObject(plane.current, true);
let intersectionPoint = intersects[0].point;
const points = floorPlanGroupPoint.current?.children ?? [];
const intersectsPoint = raycaster.intersectObjects(points, true).find(intersect => intersect.object.visible);
let intersectsLines: any = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
if (intersectsLines.length > 0 && intersects && intersects.length > 0 && !intersectsPoint) {
const lineType = intersectsLines[0].object.userData.linePoints[0][3];
if (lineType === CONSTANTS.lineConfig.zoneName) {
// console.log("intersected a zone line");
const ThroughPoint = (intersectsLines[0].object.geometry.parameters.path).getPoints(300);
let intersection = getClosestIntersection(ThroughPoint, intersectionPoint);
if (!intersection) return;
const point = addPointToScene(intersection, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
(line.current as Types.Line).push([new THREE.Vector3(intersection.x, 0.01, intersection.z), point.uuid, activeLayer, CONSTANTS.lineConfig.zoneName,]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current]);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
}
}
} else if (intersectsPoint && intersects && intersects.length > 0) {
if (intersectsPoint.object.userData.type === CONSTANTS.lineConfig.zoneName) {
// console.log("intersected a zone point");
intersectionPoint = intersectsPoint.object.position;
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), intersectsPoint.object.uuid, activeLayer, CONSTANTS.lineConfig.zoneName]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current]);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
ispreSnapped.current = false;
isSnapped.current = false;
}
}
} else if (intersects && intersects.length > 0) {
// console.log("intersected a empty area");
let uuid: string = "";
if (isAngleSnapped.current && anglesnappedPoint.current && line.current.length > 0) {
intersectionPoint = anglesnappedPoint.current;
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
uuid = point.uuid;
} else if (isSnapped.current && snappedPoint.current && line.current.length > 0) {
intersectionPoint = snappedPoint.current;
uuid = isSnappedUUID.current!;
} else if (ispreSnapped.current && snappedPoint.current) {
intersectionPoint = snappedPoint.current;
uuid = isSnappedUUID.current!;
} else {
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
uuid = point.uuid;
}
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), uuid, activeLayer, CONSTANTS.lineConfig.zoneName]);
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
lines.current.push(line.current as Types.Line);
const data = arrayLineToObject(line.current as Types.Line);
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setLine(organization, data.layer!, data.line!, data.type!);
//SOCKET
const input = {
organization: organization,
layer: data.layer,
line: data.line,
type: data.type,
socketId: socket.id
}
socket.emit('v1:Line:create', input);
setNewLines([line.current]);
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
let lastPoint = line.current[line.current.length - 1];
line.current = [lastPoint];
ispreSnapped.current = false;
isSnapped.current = false;
}
}
}
if (toolMode === 'Zone') {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("click", onMouseClick);
canvasElement.addEventListener("contextmenu", onContextMenu);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [toolMode])
return (
<group ref={zoneGroup} visible={!toggleView} name="zoneGroup">
</group>
)
}
export default ZoneGroup;

View File

@ -0,0 +1,190 @@
import * as THREE from 'three';
import { useEffect, useRef, useState } from 'react';
import { useThree, useFrame } from '@react-three/fiber';
import { useToolMode } from '../../../store/store';
import { Html } from '@react-three/drei';
const MeasurementTool = () => {
const { gl, raycaster, pointer, camera, scene } = useThree();
const { toolMode } = useToolMode();
const [points, setPoints] = useState<THREE.Vector3[]>([]);
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(null);
const groupRef = useRef<THREE.Group>(null);
const [startConePosition, setStartConePosition] = useState<THREE.Vector3 | null>(null);
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(null);
const [startConeQuaternion, setStartConeQuaternion] = useState(new THREE.Quaternion());
const [endConeQuaternion, setEndConeQuaternion] = useState(new THREE.Quaternion());
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1;
const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4;
const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0;
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = () => {
isLeftMouseDown = true;
drag = false;
};
const onMouseUp = (evt: any) => {
isLeftMouseDown = false;
if (evt.button === 0 && !drag) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true).filter(intersect => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !(intersect.object.type === "GridHelper"));
if (intersects.length > 0) {
const intersectionPoint = intersects[0].point.clone();
if (points.length < 2) {
setPoints([...points, intersectionPoint]);
} else {
setPoints([intersectionPoint]);
}
}
}
};
const onMouseMove = () => {
if (isLeftMouseDown) drag = true;
};
const onContextMenu = (evt: any) => {
evt.preventDefault();
if (!drag) {
evt.preventDefault();
setPoints([]);
setTubeGeometry(null);
}
};
if (toolMode === "MeasurementScale") {
canvasElement.addEventListener("pointerdown", onMouseDown);
canvasElement.addEventListener("pointermove", onMouseMove);
canvasElement.addEventListener("pointerup", onMouseUp);
canvasElement.addEventListener("contextmenu", onContextMenu);
} else {
resetMeasurement();
setPoints([]);
}
return () => {
canvasElement.removeEventListener("pointerdown", onMouseDown);
canvasElement.removeEventListener("pointermove", onMouseMove);
canvasElement.removeEventListener("pointerup", onMouseUp);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [toolMode, camera, raycaster, pointer, scene, points]);
useFrame(() => {
if (points.length === 1) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true).filter(intersect => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !(intersect.object.type === "GridHelper"));
if (intersects.length > 0) {
updateMeasurement(points[0], intersects[0].point);
}
} else if (points.length === 2) {
updateMeasurement(points[0], points[1]);
} else {
resetMeasurement();
}
});
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
const distance = start.distanceTo(end);
const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS);
const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS);
const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT);
setConeSize({ radius: coneRadius, height: coneHeight });
const direction = new THREE.Vector3().subVectors(end, start).normalize();
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
let tubeStart = start.clone().add(offset);
let tubeEnd = end.clone().sub(offset);
tubeStart.y = Math.max(tubeStart.y, 0);
tubeEnd.y = Math.max(tubeEnd.y, 0);
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
setStartConePosition(tubeStart);
setEndConePosition(tubeEnd);
setStartConeQuaternion(getArrowOrientation(start, end));
setEndConeQuaternion(getArrowOrientation(end, start));
};
const resetMeasurement = () => {
setTubeGeometry(null);
setStartConePosition(null);
setEndConePosition(null);
};
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
const direction = new THREE.Vector3().subVectors(end, start).normalize().negate();
const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
return quaternion;
};
useEffect(() => {
if (points.length === 2) {
console.log(points[0].distanceTo(points[1]));
}
}, [points])
return (
<group ref={groupRef} name="MeasurementGroup">
{startConePosition && (
<mesh name='MeasurementReference' position={startConePosition} quaternion={startConeQuaternion}>
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{endConePosition && (
<mesh name='MeasurementReference' position={endConePosition} quaternion={endConeQuaternion}>
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{tubeGeometry && (
<mesh name='MeasurementReference' geometry={tubeGeometry}>
<meshBasicMaterial color="yellow" />
</mesh>
)}
{startConePosition && endConePosition && (
<Html
as="div"
center
zIndexRange={[1, 0]}
style={{
padding: "10px",
color: "white",
borderRadius: "8px",
textAlign: "center",
fontFamily: "Arial, sans-serif",
}}
transform
sprite
scale={THREE.MathUtils.clamp(startConePosition.distanceTo(endConePosition) * 0.25, 0, 10)}
position={[(startConePosition.x + endConePosition.x) / 2, (startConePosition.y + endConePosition.y) / 2, (startConePosition.z + endConePosition.z) / 2]}
>
<div style={{ color: "black" }} >{startConePosition.distanceTo(endConePosition).toFixed(2)} m</div>
</Html>
)}
</group>
);
};
export default MeasurementTool;

View File

@ -0,0 +1,142 @@
import * as THREE from 'three';
import { useEffect, useRef, useState } from 'react';
import { useFrame } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import camModel from '../../../assets/models/gltf-glb/camera face 2.gltf';
import getActiveUsersData from '../../../services/factoryBuilder/collab/getActiveUsers';
import { useActiveUsers, useSocketStore } from '../../../store/store';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { useNavigate } from 'react-router-dom';
import { Text, Html } from '@react-three/drei';
import CollabUserIcon from './collabUserIcon';
import image from '../../../assets/images/userImage.png'
const CamModelsGroup = () => {
let navigate = useNavigate();
const groupRef = useRef<THREE.Group>(null);
const email = localStorage.getItem('email');
const { activeUsers, setActiveUsers } = useActiveUsers();
const { socket } = useSocketStore();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
const [cams, setCams] = useState<any[]>([]);
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler }>>({});
dracoLoader.setDecoderPath('three/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
useEffect(() => {
if (!email) {
navigate('/');
}
if (!socket) return;
const organization = email!.split('@')[1].split('.')[0];
socket.on('userConnectRespones', (data: any) => {
if (!groupRef.current) return;
if (data.data.userData.email === email) return
if (socket.id === data.socketId || organization !== data.organization) return;
const model = groupRef.current.getObjectByProperty('uuid', data.data.userData._id);
if (model) {
groupRef.current.remove(model);
}
loader.load(camModel, (gltf) => {
const newModel = gltf.scene.clone();
newModel.uuid = data.data.userData._id;
newModel.position.set(data.data.position.x, data.data.position.y, data.data.position.z);
newModel.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
newModel.userData = data.data.userData;
setCams((prev) => [...prev, newModel]);
setActiveUsers([...activeUsers, data.data.userData]);
});
});
socket.on('userDisConnectRespones', (data: any) => {
if (!groupRef.current) return;
if (socket.id === data.socketId || organization !== data.organization) return;
setCams((prev) => prev.filter((cam) => cam.uuid !== data.data.userData._id));
setActiveUsers(activeUsers.filter((user: any) => user._id !== data.data.userData._id));
});
socket.on('cameraUpdateResponse', (data: any) => {
if (!groupRef.current || socket.id === data.socketId || organization !== data.organization) return;
setModels((prev) => ({
...prev,
[data.data.userId]: {
targetPosition: new THREE.Vector3(data.data.position.x, data.data.position.y, data.data.position.z),
targetRotation: new THREE.Euler(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z),
},
}));
});
return () => {
socket.off('userConnectRespones');
socket.off('userDisConnectRespones');
socket.off('cameraUpdateResponse');
};
}, [socket]);
useFrame(() => {
if (!groupRef.current) return;
Object.keys(models).forEach((uuid) => {
const model = groupRef.current!.getObjectByProperty('uuid', uuid);
if (!model) return;
const { targetPosition, targetRotation } = models[uuid];
model.position.lerp(targetPosition, 0.1);
model.rotation.x = THREE.MathUtils.lerp(model.rotation.x, targetRotation.x, 0.1);
model.rotation.y = THREE.MathUtils.lerp(model.rotation.y, targetRotation.y, 0.1);
model.rotation.z = THREE.MathUtils.lerp(model.rotation.z, targetRotation.z, 0.1);
});
});
useEffect(() => {
if (!groupRef.current) return;
const organization = email!.split('@')[1].split('.')[0];
getActiveUsersData(organization).then((data) => {
const filteredData = data.cameraDatas.filter((camera: any) => camera.userData.email !== email);
if (filteredData.length > 0) {
loader.load(camModel, (gltf) => {
const newCams = filteredData.map((cam: any) => {
const newModel = gltf.scene.clone();
newModel.uuid = cam.userData._id;
newModel.position.set(cam.position.x, cam.position.y, cam.position.z);
newModel.rotation.set(cam.rotation.x, cam.rotation.y, cam.rotation.z);
newModel.userData = cam.userData;
setActiveUsers([...activeUsers, cam.userData]);
return newModel;
});
setCams((prev) => [...prev, ...newCams]);
});
}
});
}, []);
return (
<group ref={groupRef} name="Cam-Model-Group">
{cams.map((cam, index) => (
<primitive key={index} object={cam} >
<Html
as="div"
center
zIndexRange={[1, 0]}
sprite
style={{
color: "white",
textAlign: "center",
fontFamily: "Arial, sans-serif",
}}
position={[-0.015, 0, 0.7]}>
<CollabUserIcon color={"#ff0000"} userImage={image} userName={cam.userData.userName} />
</Html>
</primitive>
))}
</group>
);
};
export default CamModelsGroup;

View File

@ -0,0 +1,53 @@
import React from "react";
interface CollabUserIconProps {
color: string;
userImage: string;
userName: string;
}
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
color,
userImage,
userName,
}) => {
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
gap: "6px",
// transform:"translate(-20%, 0%)",
}}
>
<img
src={userImage}
alt={userName}
style={{
width: "30px",
height: "30px",
outline: `2px solid ${color}`,
borderRadius: "50%",
objectFit: 'cover'
}}
/>
<div
style={{
display: 'flex',
padding: "3px 5px",
backgroundColor: color,
borderRadius: "6px",
color: "white",
fontSize: "14px",
fontWeight: 400
}}
>
{userName}
</div>
</div>
);
};
export default CollabUserIcon;

View File

@ -0,0 +1,778 @@
import { useEffect } from "react";
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import gsap from 'gsap';
import { toast } from 'react-toastify';
import { useSocketStore, useActiveLayer, useWallItems, useFloorItems, useLayers, useUpdateScene, useWalls, useDeletedLines, useNewLines, useZonePoints, useZones } from "../../../store/store";
import * as Types from "../world/worldTypes";
import * as CONSTANTS from '../world/worldConstants';
import TempLoader from "../geomentries/assets/tempLoader";
import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
import objectLineToArray from "../geomentries/lines/lineConvertions/objectLineToArray";
import addLineToScene from "../geomentries/lines/addLineToScene";
import updateLinesPositions from "../geomentries/lines/updateLinesPositions";
import updateLines from "../geomentries/lines/updateLines";
import updateDistanceText from "../../3d-ui/functions/updateDistanceText";
import updateFloorLines from "../geomentries/floors/updateFloorLines";
import loadWalls from "../geomentries/walls/loadWalls";
import deletePoint from "../geomentries/points/deletePoint";
import RemoveConnectedLines from "../geomentries/lines/removeConnectedLines";
import Layer2DVisibility from "../geomentries/layers/layer2DVisibility";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { retrieveGLTF, storeGLTF } from "../indexDB/idbUtils";
import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi";
export default function SocketResponses({
floorPlanGroup,
lines,
floorGroup,
floorGroupAisle,
scene,
onlyFloorlines,
AssetConfigurations,
itemsGroup,
isTempLoader,
tempLoader,
currentLayerPoint,
floorPlanGroupPoint,
floorPlanGroupLine,
zoneGroup,
dragPointControls
}: any) {
const { socket } = useSocketStore();
const { activeLayer, setActiveLayer } = useActiveLayer();
const { wallItems, setWallItems } = useWallItems();
const { layers, setLayers } = useLayers();
const { floorItems, setFloorItems } = useFloorItems();
const { updateScene, setUpdateScene } = useUpdateScene();
const { walls, setWalls } = useWalls();
const { deletedLines, setDeletedLines } = useDeletedLines();
const { newLines, setNewLines } = useNewLines();
const { zones, setZones } = useZones();
const { zonePoints, setZonePoints } = useZonePoints();
useEffect(() => {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
if (!socket) return
socket.on('cameraCreateResponse', (data: any) => {
// console.log('data: ', data);
})
socket.on('userConnectRespones', (data: any) => {
// console.log('data: ', data);
})
socket.on('userDisConnectRespones', (data: any) => {
// console.log('data: ', data);
})
socket.on('cameraUpdateResponse', (data: any) => {
// console.log('data: ', data);
})
socket.on('EnvironmentUpdateResponse', (data: any) => {
// console.log('data: ', data);
})
socket.on('FloorItemsUpdateResponse', async (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "flooritem created") {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
try {
isTempLoader.current = true;
const cachedModel = THREE.Cache.get(data.data.modelname);
let url;
if (cachedModel) {
// console.log(`Getting ${data.data.modelname} from cache`);
url = URL.createObjectURL(cachedModel);
} else {
const indexedDBModel = await retrieveGLTF(data.data.modelname);
if (indexedDBModel) {
// console.log(`Getting ${data.data.modelname} from IndexedDB`);
url = URL.createObjectURL(indexedDBModel);
} else {
// console.log(`Getting ${data.data.modelname} from Backend`);
url = `${url_Backend_dwinzo}/api/v1/AssetFile/${data.data.modelfileID}`;
const modelBlob = await fetch(url).then((res) => res.blob());
await storeGLTF(data.data.modelfileID, modelBlob);
}
}
loadModel(url);
} catch (error) {
console.error('Error fetching asset model:', error);
}
function loadModel(url: string) {
loader.load(url, (gltf) => {
URL.revokeObjectURL(url);
THREE.Cache.remove(url);
const model = gltf.scene;
model.uuid = data.data.modeluuid;
model.userData = { name: data.data.modelname, modelId: data.data.modelFileID };
model.position.set(...data.data.position as [number, number, number]);
model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
model.traverse((child: any) => {
if (child.isMesh) {
// Clone the material to ensure changes are independent
// child.material = child.material.clone();
child.castShadow = true;
child.receiveShadow = true;
}
});
itemsGroup.current.add(model);
if (tempLoader.current) {
tempLoader.current.material.dispose();
tempLoader.current.geometry.dispose();
itemsGroup.current.remove(tempLoader.current);
tempLoader.current = undefined;
}
const newFloorItem: Types.FloorItemType = {
modeluuid: data.data.modeluuid,
modelname: data.data.modelname,
modelfileID: data.data.modelfileID,
position: [...data.data.position as [number, number, number]],
rotation: {
x: model.rotation.x,
y: model.rotation.y,
z: model.rotation.z,
},
isLocked: data.data.isLocked,
isVisible: data.data.isVisible,
};
setFloorItems((prevItems: any) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
gsap.to(model.position, { y: data.data.position[1], duration: 1.5, ease: 'power2.out' });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out', onComplete: () => { toast.success("Model Added!") } });
THREE.Cache.add(data.data.modelname, gltf);
}, () => {
TempLoader(new THREE.Vector3(...data.data.position), isTempLoader, tempLoader, itemsGroup);
});
}
} else if (data.message === "flooritems updated") {
itemsGroup.current.children.forEach((item: THREE.Group) => {
if (item.uuid === data.data.modeluuid) {
item.position.set(...data.data.position as [number, number, number]);
item.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
}
})
setFloorItems((prevItems: Types.FloorItems) => {
if (!prevItems) {
return
}
let updatedItem: any = null;
const updatedItems = prevItems.map((item) => {
if (item.modeluuid === data.data.modeluuid) {
updatedItem = {
...item,
position: [...data.data.position] as [number, number, number],
rotation: { x: data.data.rotation.x, y: data.data.rotation.y, z: data.data.rotation.z, },
};
return updatedItem;
}
return item;
});
return updatedItems;
})
}
})
socket.on('FloorItemsDeleteResponse', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "flooritem deleted") {
const deletedUUID = data.data.modeluuid;
let items = JSON.parse(localStorage.getItem("FloorItems")!);
const updatedItems = items.filter(
(item: { modeluuid: string }) => item.modeluuid !== deletedUUID
);
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || '[]');
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => item.modeluuid !== deletedUUID);
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
itemsGroup.current.children.forEach((item: any) => {
if (item.uuid === deletedUUID) {
itemsGroup.current.remove(item);
}
})
setFloorItems(updatedItems);
toast.success("Model Removed!");
}
})
socket.on('Line:response:update', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "line updated") {
const DraggedUUID = data.data.uuid;
const DraggedPosition = new THREE.Vector3(data.data.position.x, data.data.position.y, data.data.position.z);
const point = floorPlanGroupPoint.current.getObjectByProperty('uuid', DraggedUUID);
point.position.set(DraggedPosition.x, DraggedPosition.y, DraggedPosition.z);
const affectedLines = updateLinesPositions({ uuid: DraggedUUID, position: DraggedPosition }, lines);
updateLines(floorPlanGroupLine, affectedLines);
updateDistanceText(scene, floorPlanGroupLine, affectedLines);
updateFloorLines(onlyFloorlines, { uuid: DraggedUUID, position: DraggedPosition });
loadWalls(lines, setWalls);
setUpdateScene(true);
}
})
socket.on('Line:response:delete', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "line deleted") {
const line = objectLineToArray(data.data);
const linePoints = line;
const connectedpoints = [linePoints[0][1], linePoints[1][1]];
onlyFloorlines.current = onlyFloorlines.current.map((floorline: any) =>
floorline.filter((line: any) => line[0][1] !== connectedpoints[0] && line[1][1] !== connectedpoints[1])
).filter((floorline: any) => floorline.length > 0);
const removedLine = lines.current.find((item: any) => (item[0][1] === linePoints[0][1] && item[1][1] === linePoints[1][1] || (item[0][1] === linePoints[1][1] && item[1][1] === linePoints[0][1])));
lines.current = lines.current.filter((item: any) => item !== removedLine);
floorPlanGroupLine.current.children.forEach((line: any) => {
const linePoints = line.userData.linePoints as [number, string, number][];
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if ((uuid1 === connectedpoints[0] && uuid2 === connectedpoints[1] || (uuid1 === connectedpoints[1] && uuid2 === connectedpoints[0]))) {
line.material.dispose();
line.geometry.dispose();
floorPlanGroupLine.current.remove(line);
setDeletedLines([line.userData.linePoints])
}
});
connectedpoints.forEach((pointUUID) => {
let isConnected = false;
floorPlanGroupLine.current.children.forEach((line: any) => {
const linePoints = line.userData.linePoints;
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === pointUUID || uuid2 === pointUUID) {
isConnected = true;
}
});
if (!isConnected) {
floorPlanGroupPoint.current.children.forEach((point: any) => {
if (point.uuid === pointUUID) {
point.material.dispose();
point.geometry.dispose();
floorPlanGroupPoint.current.remove(point);
}
});
}
});
loadWalls(lines, setWalls);
setUpdateScene(true);
toast.success("Line Removed!");
}
})
socket.on('Line:response:delete:point', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "point deleted") {
const point = floorPlanGroupPoint.current?.getObjectByProperty('uuid', data.data);
point.material.dispose();
point.geometry.dispose();
floorPlanGroupPoint.current.remove(point);
onlyFloorlines.current = onlyFloorlines.current.map((floorline: any) =>
floorline.filter((line: any) => line[0][1] !== data.data && line[1][1] !== data.data)
).filter((floorline: any) => floorline.length > 0);
RemoveConnectedLines(data.data, floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, lines);
loadWalls(lines, setWalls);
setUpdateScene(true);
toast.success("Point Removed!");
}
})
socket.on('Line:response:delete:layer', async (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "layer deleted") {
setActiveLayer(1)
const removedLayer = data.data;
const removedLines: Types.Lines = lines.current.filter((line: any) => line[0][2] === removedLayer);
////////// Remove Points and lines from the removed layer //////////
removedLines.forEach(async (line) => {
line.forEach(async (removedPoint) => {
const removableLines: THREE.Mesh[] = [];
const connectedpoints: string[] = [];
floorPlanGroupLine.current.children.forEach((line: any) => {
const linePoints = line.userData.linePoints as [number, string, number][];
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === removedPoint[1] || uuid2 === removedPoint[1]) {
connectedpoints.push(uuid1 === removedPoint[1] ? uuid2 : uuid1);
removableLines.push(line as THREE.Mesh);
}
});
if (removableLines.length > 0) {
removableLines.forEach((line: any) => {
lines.current = lines.current.filter((item: any) => JSON.stringify(item) !== JSON.stringify(line.userData.linePoints));
line.material.dispose();
line.geometry.dispose();
floorPlanGroupLine.current.remove(line);
});
}
const point = floorPlanGroupPoint.current.getObjectByProperty('uuid', removedPoint[1]);
if (point) {
point.material.dispose();
point.geometry.dispose();
floorPlanGroupPoint.current.remove(point)
}
});
});
////////// Update the remaining lines layer values in the userData and in lines.current //////////
let remaining = lines.current.filter((line: any) => line[0][2] !== removedLayer);
let updatedLines: Types.Lines = [];
remaining.forEach((line: any) => {
let newLines = JSON.parse(JSON.stringify(line));
if (newLines[0][2] > removedLayer) {
newLines[0][2] -= 1;
newLines[1][2] -= 1;
}
const matchingLine = floorPlanGroupLine.current.children.find((l: any) => l.userData.linePoints[0][1] === line[0][1] && l.userData.linePoints[1][1] === line[1][1]);
if (matchingLine) {
const updatedUserData = JSON.parse(JSON.stringify(matchingLine.userData));
updatedUserData.linePoints[0][2] = newLines[0][2];
updatedUserData.linePoints[1][2] = newLines[1][2];
matchingLine.userData = updatedUserData;
}
updatedLines.push(newLines);
});
lines.current = updatedLines;
localStorage.setItem("Lines", JSON.stringify(lines.current));
////////// Also remove OnlyFloorLines and update it in localstorage //////////
onlyFloorlines.current = onlyFloorlines.current.filter((floor: any) => {
return floor[0][0][2] !== removedLayer;
});
const meshToRemove = floorGroup.current?.children.find((mesh: any) =>
mesh.name === `Only_Floor_Line_${removedLayer}`
);
if (meshToRemove) {
meshToRemove.geometry.dispose();
meshToRemove.material.dispose();
floorGroup.current?.remove(meshToRemove);
}
const zonesData = await getZonesApi(organization);
const highestLayer = Math.max(
1,
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
zonesData.data.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
);
setLayers(highestLayer);
loadWalls(lines, setWalls);
setUpdateScene(true);
toast.success("Layer Removed!");
}
})
}, [socket])
useEffect(() => {
if (!socket) return
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
socket.on('wallItemsDeleteResponse', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "wallitem deleted") {
const deletedUUID = data.data.modeluuid;
let WallItemsRef = wallItems;
const Items = WallItemsRef.filter((item: any) => item.model?.uuid !== deletedUUID);
setWallItems([]);
setTimeout(async () => {
WallItemsRef = Items;
setWallItems(WallItemsRef);
const WallItemsForStorage = WallItemsRef.map((item: any) => {
const { model, ...rest } = item;
return {
...rest,
modeluuid: model?.uuid,
};
});
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
toast.success("Model Removed!");
}, 50);
}
})
socket.on('wallItemsUpdateResponse', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "wallIitem created") {
const loader = new GLTFLoader();
loader.load(AssetConfigurations[data.data.modelname].modelUrl, async (gltf) => {
const model = gltf.scene;
model.uuid = data.data.modeluuid;
model.children[0].children.forEach((child) => {
if (child.name !== "CSG_REF") {
child.castShadow = true;
child.receiveShadow = true;
}
});
const newWallItem = {
type: data.data.type,
model: model,
modelname: data.data.modelname,
scale: data.data.scale,
csgscale: data.data.csgscale,
csgposition: data.data.csgposition,
position: data.data.position,
quaternion: data.data.quaternion
};
setWallItems((prevItems: any) => {
const updatedItems = [...prevItems, newWallItem];
const WallItemsForStorage = updatedItems.map(item => {
const { model, ...rest } = item;
return {
...rest,
modeluuid: model?.uuid,
};
});
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
toast.success("Model Added!");
return updatedItems;
});
});
} else if (data.message === "wallIitem updated") {
const updatedUUID = data.data.modeluuid;
setWallItems((prevItems: any) => {
const updatedItems = prevItems.map((item: any) => {
if (item.model.uuid === updatedUUID) {
return {
...item,
position: data.data.position,
quaternion: data.data.quaternion,
scale: data.data.scale,
csgscale: data.data.csgscale,
csgposition: data.data.csgposition,
};
}
return item;
});
const WallItemsForStorage = updatedItems.map((item: any) => {
const { model, ...rest } = item;
return {
...rest,
modeluuid: model?.uuid,
};
});
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
toast.success("Model Updated!");
return updatedItems;
});
}
})
return () => {
socket.off('wallItemsDeleteResponse');
socket.off('wallItemsUpdateResponse');
};
}, [wallItems])
function getPointColor(lineType: string | undefined): string {
switch (lineType) {
case CONSTANTS.lineConfig.wallName: return CONSTANTS.pointConfig.wallOuterColor;
case CONSTANTS.lineConfig.floorName: return CONSTANTS.pointConfig.floorOuterColor;
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.pointConfig.aisleOuterColor;
default: return CONSTANTS.pointConfig.defaultOuterColor;
}
}
function getLineColor(lineType: string | undefined): string {
switch (lineType) {
case CONSTANTS.lineConfig.wallName: return CONSTANTS.lineConfig.wallColor;
case CONSTANTS.lineConfig.floorName: return CONSTANTS.lineConfig.floorColor;
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.lineConfig.aisleColor;
default: return CONSTANTS.lineConfig.defaultColor;
}
}
useEffect(() => {
if (!socket) return
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
socket.on('Line:response:create', async (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "line create") {
const line: Types.Line = objectLineToArray(data.data);
const type = line[0][3];
const pointColour = getPointColor(type);
const lineColour = getLineColor(type);
setNewLines([line])
line.forEach((line) => {
const existingPoint = floorPlanGroupPoint.current?.getObjectByProperty('uuid', line[1]);
if (existingPoint) {
return;
}
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
const material = new THREE.ShaderMaterial({
uniforms: {
uColor: { value: new THREE.Color(pointColour) }, // Blue color for the border
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 uColor;
uniform vec3 uInnerColor;
void main() {
// Define the size of the white square as a proportion of the face
float borderThickness = 0.2; // Adjust this value for border thickness
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
} else {
gl_FragColor = vec4(uColor, 1.0); // Blue border
}
}
`,
});
const point = new THREE.Mesh(geometry, material);
point.name = "point";
point.uuid = line[1];
point.userData = { type: type, color: pointColour };
point.position.set(line[0].x, line[0].y, line[0].z);
currentLayerPoint.current.push(point);
floorPlanGroupPoint.current?.add(point);
})
if (dragPointControls.current) {
dragPointControls.current!.objects = currentLayerPoint.current;
}
addLineToScene(
new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z),
new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z),
lineColour,
line,
floorPlanGroupLine
)
lines.current.push(line);
const zonesData = await getZonesApi(organization);
const highestLayer = Math.max(
1,
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
zonesData.data.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
);
setLayers(highestLayer);
Layer2DVisibility(activeLayer, floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, currentLayerPoint, dragPointControls)
loadWalls(lines, setWalls);
setUpdateScene(true);
}
})
return () => {
socket.off('Line:response:create');
};
}, [socket, activeLayer])
useEffect(() => {
if (!socket) return
const email = localStorage.getItem('email');
const organization = (email!.split("@")[1]).split(".")[0];
socket.on('zone:response:updates', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "zone created") {
const pointsArray: [number, number, number][] = data.data.points;
const vector3Array = pointsArray.map(([x, y, z]) => new THREE.Vector3(x, y, z));
const newZones = [...zones, data.data];
setZones(newZones);
const updatedZonePoints = [...zonePoints, ...vector3Array];
setZonePoints(updatedZonePoints);
const highestLayer = Math.max(
1,
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
newZones.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
);
setLayers(highestLayer);
setUpdateScene(true);
}
if (data.message === "zone updated") {
const updatedZones = zones.map((zone: any) =>
zone.zoneId === data.data.zoneId ? data.data : zone
);
setZones(updatedZones);
setUpdateScene(true);
}
})
socket.on('zone:response:delete', (data: any) => {
if (socket.id === data.socketId) {
return
}
if (organization !== data.organization) {
return
}
if (data.message === "zone deleted") {
const updatedZones = zones.filter((zone: any) => zone.zoneId !== data.data.zoneId);
setZones(updatedZones);
const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === data.data.zoneId);
if (zoneIndex !== -1) {
const updatedzonePoints = zonePoints.filter((_: any, index: any) => index < zoneIndex * 4 || index >= zoneIndex * 4 + 4);
setZonePoints(updatedzonePoints);
}
const highestLayer = Math.max(
1,
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
updatedZones.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
);
setLayers(highestLayer);
setUpdateScene(true);
}
})
return () => {
socket.off('zone:response:updates');
socket.off('zone:response:updates');
};
}, [socket, zones, zonePoints])
return (
<></>
)
}

Some files were not shown because too many files have changed in this diff Show More