Merge remote-tracking branch 'origin/main-demo' into decal-list

This commit is contained in:
2025-08-29 12:46:48 +05:30
67 changed files with 3526 additions and 1234 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,353 @@
export const ForkLiftIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_6014_655)">
<g filter="url(#filter0_f_6014_655)">
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<defs>
<filter
id="filter0_f_6014_655"
x="2.14674"
y="4.11427"
width="17"
height="11.7715"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="0.233881"
result="effect1_foregroundBlur_6014_655"
/>
</filter>
<clipPath id="clip0_6014_655">
<rect
width="18.7105"
height="18.7105"
fill="white"
transform="translate(0.644775 0.644531)"
/>
</clipPath>
</defs>
</svg>
);
};
export const ConveyorIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_352_182)">
<path
d="M8.16336 9.72707H8.82742C8.80273 9.66301 8.7893 9.59363 8.7893 9.52144V6.32707C8.7893 6.1677 8.85492 6.02238 8.95961 5.91738C9.06586 5.8127 9.21086 5.74707 9.3693 5.74707H12.8659C13.0255 5.74707 13.1705 5.81238 13.2755 5.91738C13.3805 6.02238 13.4455 6.16707 13.4455 6.32707V9.52144C13.4455 9.59363 13.4321 9.66301 13.4074 9.72707H15.918C16.4902 9.72707 17.0105 9.96113 17.3874 10.3377C17.764 10.7146 17.998 11.2349 17.998 11.8071C17.998 12.3793 17.764 12.8996 17.3871 13.2761C17.0105 13.653 16.4896 13.8874 15.918 13.8874H4.07836C3.50617 13.8874 2.98586 13.6533 2.60867 13.2761C2.23211 12.8996 1.99805 12.3793 1.99805 11.8071C1.99805 11.2361 2.23242 10.7158 2.60961 10.3383C2.8793 10.0677 3.22305 9.87082 3.60742 9.78113C3.56655 9.70067 3.54524 9.61169 3.54523 9.52144V6.32707C3.54523 6.1677 3.61086 6.02238 3.71555 5.91738C3.82148 5.8127 3.9668 5.74707 4.12492 5.74707H7.6218C7.78148 5.74707 7.92617 5.81238 8.03117 5.91738C8.13617 6.02238 8.20148 6.16707 8.20148 6.32707V9.52144C8.20148 9.59363 8.18805 9.66301 8.16336 9.72707ZM15.6268 6.7652L16.823 7.89488L15.5705 9.08207L15.183 8.67488L15.6805 8.20363L14.1824 8.20582V7.64426L15.7365 7.64207L15.2415 7.17457L15.6268 6.7652ZM5.28961 6.6077H6.46648L6.48617 8.05176L5.8543 7.66645L5.26117 8.04238L5.28961 6.6077ZM7.6218 6.2402H4.12492C4.10117 6.2402 4.0793 6.24988 4.06367 6.26551C4.04762 6.282 4.03856 6.30406 4.03836 6.32707V9.52144C4.03836 9.54551 4.04773 9.56738 4.06305 9.58269C4.07961 9.59832 4.10117 9.60801 4.12492 9.60801H7.6218C7.64492 9.60801 7.6668 9.59801 7.68273 9.58238C7.69836 9.56644 7.70836 9.54457 7.70836 9.52144V6.32707C7.70836 6.30301 7.69867 6.28145 7.68305 6.26582C7.66742 6.24988 7.64555 6.2402 7.6218 6.2402ZM10.5334 6.6077H11.7102L11.7299 8.05176L11.098 7.66645L10.5046 8.04238L10.5334 6.6077ZM12.8659 6.2402H9.3693C9.34523 6.2402 9.32336 6.24988 9.30773 6.26551C9.29169 6.282 9.28262 6.30406 9.28242 6.32707V9.52144C9.28242 9.54551 9.29211 9.56738 9.30711 9.58269C9.32367 9.59832 9.34523 9.60801 9.3693 9.60801H12.8659C12.889 9.60801 12.9109 9.59801 12.9268 9.58238C12.9424 9.56644 12.9524 9.54457 12.9524 9.52144V6.32707C12.9524 6.30301 12.9427 6.28145 12.9271 6.26582C12.9115 6.24988 12.8896 6.2402 12.8659 6.2402ZM14.7877 11.0064C15.2299 11.0064 15.5887 11.3649 15.5887 11.8071C15.5887 12.2493 15.2299 12.608 14.7877 12.608C14.3455 12.608 13.9871 12.2493 13.9871 11.8071C13.9871 11.3649 14.3455 11.0064 14.7877 11.0064ZM11.5946 11.0064C12.0368 11.0064 12.3952 11.3649 12.3952 11.8071C12.3952 12.2493 12.0368 12.608 11.5946 12.608C11.1521 12.608 10.7937 12.2493 10.7937 11.8071C10.7937 11.3649 11.1521 11.0064 11.5946 11.0064ZM8.40148 11.0064C8.84367 11.0064 9.20242 11.3649 9.20242 11.8071C9.20242 12.2493 8.84367 12.608 8.40148 12.608C7.9593 12.608 7.60086 12.2493 7.60086 11.8071C7.60086 11.3649 7.9593 11.0064 8.40148 11.0064ZM5.20836 11.0064C5.65055 11.0064 6.0093 11.3649 6.0093 11.8071C6.0093 12.2493 5.65055 12.608 5.20836 12.608C4.76617 12.608 4.40742 12.2493 4.40742 11.8071C4.40742 11.3649 4.76617 11.0064 5.20836 11.0064ZM15.918 10.3608H4.07836C3.68086 10.3608 3.3193 10.5239 3.05711 10.7861C2.7943 11.0474 2.63211 11.4089 2.63211 11.8071C2.63211 12.2043 2.79492 12.5661 3.05711 12.828C3.3193 13.0905 3.68117 13.2533 4.07836 13.2533H15.918C16.3149 13.2533 16.6768 13.0902 16.939 12.828C17.2012 12.5658 17.3643 12.2043 17.3643 11.8071C17.3643 11.4099 17.2012 11.0483 16.939 10.7861C16.6768 10.5239 16.3152 10.3608 15.918 10.3608Z"
fill="white"
/>
<path
d="M9.2334 9.76172L9.15527 6.13672H13.1396V9.76172H9.2334Z"
fill="white"
/>
<path
d="M3.9209 9.76172L3.84277 6.13672H7.82715V9.76172H3.9209Z"
fill="white"
/>
<path
d="M11.7178 6.60645H10.5303L10.499 8.05957L11.0928 7.66895L11.7334 8.05957L11.7178 6.60645Z"
fill="#494949"
/>
<path
d="M6.43555 6.60645H5.24805L5.2168 8.05957L5.81055 7.66895L6.45117 8.05957L6.43555 6.60645Z"
fill="#494949"
/>
</g>
<defs>
<clipPath id="clip0_352_182">
<rect
width="16"
height="8.14031"
fill="white"
transform="translate(1.99902 5.74707)"
/>
</clipPath>
</defs>
</svg>
);
};
export const RoboticArmIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.9796 9.68306L10.6387 7.13761L12.5478 5.22852L15.2523 8.25124L13.9796 9.68306Z"
fill="white"
/>
<rect
x="3.07955"
y="15.1713"
width="9.06818"
height="2.06818"
rx="0.238636"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<rect
x="4.59277"
y="10.9551"
width="2.86364"
height="4.13636"
fill="white"
/>
<rect
x="5.22656"
y="12.5459"
width="0.636364"
height="2.22727"
rx="0.318182"
fill="#494949"
/>
<rect
x="11.2617"
y="16.0898"
width="0.636364"
height="2.22727"
rx="0.318182"
transform="rotate(88.7682 11.2617 16.0898)"
fill="#494949"
/>
<rect
x="5.71387"
y="8.08301"
width="4.85107"
height="2.86364"
transform="rotate(-46.048 5.71387 8.08301)"
fill="white"
/>
<circle
cx="6.02326"
cy="10.161"
r="1.98864"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<circle
cx="10.4764"
cy="5.06916"
r="1.98864"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<circle cx="6.02379" cy="10.1615" r="1.11364" fill="#494949" />
<circle cx="10.4769" cy="5.06871" r="1.11364" fill="#494949" />
<path
d="M14.9316 8.17188C15.5026 8.17188 15.9656 8.63511 15.9658 9.20605V10.2402H13.8975V9.20605C13.8977 8.63522 14.3608 8.17207 14.9316 8.17188Z"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<path
d="M14.4561 10.1602H15.7295C16.3442 10.1604 16.8426 10.6587 16.8428 11.2734V13.291C16.8427 13.4108 16.7153 13.4875 16.6094 13.4316L15.834 13.0215V11.6855C15.8338 11.4222 15.6208 11.2092 15.3574 11.209H14.8799C14.6164 11.209 14.4025 11.4221 14.4023 11.6855V13.0205L13.5742 13.4424C13.4684 13.4963 13.3428 13.4186 13.3428 13.2998V11.2734C13.3429 10.6586 13.8412 10.1603 14.4561 10.1602Z"
stroke="white"
stroke-width="0.318182"
/>
<rect
x="8.43457"
y="6.18359"
width="0.636364"
height="2.22727"
rx="0.318182"
transform="rotate(46.5101 8.43457 6.18359)"
fill="#494949"
/>
<rect
x="14.0859"
y="7.48438"
width="0.636364"
height="1.34988"
rx="0.318182"
transform="rotate(131.749 14.0859 7.48438)"
fill="#494949"
/>
</svg>
);
};
export const MachineIcon = () => {
return (
<svg
width="21"
height="20"
viewBox="0 0 21 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.9369 3.38379V5.39368H10.7644V4.55635H9.5918V3.38379H11.9369Z"
fill="white"
/>
<path
d="M8.07979 12.0938H6.90723V16.2816H8.07979V12.0938Z"
fill="white"
/>
<path
d="M5.90694 12.0938H4.73438V16.2816H5.90694V12.0938Z"
fill="white"
/>
<path
d="M9.59346 4.55635V5.8964H8.4209V3.38379H9.59346V4.55635Z"
fill="white"
/>
<path
d="M6.40843 5.89694C6.96354 5.89694 7.41354 5.44693 7.41354 4.89183C7.41354 4.33672 6.96354 3.88672 6.40843 3.88672C5.85332 3.88672 5.40332 4.33672 5.40332 4.89183C5.40332 5.44693 5.85332 5.89694 6.40843 5.89694Z"
fill="white"
/>
<path
d="M11.418 5.08984V4.08984"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
<path
d="M10.7646 7.9055L10.9324 6.90039H12.2724L12.4399 7.9055H13.1097V9.91539H10.0947V7.9055H10.7646Z"
fill="white"
/>
<path
d="M14.7864 5.39258H13.7812V6.73295H14.7864V5.39258Z"
fill="white"
/>
<path d="M16.6304 16.2832H15.29V17.4558H16.6304V16.2832Z" fill="white" />
<path
d="M7.58266 16.2832V17.4558H4.23242V16.2832H4.73481H5.90737H7.07994H7.58266Z"
fill="white"
/>
<path d="M14.7864 13.9365H8.4209V15.1091H14.7864V13.9365Z" fill="white" />
<path
d="M7.08048 10.585V12.0925H5.90791H4.73535V10.585H7.08048Z"
fill="#494949"
/>
<path
d="M14.7862 11.2557H8.42069V13.9358V15.1083V16.2809H7.58337H7.08065V12.093V10.5855H4.73552V12.093V16.2809H4.23313H3.39551V7.23531H4.40059H8.42069V9.91537H10.096H13.1109H14.7862V6.73292V5.39258H17.1314V16.2809H16.629H15.2886H14.7862V15.1083V13.9358V11.2557Z"
fill="white"
/>
<path
d="M8.42343 5.89613V7.23615H4.40332V2.5459H8.4234V3.38355L8.42343 5.89613ZM7.41865 4.89102C7.41865 4.33596 6.96861 3.88591 6.41354 3.88591C5.85847 3.88591 5.40843 4.33596 5.40843 4.89102C5.40843 5.44609 5.85847 5.89613 6.41354 5.89613C6.96861 5.89613 7.41865 5.44609 7.41865 4.89102Z"
fill="white"
/>
<path
d="M7.41862 8.15625H4.40332V8.65963H7.41862V8.15625Z"
fill="#494949"
/>
<path
d="M7.41862 9.16211H4.40332V9.66549H7.41862V9.16211Z"
fill="#494949"
/>
<path
d="M6.41496 3.63379C5.722 3.63379 5.1582 4.19758 5.1582 4.89057C5.1582 5.58356 5.722 6.14736 6.41496 6.14736C7.10795 6.14736 7.67174 5.58356 7.67174 4.89057C7.67174 4.19758 7.10798 3.63379 6.41496 3.63379ZM6.41496 5.64401C5.99953 5.64401 5.66159 5.30603 5.66159 4.8906C5.66159 4.47518 5.99953 4.1372 6.41496 4.1372C6.83039 4.1372 7.16836 4.47518 7.16836 4.8906C7.16836 5.30603 6.83039 5.64401 6.41496 5.64401Z"
fill="#494949"
/>
<path
d="M6.74732 4.63965H6.07715V5.14303H6.74732V4.63965Z"
fill="#494949"
/>
<path
d="M8.91992 5.08984V4.08984"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
<path
d="M12.3857 9.08984H10.8857"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
<path
d="M15.9199 6.09082V12.5908"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
</svg>
);
};
type TypeBasedAssetIconsProps = {
assetType: string;
};
export function TypeBasedAssetIcons({ assetType }: TypeBasedAssetIconsProps) {
console.log("assetType: ", assetType);
return (
<div>
{assetType === "machine" && <MachineIcon />}
{assetType === "vehicle" && <ForkLiftIcon />}
{assetType === "transfer" && <ConveyorIcon />}
{assetType === "roboticArm" && <RoboticArmIcon />}
</div>
);
}

View File

@@ -1697,93 +1697,6 @@ export const TargetIcon = () => {
);
};
export const ForkLiftIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_6014_655)">
<g filter="url(#filter0_f_6014_655)">
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<defs>
<filter
id="filter0_f_6014_655"
x="2.14674"
y="4.11427"
width="17"
height="11.7715"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="0.233881"
result="effect1_foregroundBlur_6014_655"
/>
</filter>
<clipPath id="clip0_6014_655">
<rect
width="18.7105"
height="18.7105"
fill="white"
transform="translate(0.644775 0.644531)"
/>
</clipPath>
</defs>
</svg>
);
};
export const RightHalfFillCircleIcon = () => {
return (
<svg

View File

@@ -2,7 +2,11 @@ import React, { useEffect, useState } from "react";
import Search from "../../ui/inputs/Search";
import { getCategoryAsset } from "../../../services/factoryBuilder/asset/assets/getCategoryAsset";
import { fetchAssets } from "../../../services/marketplace/fetchAssets";
import { useDecalStore, useSelectedItem } from "../../../store/builder/store";
import {
useDecalStore,
useDroppedDecal,
useSelectedItem,
} from "../../../store/builder/store";
// images -------------------
import vehicle from "../../../assets/image/categories/vehicles.png";
@@ -17,11 +21,11 @@ import decal from "../../../assets/image/categories/decal.png";
import SkeletonUI from "../../templates/SkeletonUI";
import {
AlertIcon,
ArrowIcon,
DecalInfoIcon,
HangTagIcon,
NavigationIcon,
} from "../../icons/ExportCommonIcons";
import { assert } from "console";
import { getCategoryDecals } from "../../../services/factoryBuilder/asset/decals/getCategoryDecals";
// -------------------------------------
@@ -46,6 +50,7 @@ interface CategoryListProp {
}
const Assets: React.FC = () => {
const { setSelectedItem } = useSelectedItem();
const { setDroppedDecal } = useDroppedDecal();
const [searchValue, setSearchValue] = useState<string>("");
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [categoryAssets, setCategoryAssets] = useState<AssetProp[]>([]);
@@ -146,7 +151,12 @@ const Assets: React.FC = () => {
echo.error("failed to fetch assets");
setisLoading(false);
}
if (asset === "Decals") {
fetchCategoryDecals("Safety");
}
};
const fetchCategoryDecals = async (asset: any) => {
setisLoading(true);
// setSelectedCategory(asset);
@@ -185,7 +195,10 @@ const Assets: React.FC = () => {
<div className="assets-result">
<div className="assets-wrapper">
<div className="searched-content">
<p>Results for {searchValue}</p>
<p>
Results for{" "}
<span className="search-for">'{searchValue}'</span>
</p>
</div>
<div className="assets-container">
{selectedCategory == "Decals" ? (
@@ -291,7 +304,7 @@ const Assets: React.FC = () => {
if (selectedCategory) {
return (
<div className="assets-wrapper">
<h2>
<h2 className="header">
{selectedCategory}
<button
className="back-button"
@@ -302,7 +315,10 @@ const Assets: React.FC = () => {
setCategoryAssets([]);
}}
>
Back
<div className="back-arrow">
<ArrowIcon />
</div>
Back
</button>
</h2>
@@ -327,7 +343,6 @@ const Assets: React.FC = () => {
</div>
</>
)}
{selectedCategory !== "Decals" && !selectedSubCategory ? (
<div className="assets-container">
{categoryAssets?.map((asset: any, index: number) => (
@@ -386,15 +401,11 @@ const Assets: React.FC = () => {
alt={asset.decalName}
className="asset-image"
onPointerDown={() => {
setSelectedItem({
name: asset.decalName,
id: asset.id,
type:
asset.type === "undefined"
? undefined
: asset.type,
setDroppedDecal({
category: asset.category,
// subType: asset.subType,
decalName: asset.decalName,
decalImage: asset.decalImage,
decalId: asset.id,
});
}}
/>
@@ -423,7 +434,7 @@ const Assets: React.FC = () => {
return (
<div className="assets-wrapper">
<h2>Categories</h2>
<h2 className="categories-header">Categories</h2>
<div className="categories-container">
{Array.from(
new Set(categoryList.map((asset) => asset.category))

View File

@@ -1,29 +1,13 @@
import React, { useEffect, useState } from "react";
import Header from "./Header";
import useModuleStore, {
useSubModuleStore,
} from "../../../store/useModuleStore";
import {
AnalysisIcon,
FilePackageIcon,
MechanicsIcon,
PropertiesIcon,
SimulationIcon,
} from "../../icons/SimulationIcons";
import useModuleStore, { useSubModuleStore } from "../../../store/useModuleStore";
import { AnalysisIcon, FilePackageIcon, MechanicsIcon, PropertiesIcon, SimulationIcon, } from "../../icons/SimulationIcons";
import { useToggleStore } from "../../../store/useUIToggleStore";
import Visualization from "./visualization/Visualization";
import Analysis from "./analysis/Analysis";
import Simulations from "./simulation/Simulations";
import useVersionHistoryVisibleStore, {
useDecalStore,
useSaveVersion,
useSelectedFloorItem,
useToolMode,
} from "../../../store/builder/store";
import {
useSelectedEventData,
useSelectedEventSphere,
} from "../../../store/simulation/useSimulationStore";
import useVersionHistoryVisibleStore, { useSaveVersion, useSelectedFloorItem, useToolMode } from "../../../store/builder/store";
import { useSelectedEventData, useSelectedEventSphere, } from "../../../store/simulation/useSimulationStore";
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
import GlobalProperties from "./properties/GlobalProperties";
import AssetProperties from "./properties/AssetProperties";
@@ -35,8 +19,9 @@ import WallProperties from "./properties/WallProperties";
import FloorProperties from "./properties/FloorProperties";
import SelectedWallProperties from "./properties/SelectedWallProperties";
import SelectedFloorProperties from "./properties/SelectedFloorProperties";
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
import ResourceManagement from "./resourceManagement/ResourceManagement";
import DecalProperties from "./properties/DecalProperties";
type DisplayComponent =
| "versionHistory"
@@ -47,16 +32,18 @@ type DisplayComponent =
| "assetProperties"
| "selectedWallProperties"
| "selectedFloorProperties"
| "selectedDecalProperties"
| "selectedAisleProperties"
| "zoneProperties"
| "simulations"
| "mechanics"
| "analysis"
| "visualization"
| "selectedDecalProperties"
| "resourceManagement"
| "none";
const SideBarRight: React.FC = () => {
const { selectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { toggleUIRight } = useToggleStore();
const { toolMode } = useToolMode();
@@ -65,13 +52,10 @@ const SideBarRight: React.FC = () => {
const { selectedWall, selectedFloor, selectedAisle } = useBuilderStore();
const { selectedEventData } = useSelectedEventData();
const { selectedEventSphere } = useSelectedEventSphere();
const { viewVersionHistory, setVersionHistoryVisible } =
useVersionHistoryVisibleStore();
const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { isVersionSaved } = useSaveVersion();
const { selectedSubCategory } = useDecalStore();
const [displayComponent, setDisplayComponent] =
useState<DisplayComponent>("none");
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
useEffect(() => {
if (activeModule !== "simulation") setSubModule("properties");
@@ -79,11 +63,7 @@ const SideBarRight: React.FC = () => {
}, [activeModule, setSubModule]);
useEffect(() => {
if (
activeModule !== "mechanics" &&
selectedEventData &&
selectedEventSphere
) {
if (activeModule !== "mechanics" && selectedEventData && selectedEventSphere) {
setSubModule("mechanics");
} else if (!selectedEventData && !selectedEventSphere) {
if (activeModule === "simulation") {
@@ -132,38 +112,27 @@ const SideBarRight: React.FC = () => {
setDisplayComponent("assetProperties");
return;
}
if (
!selectedFloorItem &&
!selectedFloor &&
!selectedAisle &&
selectedWall
) {
if (!selectedFloorItem && !selectedFloor && !selectedAisle && !selectedDecal && selectedWall) {
setDisplayComponent("selectedWallProperties");
return;
}
if (
!selectedFloorItem &&
!selectedWall &&
!selectedAisle &&
selectedFloor
) {
if (!selectedFloorItem && !selectedWall && !selectedAisle && !selectedDecal && selectedFloor) {
setDisplayComponent("selectedFloorProperties");
return;
}
if (viewVersionHistory) {
if (viewVersionHistory && !selectedFloorItem && !selectedWall && !selectedAisle && !selectedFloor && !selectedDecal) {
setDisplayComponent("versionHistory");
return;
}
if (selectedSubCategory) {
if (!selectedFloorItem && !selectedFloor && !selectedAisle && !selectedWall && selectedDecal) {
setDisplayComponent("selectedDecalProperties");
return;
}
if (
!selectedFloorItem &&
!selectedFloor &&
!selectedWall &&
!selectedSubCategory
) {
if (!selectedFloorItem && !selectedFloor && !selectedWall && !selectedDecal && selectedAisle) {
setDisplayComponent("selectedAisleProperties");
return;
}
if (!selectedFloorItem && !selectedFloor && !selectedWall && !selectedDecal && !selectedAisle) {
if (toolMode === "Aisle") {
setDisplayComponent("aisleProperties");
return;
@@ -181,27 +150,13 @@ const SideBarRight: React.FC = () => {
}
}
if (
subModule === "zoneProperties" &&
(activeModule === "builder" || activeModule === "simulation")
) {
if (subModule === "zoneProperties" && (activeModule === "builder" || activeModule === "simulation")) {
setDisplayComponent("zoneProperties");
return;
}
setDisplayComponent("none");
}, [
viewVersionHistory,
activeModule,
subModule,
isVersionSaved,
selectedFloorItem,
selectedWall,
selectedFloor,
selectedAisle,
toolMode,
selectedSubCategory,
]);
}, [viewVersionHistory, activeModule, subModule, isVersionSaved, selectedFloorItem, selectedWall, selectedFloor, selectedAisle, toolMode, selectedDecal]);
const renderComponent = () => {
switch (displayComponent) {
@@ -217,12 +172,16 @@ const SideBarRight: React.FC = () => {
return <FloorProperties />;
case "assetProperties":
return <AssetProperties />;
case "zoneProperties":
return <ZoneProperties />;
case "selectedWallProperties":
return <SelectedWallProperties />;
case "selectedFloorProperties":
return <SelectedFloorProperties />;
case "zoneProperties":
return <ZoneProperties />;
case "selectedDecalProperties":
return <SelectedDecalProperties />;
case "selectedAisleProperties":
return <SelectedAisleProperties />;
case "simulations":
return <Simulations />;
case "mechanics":
@@ -231,8 +190,6 @@ const SideBarRight: React.FC = () => {
return <Analysis />;
case "visualization":
return <Visualization />;
case "selectedDecalProperties":
return <DecalProperties />;
case "resourceManagement":
return <ResourceManagement />;
default:
@@ -241,13 +198,7 @@ const SideBarRight: React.FC = () => {
};
return (
<div
className={`sidebar-right-wrapper ${
toggleUIRight && (!isVersionSaved || activeModule !== "simulation")
? "open"
: "closed"
}`}
>
<div className={`sidebar-right-wrapper ${toggleUIRight && (!isVersionSaved || activeModule !== "simulation") ? "open" : "closed"}`}>
<Header />
{toggleUIRight && (
<>
@@ -257,8 +208,7 @@ const SideBarRight: React.FC = () => {
<>
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${
subModule === "properties" ? "active" : ""
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
}`}
onClick={() => {
setSubModule("properties");
@@ -275,8 +225,7 @@ const SideBarRight: React.FC = () => {
<>
<button
id="sidebar-action-list-simulation"
className={`sidebar-action-list ${
subModule === "simulations" ? "active" : ""
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
}`}
onClick={() => {
setSubModule("simulations");
@@ -288,8 +237,7 @@ const SideBarRight: React.FC = () => {
</button>
<button
id="sidebar-action-list-mechanics"
className={`sidebar-action-list ${
subModule === "mechanics" ? "active" : ""
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
}`}
onClick={() => {
setSubModule("mechanics");
@@ -301,8 +249,7 @@ const SideBarRight: React.FC = () => {
</button>
<button
id="sidebar-action-list-analysis"
className={`sidebar-action-list ${
subModule === "analysis" ? "active" : ""
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
}`}
onClick={() => {
setSubModule("analysis");
@@ -319,8 +266,7 @@ const SideBarRight: React.FC = () => {
activeModule === "simulation") && (
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${
subModule === "resourceManagement" ? "active" : ""
className={`sidebar-action-list ${subModule === "resourceManagement" ? "active" : ""
}`}
onClick={() => {
setSubModule("resourceManagement");

View File

@@ -1,30 +1,46 @@
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)
heading?: string;
label?: string;
onChange: (value: string) => void;
placeholder?: string;
type?: string;
value?: number;
disabled?: boolean;
min?: number;
max?: number;
step?: number;
}
const RotationInput: React.FC<RotationInputProps> = ({
label = "Rotate :",
heading = "Rotation",
onChange,
placeholder = "Enter value", // Default placeholder
type = "number", // Default type
value = "number",
placeholder = "Enter value",
type = "number",
value,
disabled = false,
min,
max,
step,
}) => {
return (
<div className="custom-input-container">
<div className="header">Rotation</div>
<div className="header">{heading}</div>
<div className="inputs-container" style={{ display: "block" }}>
<div className="input-container">
<div className="custom-input-label">Rotate : </div>
<div className="custom-input-label">{label}</div>
<input
className="custom-input-field"
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
value={value}
disabled={disabled}
min={min}
max={max}
step={step}
/>
</div>
</div>

View File

@@ -21,24 +21,25 @@ interface TextureItem {
texture: string;
}
export const aisleTextureList: TextureItem[] = [
{ color: "yellow", id: "#FBE50E", brief: "pedestrian walkways", texture: "" },
{ color: "gray", id: "#6F6F7A", brief: "basic", texture: "" },
{ color: "green", id: "#43C06D", brief: "pedestrian walkways", texture: "" },
{ color: "orange", id: "#FF711B", brief: "material flow", texture: "" },
{ color: "blue", id: "#488EF6", brief: "vehicle paths", texture: "" },
{ color: "purple", id: "#AF52DE", brief: "material flow", texture: "" },
{ color: "red", id: "#FF3B30", brief: "safety zone", texture: "" },
{ color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" },
{ color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" },
{ color: "white-black", id: "white-black", brief: "utility aisles", texture: "" },
];
const AisleProperties: React.FC = () => {
const [collapsePresets, setCollapsePresets] = useState(false);
const [collapseTexture, setCollapseTexture] = useState(true);
const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength, setIsFlipped } = useBuilderStore();
const aisleTextureList: TextureItem[] = [
{ color: "yellow", id: "yellow", brief: "pedestrian walkways", texture: "" },
{ color: "gray", id: "gray", brief: "basic", texture: "" },
{ color: "green", id: "green", brief: "pedestrian walkways", texture: "" },
{ color: "orange", id: "orange", brief: "material flow", texture: "" },
{ color: "blue", id: "blue", brief: "vehicle paths", texture: "" },
{ color: "purple", id: "purple", brief: "material flow", texture: "" },
{ color: "red", id: "red", brief: "safety zone", texture: "" },
{ color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" },
{ color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" },
{ color: "white-black", id: "white-black", brief: "utility aisles", texture: "" },
];
const aisleTypes: {
name: string;
@@ -285,7 +286,12 @@ const AisleProperties: React.FC = () => {
onClick={() => setAisleColor(val.id)}
aria-pressed={aisleColor === val.id}
>
<div className="texture-display">{val.texture}</div>
<div
className={`texture-display ${val.id}`}
style={{ background: val.id }}
>
{val.texture}
</div>
<div className="aisle-color">{val.color}</div>
<div className="aisle-brief">{`( ${val.brief} )`}</div>
</button>

View File

@@ -57,6 +57,7 @@ const AssetProperties: React.FC = () => {
<section>
{objectPosition && (
<PositionInput
disabled={true}
onChange={() => { }}
value1={parseFloat(objectPosition.x.toFixed(5))}
value2={parseFloat(objectPosition.z.toFixed(5))}
@@ -64,6 +65,7 @@ const AssetProperties: React.FC = () => {
)}
{objectRotation && (
<RotationInput
disabled={true}
onChange={() => { }}
value={parseFloat(objectRotation.y.toFixed(5))}
/>
@@ -107,7 +109,7 @@ const AssetProperties: React.FC = () => {
if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations)
return (
i === 0 && (
<div className="no-animation">
<div className="no-animation" key={i}>
Looks like there are no preset animations yet. Stay tuned for
future additions!
</div>

View File

@@ -1,53 +0,0 @@
import {
LayeringBottomIcon,
LayeringTopIcon,
} from "../../../icons/ExportCommonIcons";
import InputRange from "../../../ui/inputs/InputRange";
import RotationInput from "../customInput/RotationInput";
import Vector3Input from "../customInput/Vector3Input";
const DecalProperties = () => {
return (
<div className="decal-transformation-container">
<div className="header">Decal Propertis</div>
<section>
<RotationInput
onChange={() => {}}
value={10}
/>
<Vector3Input
onChange={(value) => console.log(value)}
header="Scale"
value={[0, 0, 0] as [number, number, number]}
/>
</section>
<section>
<InputRange
label="Opacity"
value={1}
min={0}
step={0.1}
max={1}
onChange={(value: number) => console.log(value)}
key={"6"}
/>
<div className="transformation-wrapper opacity">
<div className="transformation-header">Layering</div>
<div className="layers-list">
<button className="layer-move-btn">
<LayeringBottomIcon />
</button>
<button className="layer-move-btn">
<LayeringTopIcon />
</button>
</div>
</div>
</section>
</div>
);
};
export default DecalProperties;

View File

@@ -0,0 +1,529 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { ArrowIcon } from "../../../icons/ExportCommonIcons";
// image imports
import Arc from "../../../../assets/image/aisleTypes/Arc.png";
import Arrow from "../../../../assets/image/aisleTypes/Arrow.png";
import Arrows from "../../../../assets/image/aisleTypes/Arrows.png";
import Circle from "../../../../assets/image/aisleTypes/Circle.png";
import Dashed from "../../../../assets/image/aisleTypes/Dashed.png";
import Directional from "../../../../assets/image/aisleTypes/Directional.png";
import Dotted from "../../../../assets/image/aisleTypes/Dotted.png";
import Solid from "../../../../assets/image/aisleTypes/Solid.png";
import InputToggle from "../../../ui/inputs/InputToggle";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import { useVersionContext } from "../../../../modules/builder/version/versionContext";
import { useSocketStore } from "../../../../store/builder/store";
import { getUserData } from "../../../../functions/getUserData";
import { aisleTextureList } from "./AisleProperties";
const SelectedAisleProperties: React.FC = () => {
const [collapsePresets, setCollapsePresets] = useState(false);
const [collapseTexture, setCollapseTexture] = useState(true);
const { aisleStore } = useSceneContext();
const { getAisleById, updateAisle, setDashedAisleProperties, setDottedAisleProperties, setArrowsAisleProperties, setArcAisleWidth, setColor } = aisleStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { socket } = useSocketStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const [selectedAisleData, setSelectedAisleData] = useState<Aisle | undefined>();
const { selectedAisle, setSelectedAisle } = useBuilderStore();
useEffect(() => {
const aisleData = getAisleById(selectedAisle?.aisleMesh?.uuid || "");
setSelectedAisleData(aisleData);
}, [selectedAisle, getAisleById]);
if (!selectedAisleData) return null;
const updateBackend = (updatedAisle: Aisle) => {
if (updatedAisle && projectId) {
// API
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
}
socket.emit('v1:model-aisle:add', data);
}
}
const aisleTypes: {
name: string;
type: AisleTypes;
id: string;
thumbnail: string;
}[] = [
{ name: "Solid", type: "solid-aisle", id: "1", thumbnail: Solid },
{ name: "Dotted", type: "dotted-aisle", id: "2", thumbnail: Dotted },
{ name: "Dashed", type: "dashed-aisle", id: "3", thumbnail: Dashed },
{ name: "Arrow", type: "arrow-aisle", id: "4", thumbnail: Arrow },
{ name: "Continuous Arrows", type: "arrows-aisle", id: "5", thumbnail: Arrows },
{ name: "Directional", type: "junction-aisle", id: "6", thumbnail: Directional },
{ name: "Arc", type: "arc-aisle", id: "7", thumbnail: Arc },
{ name: "Circle", type: "circle-aisle", id: "8", thumbnail: Circle },
];
const createAisleTypeObject = (newType: AisleTypes, currentType: AisleType): AisleType => {
switch (newType) {
case 'solid-aisle':
return {
aisleType: 'solid-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
} as SolidAisle;
case 'dashed-aisle':
return {
aisleType: 'dashed-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
dashLength: 'dashLength' in currentType ? (currentType as DashedAisle).dashLength : 0.5,
gapLength: 'gapLength' in currentType ? (currentType as DashedAisle).gapLength : 0.3
} as DashedAisle;
case 'dotted-aisle':
return {
aisleType: 'dotted-aisle',
aisleColor: currentType.aisleColor,
dotRadius: 'dotRadius' in currentType ? (currentType as DottedAisle).dotRadius : 0.1,
gapLength: 'gapLength' in currentType ? (currentType as DottedAisle).gapLength : 0.3
} as DottedAisle;
case 'arrow-aisle':
return {
aisleType: 'arrow-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
} as ArrowAisle;
case 'arrows-aisle':
return {
aisleType: 'arrows-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
aisleLength: 'aisleLength' in currentType ? (currentType as ArrowsAisle).aisleLength : 0.6,
gapLength: 'gapLength' in currentType ? (currentType as ArrowsAisle).gapLength : 0.3
} as ArrowsAisle;
case 'arc-aisle':
return {
aisleType: 'arc-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
isFlipped: 'isFlipped' in currentType ? (currentType as ArcAisle).isFlipped : false
} as ArcAisle;
case 'circle-aisle':
return {
aisleType: 'circle-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
} as CircleAisle;
case 'junction-aisle':
return {
aisleType: 'junction-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
isFlipped: 'isFlipped' in currentType ? (currentType as JunctionAisle).isFlipped : false
} as JunctionAisle;
default:
return {
aisleType: 'solid-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 0.1
} as SolidAisle;
}
};
const handleAisleTypeChange = (newType: AisleTypes) => {
if (!selectedAisle?.aisleData) return;
const newAisleType = createAisleTypeObject(newType, selectedAisleData.type);
const updatedAisle = updateAisle(selectedAisleData.aisleUuid, {
type: newAisleType
});
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: null });
setSelectedAisleData({
...selectedAisleData,
type: newAisleType
});
if (updatedAisle) {
updateBackend(updatedAisle);
}
};
const handleColorChange = (value: AisleColors) => {
const updatedAisle = setColor(selectedAisleData.aisleUuid, value);
setSelectedAisleData({
...selectedAisleData,
type: {
...selectedAisleData.type,
aisleColor: value
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
};
const handleAisleWidthChange = (value: string) => {
const width = parseFloat(value);
if (!isNaN(width) && selectedAisleData.type.aisleType !== 'dotted-aisle') {
const updatedAisle = updateAisle(selectedAisleData.aisleUuid, {
type: {
...selectedAisleData.type,
aisleWidth: width
}
});
setSelectedAisleData({
...selectedAisleData,
type: {
...selectedAisleData.type,
aisleWidth: width
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleDashLengthChange = (value: string) => {
const length = parseFloat(value);
if (!isNaN(length) && selectedAisleData.type.aisleType === 'dashed-aisle') {
const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, {
dashLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DashedAisle),
dashLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleGapLengthChange = (value: string) => {
const length = parseFloat(value);
if (!isNaN(length) && (selectedAisleData.type.aisleType === 'dashed-aisle' || selectedAisleData.type.aisleType === 'dotted-aisle' || selectedAisleData.type.aisleType === 'arrows-aisle')) {
if (selectedAisleData.type.aisleType === 'dashed-aisle') {
const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, {
gapLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DashedAisle),
gapLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
} else if (selectedAisleData.type.aisleType === 'dotted-aisle') {
const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, {
gapLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DottedAisle),
gapLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
} else if (selectedAisleData.type.aisleType === 'arrows-aisle') {
const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, {
gapLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as ArrowsAisle),
gapLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
}
};
const handleDotRadiusChange = (value: string) => {
const radius = parseFloat(value);
if (!isNaN(radius) && selectedAisleData.type.aisleType === 'dotted-aisle') {
const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, {
dotRadius: radius
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DottedAisle),
dotRadius: radius
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleAisleLengthChange = (value: string) => {
const length = parseFloat(value);
if (!isNaN(length) && selectedAisleData.type.aisleType === 'arrows-aisle') {
const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, {
aisleLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as ArrowsAisle),
aisleLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleIsFlippedChange = () => {
if (selectedAisleData.type.aisleType === 'arc-aisle' || selectedAisleData.type.aisleType === 'junction-aisle') {
const currentType = selectedAisleData.type as ArcAisle | JunctionAisle;
const currentFlipped = currentType.isFlipped || false;
const updatedAisle = setArcAisleWidth(selectedAisleData.aisleUuid, {
isFlipped: !currentFlipped
});
setSelectedAisleData({
...selectedAisleData,
type: {
...currentType,
isFlipped: !currentFlipped
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const renderAdvancedProperties = () => {
switch (selectedAisleData.type.aisleType) {
case 'dashed-aisle':
const dashedType = selectedAisleData.type as DashedAisle;
return (
<>
<InputWithDropDown
label="Dash Length"
value={`${dashedType.dashLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleDashLengthChange}
/>
<InputWithDropDown
label="Gap Length"
value={`${dashedType.gapLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleGapLengthChange}
/>
</>
);
case 'dotted-aisle':
const dottedType = selectedAisleData.type as DottedAisle;
return (
<>
<InputWithDropDown
label="Dot Radius"
value={`${dottedType.dotRadius}`}
min={0.1}
step={0.1}
max={2}
onChange={handleDotRadiusChange}
/>
<InputWithDropDown
label="Gap Length"
value={`${dottedType.gapLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleGapLengthChange}
/>
</>
);
case 'arrows-aisle':
const arrowsType = selectedAisleData.type as ArrowsAisle;
return (
<>
<InputWithDropDown
label="Arrow Length"
value={`${arrowsType.aisleLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleAisleLengthChange}
/>
<InputWithDropDown
label="Gap Length"
value={`${arrowsType.gapLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleGapLengthChange}
/>
</>
);
case 'junction-aisle':
case 'arc-aisle':
const flippedType = selectedAisleData.type as ArcAisle | JunctionAisle;
return (
<InputToggle
inputKey="Flip Aisle"
label="Flip Aisle"
value={flippedType.isFlipped || false}
onClick={handleIsFlippedChange}
/>
);
default:
return null;
}
};
return (
<div className="aisle-properties-container">
<div className="header">Properties</div>
{/* Basic Properties */}
<section>
{selectedAisleData.type.aisleType !== 'dotted-aisle' &&
<InputWithDropDown
label="Aisle Width"
value={`${selectedAisleData.type.aisleWidth || 0.5}`}
min={0.1}
step={0.1}
max={2}
onChange={handleAisleWidthChange}
/>
}
{renderAdvancedProperties()}
</section>
{/* Presets */}
<section>
<button
className="header"
onClick={() => setCollapsePresets(!collapsePresets)}
aria-expanded={!collapsePresets}
>
<div className="value">Presets</div>
<div className="icon">
<ArrowIcon />
</div>
</button>
{!collapsePresets && (
<div className="presets-list-container">
{aisleTypes.map((val) => (
<div className="preset-list" key={val.id}>
<button
className={`thumbnail ${selectedAisleData.type.aisleType === val.type ? "selected" : ""}`}
title={val.name}
onClick={() => handleAisleTypeChange(val.type)}
>
<img src={val.thumbnail} alt="" />
</button>
</div>
))}
</div>
)}
</section>
{/* Texture */}
<section>
<button
className="header"
onClick={() => setCollapseTexture(!collapseTexture)}
aria-expanded={!collapseTexture}
>
<div className="value">Aisle Texture</div>
<div className="icon" style={{ rotate: collapseTexture ? "" : "-90deg" }}>
<ArrowIcon />
</div>
</button>
{collapseTexture && (
<div className="aisle-texture-container">
{aisleTextureList.map((val) => (
<button
key={val.id}
title={val.brief || val.id}
className={`aisle-list ${selectedAisleData.type.aisleColor === val.id ? "selected" : ""}`}
onClick={() => handleColorChange(val.id)}
aria-pressed={selectedAisleData.type.aisleColor === val.id}
>
<div
className={`texture-display ${val.id}`}
style={{ background: val.id }}
>
{val.texture}
</div>
<div className="aisle-color">{val.color}</div>
<div className="aisle-brief">{`( ${val.brief} )`}</div>
</button>
))}
</div>
)}
</section>
</div>
);
};
export default SelectedAisleProperties;

View File

@@ -0,0 +1,188 @@
import { useParams } from "react-router-dom";
import { useVersionContext } from "../../../../modules/builder/version/versionContext";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons";
import { useSocketStore } from "../../../../store/builder/store";
import InputRange from "../../../ui/inputs/InputRange";
import { getUserData } from "../../../../functions/getUserData";
// import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi";
// import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
const SelectedDecalProperties = () => {
const { selectedDecal, setSelectedDecal } = useBuilderStore();
const { wallStore, floorStore } = useSceneContext();
const { updateDecal: updateDecalInWall } = wallStore();
const { updateDecal: updateDecalInFloor } = floorStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { socket } = useSocketStore();
const updateBackend = (updatedData: Wall | Floor) => {
if ('wallUuid' in updatedData) {
if (projectId && updatedData) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
wallData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
} else if ('floorUuid' in updatedData) {
if (projectId && updatedData) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
// SOCKET
const data = {
floorData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}
}
const handleRotationChange = (value: number) => {
if (!selectedDecal) return;
const updatedDecal = { ...selectedDecal.decalData, decalRotation: value };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ('wallUuid' in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
}
const handleScaleChange = (value: number) => {
if (!selectedDecal) return;
const updatedDecal = { ...selectedDecal.decalData, decalScale: value };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ('wallUuid' in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
}
const handleOpacityChange = (value: number) => {
if (!selectedDecal) return;
const updatedDecal = { ...selectedDecal.decalData, decalOpacity: value };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ('wallUuid' in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
}
const handleLayerChange = (direction: "up" | "down") => {
if (!selectedDecal) return;
const position: [number, number, number] = [...(selectedDecal.decalData.decalPosition || [0, 0, 0]),];
if (direction === "up") {
position[2] = Math.abs(position[2]);
} else {
position[2] = -Math.abs(position[2]);
}
const updatedDecal: Decal = { ...selectedDecal.decalData, decalPosition: position, };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ("wallUuid" in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ("floorUuid" in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
};
if (!selectedDecal) return null;
return (
<div className="decal-transformation-container">
<div className="header">Decal Properties</div>
<section>
<InputRange
label="Rotation"
value={selectedDecal.decalData.decalRotation || 0}
min={0}
max={360}
step={1}
onChange={(value: number) => handleRotationChange(value)}
/>
<InputRange
label="Scale"
value={selectedDecal.decalData.decalScale || 1}
min={0.1}
max={5}
step={0.1}
onChange={(value: number) => handleScaleChange(value)}
/>
</section>
<section>
<InputRange
label="Opacity"
value={selectedDecal.decalData.decalOpacity || 1}
min={0.1}
step={0.1}
max={1}
onChange={(value: number) => handleOpacityChange(value)}
/>
<div className="transformation-wrapper opacity">
<div className="transformation-header">Layering</div>
<div className="layers-list">
<button
className="layer-move-btn"
onClick={() => handleLayerChange("down")}
>
<LayeringBottomIcon />
</button>
<button
className="layer-move-btn"
onClick={() => handleLayerChange("up")}
>
<LayeringTopIcon />
</button>
</div>
</div>
</section>
</div>
);
};
export default SelectedDecalProperties;

View File

@@ -1,18 +1,19 @@
import { useEffect, useState } from 'react'
// import NavigateCatagory from '../../NavigateCatagory'
import { EyeIcon, ForkLiftIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons';
import { EyeIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons';
import assetImage from "../../../../../../assets/image/asset-image.png"
import { useSceneContext } from '../../../../../../modules/scene/sceneContext';
import { useProductContext } from '../../../../../../modules/simulation/products/productContext';
import RenameInput from '../../../../../ui/inputs/RenameInput';
import { useResourceManagementId } from '../../../../../../store/builder/store';
import { TypeBasedAssetIcons } from '../../../../../icons/AssetTypeIcons';
const AssetManagement = () => {
// const [selectedCategory, setSelectedCategory] = useState("All Assets");
const [expandedAssetId, setExpandedAssetId] = useState<string | null>(null);
const [assets, setAssets] = useState<any[]>([]);
const { productStore } = useSceneContext();
const { products, getProductById } = productStore();
const { getProductById } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { setResourceManagementId } = useResourceManagementId();
@@ -30,6 +31,7 @@ const AssetManagement = () => {
grouped[asset.modelName] = {
id: asset.modelUuid,
name: asset.modelName,
type: asset.type,
model: asset.modelCode || "N/A",
status: asset.status || "Online",
usageRate: asset.usageRate || 15,
@@ -46,6 +48,7 @@ const AssetManagement = () => {
setAssets(Object.values(grouped));
}
// eslint-disable-next-line
}, [selectedProduct]);
function handleRenameAsset(newName: string) {
@@ -130,7 +133,7 @@ const AssetManagement = () => {
<img className='asset-image' src={asset.image} alt="" />
</>
:
<div className="icon"><ForkLiftIcon /></div>
<div className="icon"><TypeBasedAssetIcons assetType={asset.type}/></div>
}
<div className="asset-details-container">
<div className="asset-details" >
@@ -210,16 +213,10 @@ const AssetManagement = () => {
<div className="value">{expandedAssetId === asset.id ? "View Less" : "View More"}</div>
<div className="icon"><KebabIcon /></div>
</div>
</div>
</div>
)}
</div>
))}
</div >
</>

View File

@@ -146,6 +146,12 @@ const Tools: React.FC = () => {
case "draw-floor":
is2D && setToolMode("Floor");
break;
case "move":
if (!is2D) setToolMode("Move-Asset");
break;
case "rotate":
if (!is2D) setToolMode("Rotate-Asset");
break;
case "measure":
setToolMode("MeasurementScale");
break;
@@ -368,7 +374,7 @@ const Tools: React.FC = () => {
)}
</div>
{activeModule !== "visualization" && (
{toggleThreeD && activeModule !== "visualization" && (
<>
<div className="split"></div>
<div className="transform-tools">

View File

@@ -0,0 +1,14 @@
import DecalCreator from './decalCreator/decalCreator'
function Decal() {
return (
<>
<DecalCreator />
</>
)
}
export default Decal

View File

@@ -0,0 +1,152 @@
import { MathUtils } from 'three';
import { useEffect } from 'react';
import { useThree } from '@react-three/fiber';
import { useParams } from 'react-router-dom';
import { useDroppedDecal, useSocketStore } from '../../../../store/builder/store';
import useModuleStore from '../../../../store/useModuleStore';
import { useSceneContext } from '../../../scene/sceneContext';
import { useVersionContext } from '../../version/versionContext';
import { getUserData } from '../../../../functions/getUserData';
// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
function DecalCreator() {
const { wallStore, floorStore } = useSceneContext();
const { addDecal: addDecalOnWall, getWallById } = wallStore();
const { addDecal: addDecalOnFloor, getFloorById } = floorStore();
const { droppedDecal } = useDroppedDecal();
const { activeModule } = useModuleStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { socket } = useSocketStore();
const { controls, gl, pointer, camera, raycaster, scene } = useThree();
useEffect(() => {
const canvasElement = gl.domElement;
const onDrop = (event: DragEvent) => {
if (droppedDecal) {
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const wallIntersect = intersects.find(i => i.object.userData && i.object.userData.wallUuid);
const floorIntersect = intersects.find(i => i.object.userData && i.object.userData.floorUuid);
console.log('wallIntersect: ', wallIntersect);
if (wallIntersect) {
const wall = getWallById(wallIntersect.object.userData.wallUuid);
if (!wall) return;
const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone());
const decal: Decal = {
decalUuid: MathUtils.generateUUID(),
decalName: droppedDecal.decalName,
decalId: droppedDecal.decalId,
decalType: {
type: 'Wall',
wallUuid: wallIntersect.object.userData.wallUuid,
},
decalPosition: [point.x, point.y, (wall.wallThickness / 2 + 0.001) * (wallIntersect.normal?.z || 1)],
decalRotation: 0,
decalOpacity: 1,
decalScale: 1,
}
addDecalOnWall(wallIntersect.object.userData.wallUuid, decal);
setTimeout(() => {
const updatedWall = getWallById(wallIntersect.object.userData.wallUuid);
if (updatedWall) {
if (projectId && updatedWall) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
}, 0)
} else if (floorIntersect) {
const floor = getFloorById(floorIntersect.object.userData.floorUuid);
if (!floor) return;
const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone());
const decal: Decal = {
decalUuid: MathUtils.generateUUID(),
decalName: droppedDecal.decalName,
decalId: droppedDecal.decalId,
decalType: {
type: 'Floor',
floorUuid: floorIntersect.object.userData.floorUuid,
},
decalPosition: [point.x, point.y, -0.001],
decalRotation: 0,
decalOpacity: 1,
decalScale: 1,
}
addDecalOnFloor(floorIntersect.object.userData.floorUuid, decal);
setTimeout(() => {
const updatedFloor = getFloorById(floorIntersect.object.userData.floorUuid);
if (projectId && updatedFloor) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}, 0)
}
}
};
const onDragOver = (event: any) => {
event.preventDefault();
};
if (activeModule === "builder") {
canvasElement.addEventListener("drop", onDrop);
canvasElement.addEventListener("dragover", onDragOver);
}
return () => {
canvasElement.removeEventListener("drop", onDrop);
canvasElement.removeEventListener("dragover", onDragOver);
};
}, [droppedDecal, camera, activeModule, controls]);
return (
<>
</>
)
}
export default DecalCreator

View File

@@ -1,50 +0,0 @@
import * as THREE from 'three';
import { Decal } from '@react-three/drei'
import { useLoader } from '@react-three/fiber';
import { useToggleView } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import defaultMaterial from '../../../assets/textures/floor/wall-tex.png';
import useModuleStore from '../../../store/useModuleStore';
function DecalInstance({ visible = true, decal, zPosition = decal.decalPosition[2] }: { visible?: boolean, decal: Decal, zPosition?: number }) {
const { setSelectedWall, setSelectedFloor, selectedDecal, setSelectedDecal } = useBuilderStore();
const { togglView } = useToggleView();
const { activeModule } = useModuleStore();
const material = useLoader(THREE.TextureLoader, defaultMaterial);
return (
<Decal
// debug
visible={visible}
position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]}
rotation={[0, 0, decal.decalRotation]}
scale={[decal.decalScale, decal.decalScale, 0.01]}
userData={decal}
onClick={(e) => {
if (visible && !togglView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
setSelectedDecal(e.object);
setSelectedWall(null);
setSelectedFloor(null);
}
}
}}
onPointerMissed={() => {
if (selectedDecal && selectedDecal.userData.decalUuid === decal.decalUuid) {
setSelectedDecal(null);
}
}}
>
<meshBasicMaterial
map={material}
side={THREE.DoubleSide}
polygonOffset
polygonOffsetFactor={-1}
/>
</Decal>
)
}
export default DecalInstance

View File

@@ -0,0 +1,172 @@
import * as THREE from 'three';
import { Decal } from '@react-three/drei'
import { useToggleView, useToolMode } from '../../../../store/builder/store';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { retrieveImage, storeImage } from '../../../../utils/indexDB/idbUtils';
import defaultMaterial from '../../../../assets/image/fallback/fallback decal 1.png';
import useModuleStore from '../../../../store/useModuleStore';
import { useEffect, useRef, useState } from 'react';
import { useDecalEventHandlers } from '../eventHandler/useDecalEventHandlers';
// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalPosition[2] }: { parent: Wall | Floor; visible?: boolean, decal: Decal, zPosition?: number }) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const { selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const decalRef = useRef<any>(null);
useEffect(() => {
if (selectedDecal?.decalData.decalUuid === decal.decalUuid && !selectedDecal.decalMesh) {
setSelectedDecal({ decalData: selectedDecal.decalData, decalMesh: decalRef.current });
}
}, [selectedDecal])
const { handlePointerMissed, handlePointerLeave, handleClick, handlePointerDown, handlePointerEnter } = useDecalEventHandlers({ parent, decal, visible });
const [texture, setTexture] = useState<THREE.Texture | null>(null);
const logDecalStatus = (decalId: string, status: string) => {
// console.log(decalId, status);
}
const loadDefaultTexture = () => {
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
defaultMaterial,
(fallbackTex) => {
fallbackTex.name = "default-decal";
setTexture(fallbackTex);
logDecalStatus(decal.decalId, 'default-loaded');
},
undefined,
(error) => {
console.error("Error loading default decal texture:", error);
}
);
};
const loadDecalTexture = async (decalId: string) => {
try {
const cachedTexture = THREE.Cache.get(decalId);
if (cachedTexture) {
setTexture(cachedTexture);
logDecalStatus(decalId, 'cache-loaded');
return;
}
const indexedDBTexture = await retrieveImage(decalId);
if (indexedDBTexture) {
const blobUrl = URL.createObjectURL(indexedDBTexture);
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
blobUrl,
(tex) => {
URL.revokeObjectURL(blobUrl);
tex.name = decalId;
THREE.Cache.add(decalId, tex);
setTexture(tex);
logDecalStatus(decalId, 'indexedDB-loaded');
},
undefined,
(error) => {
console.error(`Error loading texture from IndexedDB:`, error);
URL.revokeObjectURL(blobUrl);
loadFromBackend(decalId);
}
);
return;
}
loadFromBackend(decalId);
} catch (error) {
console.error("Error loading decal texture:", error);
loadDefaultTexture();
}
};
const loadFromBackend = (decalId: string) => {
const textureUrl = `${url_Backend_dwinzo}/api/v1/DecalImage/${decalId}`;
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
textureUrl,
async (tex) => {
tex.name = decalId;
THREE.Cache.add(decalId, tex);
setTexture(tex);
logDecalStatus(decalId, 'backend-loaded');
try {
const response = await fetch(textureUrl);
const blob = await response.blob();
await storeImage(decalId, blob);
} catch (error) {
console.error("Error storing texture in IndexedDB:", error);
}
},
undefined,
(error) => {
echo.error(`Error loading texture from backend: ${decal.decalName}`);
loadDefaultTexture();
}
);
};
useEffect(() => {
if (decal.decalId) {
loadDecalTexture(decal.decalId);
} else {
loadDefaultTexture();
}
}, [decal.decalId]);
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (selectedDecal) setSelectedDecal(null);
}
if (toolMode !== '3D-Delete') {
if (deletableDecal) setDeletableDecal(null);
}
} else {
if (selectedDecal) setSelectedDecal(null);
if (deletableDecal) setDeletableDecal(null);
}
}, [toggleView, toolMode, activeModule, selectedDecal, deletableDecal]);
return (
<Decal
// debug
visible={visible}
ref={decalRef}
position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]}
rotation={[0, 0, decal.decalRotation * (Math.PI / 180)]}
scale={[(decal.decalType.type === 'Floor' || zPosition < 0) ? -decal.decalScale : decal.decalScale, decal.decalScale, 0.01]}
userData={decal}
onPointerDown={(e) => { if (e.button === 0) handlePointerDown(e) }}
onClick={(e) => { handleClick(e) }}
onPointerEnter={(e) => { handlePointerEnter(e) }}
onPointerLeave={(e) => { handlePointerLeave(e) }}
onPointerMissed={() => handlePointerMissed()}
>
<meshBasicMaterial
map={texture}
side={THREE.DoubleSide}
polygonOffset
polygonOffsetFactor={-1}
transparent
opacity={decal.decalOpacity}
/>
</Decal>
)
}
export default DecalInstance

View File

@@ -0,0 +1,301 @@
import * as THREE from 'three';
import { CameraControls } from '@react-three/drei';
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
import { useEffect, useRef } from 'react';
import { useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import useModuleStore from '../../../../store/useModuleStore';
import { getUserData } from '../../../../functions/getUserData';
import { useVersionContext } from '../../version/versionContext';
import { useParams } from 'react-router-dom';
import { useSceneContext } from '../../../scene/sceneContext';
// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
export function useDecalEventHandlers({
parent,
decal,
visible,
}: {
parent: Wall | Floor;
decal: Decal;
visible: boolean;
}) {
const { wallStore, floorStore } = useSceneContext();
const { removeDecal: removeDecalInWall, updateDecalPosition: updateDecalPositionInWall, getWallById, addDecal: addDecalToWall } = wallStore();
const { removeDecal: removeDecalInFloor, updateDecalPosition: updateDecalPositionInFloor, getFloorById, addDecal: addDecalToFloor } = floorStore();
const { setSelectedWall, setSelectedFloor, setSelectedDecal, setDeletableDecal, deletableDecal, selectedDecal, setDecalDragState, decalDragState } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { socket } = useSocketStore();
const { raycaster, pointer, camera, scene, gl, controls } = useThree();
useFrame(() => {
if (activeModule !== 'builder' || toggleView || !decalDragState.isDragging || !selectedDecal || selectedDecal.decalData.decalUuid !== decal.decalUuid) return;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const wallIntersect = intersects.find(i => i.object.userData?.wallUuid);
const floorIntersect = intersects.find(i => i.object.userData?.floorUuid);
let offset = decalDragState.dragOffset || new THREE.Vector3(0, 0, 0);
if (wallIntersect) {
const wallUuid = wallIntersect.object.userData.wallUuid;
const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone());
if ("wallUuid" in parent && parent.wallUuid === wallUuid && decal.decalType.type === 'Wall') {
updateDecalPositionInWall(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]);
} else if (decal.decalType.type === 'Wall' && wallUuid) {
deleteDecal(decal.decalUuid, parent);
const addedDecal = addDecalToWall(wallUuid, {
...decal,
decalPosition: [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]],
decalType: { type: 'Wall', wallUuid: wallUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
} else if (decal.decalType.type === 'Floor' && wallUuid) {
deleteDecal(decal.decalUuid, parent);
const wall = getWallById(wallUuid);
if (!wall) return;
const addedDecal = addDecalToWall(wallUuid, {
...decal,
decalPosition: [point.x + offset.x, point.y + offset.y, wall.wallThickness / 2 + 0.001],
decalType: { type: 'Wall', wallUuid: wallUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
}
} else if (floorIntersect) {
const floorUuid = floorIntersect.object.userData.floorUuid;
const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone());
if ("floorUuid" in parent && parent.floorUuid === floorUuid && decal.decalType.type === 'Floor') {
updateDecalPositionInFloor(decal.decalUuid, [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]]);
} else if (decal.decalType.type === 'Floor' && floorUuid) {
deleteDecal(decal.decalUuid, parent);
const addedDecal = addDecalToFloor(floorUuid, {
...decal,
decalPosition: [point.x + offset.x, point.y + offset.y, decal.decalPosition[2]],
decalType: { type: 'Floor', floorUuid: floorUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
} else if (decal.decalType.type === 'Wall' && floorUuid) {
deleteDecal(decal.decalUuid, parent);
const floor = getFloorById(floorUuid);
if (!floor) return;
const addedDecal = addDecalToFloor(floorUuid, {
...decal,
decalPosition: [point.x + offset.x, point.y + offset.y, -0.001],
decalType: { type: 'Floor', floorUuid: floorUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
}
}
});
const handlePointerUp = (e: PointerEvent) => {
if (controls) {
(controls as CameraControls).enabled = true;
}
if (decalDragState.isDragging) {
setDecalDragState(false, null, null);
if ('wallUuid' in parent) {
setTimeout(() => {
const updatedWall = getWallById(parent.wallUuid);
if (updatedWall) {
if (projectId && updatedWall) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
}, 0)
} else if ('floorUuid' in parent) {
setTimeout(() => {
const updatedFloor = parent;
if (projectId && updatedFloor) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}, 0)
}
}
};
const deleteDecal = (decalUuid: string, parent: Wall | Floor) => {
if ('wallUuid' in parent) {
const updatedWall = removeDecalInWall(decalUuid);
if (projectId && updatedWall) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
} else if ('floorUuid' in parent) {
const updatedFloor = removeDecalInFloor(decalUuid);
if (projectId && updatedFloor) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}
}
const handlePointerDown = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid && toolMode === 'cursor') {
e.stopPropagation();
setDecalDragState(true, decal.decalUuid, null);
if (controls) {
(controls as CameraControls).enabled = false;
}
setSelectedDecal({ decalMesh: e.object, decalData: decal });
setSelectedWall(null);
setSelectedFloor(null);
const localIntersect = e.object.worldToLocal(e.point.clone());
let dragOffset = new THREE.Vector3(decal.decalPosition[0] - localIntersect.x, decal.decalPosition[1] - localIntersect.y, 0);
setDecalDragState(true, decal.decalUuid, dragOffset);
}
}
};
const handleClick = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
if (toolMode === 'cursor') {
setSelectedDecal({ decalMesh: e.object, decalData: decal });
setSelectedWall(null);
setSelectedFloor(null);
} else if (toolMode === '3D-Delete') {
deleteDecal(e.object.userData.decalUuid, parent);
}
}
}
};
const handlePointerEnter = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
if (toolMode === '3D-Delete') {
setDeletableDecal(e.object);
}
}
}
};
const handlePointerLeave = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
if (toolMode === '3D-Delete' && deletableDecal && deletableDecal?.userData.decalUuid === e.object.userData.decalUuid) {
setDeletableDecal(null);
}
}
}
};
const handlePointerMissed = () => {
if (selectedDecal && selectedDecal.decalMesh && selectedDecal.decalMesh.userData.decalUuid === decal.decalUuid) {
setSelectedDecal(null);
}
};
useEffect(() => {
const canvasElement = gl.domElement;
if (activeModule === 'builder' && !toggleView && selectedDecal && selectedDecal.decalData.decalUuid === decal.decalUuid) {
canvasElement.addEventListener('pointerup', handlePointerUp);
}
return () => {
canvasElement.removeEventListener('pointerup', handlePointerUp);
};
}, [gl, activeModule, toggleView, selectedDecal, camera, controls, visible, parent, decal, decalDragState]);
return {
handlePointerDown,
handleClick,
handlePointerEnter,
handlePointerLeave,
handlePointerMissed,
deleteDecal
};
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const arc = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arc-aisle') return null;
@@ -63,8 +69,8 @@ function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const arrow = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null;
@@ -50,8 +56,8 @@ function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Instances, Instance } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const { arrowGeometry, arrowInstances } = useMemo(() => {
const result = {
@@ -68,8 +74,8 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
};

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const circle = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null;
@@ -38,8 +44,8 @@ function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Instances, Instance } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const dashInstances = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
@@ -43,8 +49,8 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
};

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Instance, Instances } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const dotPositions = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
@@ -27,8 +33,8 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const arrows = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null;
@@ -85,8 +91,8 @@ function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,14 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const shape = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'solid-aisle') return null;
@@ -35,8 +42,8 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -363,6 +363,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
setLeft(relativeX);
}
};
const onMouseUp = (evt: any) => {
setIsRenameMode(false);
}

View File

@@ -34,7 +34,7 @@ export function useModelEventHandlers({
const { socket } = useSocketStore();
const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
const { getAssetById, removeAsset } = assetStore();
const { removeAsset } = assetStore();
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { resourceManagementId, setResourceManagementId } = useResourceManagementId();
const { removeEvent, getEventByModelUuid } = eventStore();
@@ -77,6 +77,7 @@ export function useModelEventHandlers({
}
}, [zoneAssetId])
useEffect(() => {
if (!resourceManagementId) return
if (resourceManagementId === asset.modelUuid) {

View File

@@ -88,6 +88,10 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
}
}, [gltfScene]);
const logModelStatus = (modelId: string, status: string) => {
// console.log(modelId, status);
}
useEffect(() => {
// Calculate Bounding Box
const calculateBoundingBox = (scene: THREE.Object3D) => {
@@ -103,6 +107,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
clone.animations = cachedModel.animations || [];
setGltfScene(clone);
calculateBoundingBox(clone);
logModelStatus(assetId, 'cache-loaded');
return;
}
@@ -118,6 +123,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
logModelStatus(assetId, 'indexedDB-loaded');
},
undefined,
(error) => {
@@ -140,6 +146,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
logModelStatus(assetId, 'backend-loaded');
})
.catch((error) => {
console.error(

View File

@@ -33,13 +33,14 @@ import AssetsGroup from "./asset/assetsGroup";
import DxfFile from "./dfx/LoadBlueprint";
import AislesGroup from "./aisle/aislesGroup";
import WallGroup from "./wall/wallGroup";
import WallAssetGroup from "./wallAsset/wallAssetGroup";
import FloorGroup from "./floor/floorGroup";
import ZoneGroup from "./zone/zoneGroup";
import Decal from "./Decal/decal";
import { useParams } from "react-router-dom";
import { useBuilderStore } from "../../store/builder/useBuilderStore";
import { getUserData } from "../../functions/getUserData";
import WallAssetGroup from "./wallAsset/wallAssetGroup";
export default function Builder() {
const state = useThree<Types.ThreeState>();
@@ -106,6 +107,8 @@ export default function Builder() {
</Geometry>
</mesh>
<Decal />
<AislesGroup />
<FloorGroup />

View File

@@ -1,12 +1,13 @@
import { useMemo } from "react";
import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, } from "three";
import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, ExtrudeGeometry, Vector3, Euler, } from "three";
import { useLoader } from "@react-three/fiber";
import { Extrude } from "@react-three/drei";
import useModuleStore from "../../../../../store/useModuleStore";
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
import { useToggleView } from "../../../../../store/builder/store";
import * as Constants from "../../../../../types/world/worldConstants";
import DecalInstance from "../../../Decal/decalInstance/decalInstance";
import texturePath from "../../../../../assets/textures/floor/white.png";
import texturePathDark from "../../../../../assets/textures/floor/black.png";
import material1 from "../../../../../assets/textures/floor/factory wall texture.jpg";
@@ -28,7 +29,7 @@ import material4MetalicMap from "../../../../../assets/textures/floor/tex3/metal
import material4NormalMap from "../../../../../assets/textures/floor/tex3/metal_plate_nor_gl_1k.png";
function FloorInstance({ floor }: { floor: Floor }) {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { selectedFloor, setSelectedFloor, setSelectedDecal } = useBuilderStore();
const savedTheme = localStorage.getItem("theme");
@@ -67,20 +68,26 @@ function FloorInstance({ floor }: { floor: Floor }) {
},
};
const shape = useMemo(() => {
const shape = new Shape();
const shapeData = useMemo(() => {
const points = floor.points.map((p) => new Vector2(p.position[0], p.position[2]));
if (points.length < 3) return null;
shape.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
shape.lineTo(points[i].x, points[i].y);
const centroidX = points.reduce((sum, p) => sum + p.x, 0) / points.length;
const centroidY = points.reduce((sum, p) => sum + p.y, 0) / points.length;
const relativePoints = points.map((p) => new Vector2(p.x - centroidX, p.y - centroidY));
const shape = new Shape();
shape.moveTo(relativePoints[0].x, relativePoints[0].y);
for (let i = 1; i < relativePoints.length; i++) {
shape.lineTo(relativePoints[i].x, relativePoints[i].y);
}
return shape;
return { shape, center: [centroidX, centroidY] };
}, [floor]);
const textureScale = Constants.floorConfig.textureScale;
// Helper function to handle texture maps and filter out null values
function getMaterialMaps(material: any, defaultMap: any) {
const materialMap = material.map || defaultMap;
const normalMap = material.normalMap || null;
@@ -90,26 +97,18 @@ function FloorInstance({ floor }: { floor: Floor }) {
return [materialMap, normalMap, roughnessMap, metalnessMap].filter((texture): texture is string => texture !== null);
}
// Default material map
const defaultMaterialMap = materials["Default Material"].map;
// Get top and side material maps
const topMaterial = materials[floor.topMaterial];
const sideMaterial = materials[floor.sideMaterial];
// Get the filtered lists for top and side textures
const topTexturesList = getMaterialMaps(topMaterial, defaultMaterialMap);
const sideTexturesList = getMaterialMaps(sideMaterial, defaultMaterialMap);
// Use loader to load top and side textures
const [topTexture, topNormalTexture, topRoughnessTexture, topMetalicTexture] = useLoader(TextureLoader, topTexturesList);
const [sideTexture, sideNormalTexture, sideRoughnessTexture, sideMetalicTexture] = useLoader(TextureLoader, sideTexturesList);
// Early exit if materials are missing
if (!materials[floor.topMaterial] || !materials[floor.sideMaterial]) return null;
// Combine and pair textures with their corresponding material
const textureMaterialMap = [
{
textures: [
@@ -131,7 +130,6 @@ function FloorInstance({ floor }: { floor: Floor }) {
},
];
// Apply texture settings
textureMaterialMap.forEach(({ textures, materialKey }) => {
const tileScale = materials[materialKey]?.textureTileScale ?? [
textureScale,
@@ -143,23 +141,39 @@ function FloorInstance({ floor }: { floor: Floor }) {
tex.wrapS = tex.wrapT = RepeatWrapping;
tex.repeat.set(tileScale[0], tileScale[1]);
tex.anisotropy = 16;
// First texture is always the color map (use SRGB), others should be linear
tex.colorSpace = idx < 1 ? SRGBColorSpace : NoColorSpace;
});
});
if (!shape) return null;
const geometry = useMemo(() => {
if (!shapeData) return null;
return new ExtrudeGeometry(shapeData.shape, {
depth: !floor.isBeveled ? floor.floorDepth : floor.floorDepth - 0.1,
bevelEnabled: floor.isBeveled,
bevelSegments: floor.bevelStrength,
bevelOffset: -0.1,
bevelSize: 0.1,
bevelThickness: 0.1,
});
}, [shapeData, floor]);
if (!geometry) return null;
return (
<mesh
castShadow
receiveShadow
geometry={geometry}
name={`Floor-${floor.floorUuid}`}
rotation={[Math.PI / 2, 0, 0]}
position={[0, !floor.isBeveled ? floor.floorDepth - 0.1 : floor.floorDepth - 0.2, 0,]}
position={[
shapeData?.center[0] ?? 0,
!floor.isBeveled ? floor.floorDepth - 0.1 : floor.floorDepth - 0.2,
shapeData?.center[1] ?? 0,
]}
userData={floor}
onDoubleClick={(e) => {
if (!togglView && activeModule === "builder") {
if (!toggleView && activeModule === "builder") {
if (e.object.userData.floorUuid) {
e.stopPropagation();
setSelectedFloor(e.object);
@@ -172,20 +186,6 @@ function FloorInstance({ floor }: { floor: Floor }) {
setSelectedFloor(null);
}
}}
>
<Extrude
name={`Floor-${floor.floorUuid}`}
args={[shape,
{
depth: !floor.isBeveled ? floor.floorDepth : floor.floorDepth - 0.1,
bevelEnabled: floor.isBeveled,
bevelSegments: floor.bevelStrength,
bevelOffset: -0.1,
bevelSize: 0.1,
bevelThickness: 0.1,
},
]}
userData={floor}
>
<meshPhysicalMaterial
attach="material-0"
@@ -205,9 +205,14 @@ function FloorInstance({ floor }: { floor: Floor }) {
roughnessMap={sideRoughnessTexture?.clone()}
metalnessMap={sideMetalicTexture?.clone()}
normalMap={sideNormalTexture?.clone()}
roughness={1.5}
metalness={1.0}
side={DoubleSide}
/>
</Extrude>
{floor.decals.map((decal) => (
<DecalInstance parent={floor} key={decal.decalUuid} decal={decal} />
))}
</mesh>
);
}

View File

@@ -2,21 +2,36 @@ import React, { useEffect, useMemo } from 'react';
import { Vector3 } from 'three';
import { Html } from '@react-three/drei';
import { useSceneContext } from '../../../scene/sceneContext';
import { useToggleView } from '../../../../store/builder/store';
import { useToggleView, useToolMode } from '../../../../store/builder/store';
import Line from '../../line/line';
import Point from '../../point/point';
import FloorInstance from './Instance/floorInstance';
import Floor2DInstance from './Instance/floor2DInstance';
import useModuleStore from '../../../../store/useModuleStore';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
function FloorInstances() {
const { floorStore } = useSceneContext();
const { floors } = floorStore();
const { setSelectedFloor, selectedFloor } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
useEffect(() => {
// console.log('floors: ', floors);
}, [floors])
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (selectedFloor) setSelectedFloor(null);
}
} else {
if (selectedFloor) setSelectedFloor(null);
}
}, [toggleView, toolMode, activeModule, selectedFloor]);
const allPoints = useMemo(() => {
const points: Point[] = [];
const seenUuids = new Set<string>();

View File

@@ -10,7 +10,7 @@ import FloorInstances from './Instances/floorInstances';
import { getFloorsApi } from '../../../services/factoryBuilder/floor/getFloorsApi';
function FloorGroup() {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { setSelectedFloor, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -21,11 +21,11 @@ function FloorGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
if (toggleView || activeModule !== 'builder') {
setSelectedFloor(null);
setSelectedDecal(null);
}
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -10,7 +10,7 @@ import { useToggleView, useWallVisibility } from '../../../../../store/builder/s
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import * as Constants from '../../../../../types/world/worldConstants';
import DecalInstance from '../../../Decal/decalInstance';
import DecalInstance from '../../../Decal/decalInstance/decalInstance';
import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
@@ -21,7 +21,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
const { wallAssets, getWallAssetsByWall, setVisibility } = wallAssetStore();
const assets = getWallAssetsByWall(wall.wallUuid);
const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore();
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { camera } = useThree();
const { wallVisibility } = useWallVisibility();
@@ -118,10 +118,11 @@ function Wall({ wall }: { readonly wall: Wall }) {
>
{(assets.length > 0 || (walls[0].wallUuid === wall.wallUuid && wallAssets.length > 0)) ?
<Base
name={`BaseWall${wall.wallUuid}`}
castShadow
receiveShadow
ref={meshRef}
geometry={geometry}
geometry={visible ? geometry : new THREE.BoxGeometry(0, 0)}
position={[centerX, centerY, centerZ]}
rotation={[0, -angle, 0]}
userData={wall}
@@ -154,7 +155,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
userData={wall}
name={`WallReference_${wall.wallUuid}`}
onDoubleClick={(e) => {
if (visible && !togglView && activeModule === 'builder') {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.wallUuid) {
e.stopPropagation();
setSelectedWall(e.object);
@@ -171,7 +172,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
<MeshDiscardMaterial />
{wall.decals.map((decal) => (
<DecalInstance zPosition={wall.wallThickness / 2 + 0.001} visible={visible} key={decal.decalUuid} decal={decal} />
<DecalInstance parent={wall} visible={visible} key={decal.decalUuid} decal={decal} />
))}
</mesh>
</mesh>

View File

@@ -2,8 +2,9 @@ import React, { useEffect, useMemo } from 'react';
import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three';
import { Html, Extrude } from '@react-three/drei';
import { useLoader } from '@react-three/fiber';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { useSceneContext } from '../../../scene/sceneContext';
import { useToggleView } from '../../../../store/builder/store';
import { useToggleView, useToolMode } from '../../../../store/builder/store';
import { useWallClassification } from './instance/helpers/useWallClassification';
import Line from '../../line/line';
import Point from '../../point/point';
@@ -12,17 +13,31 @@ import * as Constants from '../../../../types/world/worldConstants';
import texturePath from "../../../../assets/textures/floor/white.png";
import texturePathDark from "../../../../assets/textures/floor/black.png";
import useModuleStore from '../../../../store/useModuleStore';
function WallInstances() {
const { wallStore } = useSceneContext();
const { setSelectedWall, selectedWall } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { walls } = wallStore();
const { rooms } = useWallClassification(walls);
const { toggleView } = useToggleView();
useEffect(() => {
// console.log('walls: ', walls);
}, [walls])
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (selectedWall) setSelectedWall(null);
}
} else {
if (selectedWall) setSelectedWall(null);
}
}, [toggleView, toolMode, activeModule, selectedWall]);
const allPoints = useMemo(() => {
const points: Point[] = [];
const seenUuids = new Set<string>();

View File

@@ -11,7 +11,7 @@ import WallInstances from './Instances/wallInstances';
import { getWallsApi } from '../../../services/factoryBuilder/wall/getWallsApi';
function WallGroup() {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { setSelectedWall, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -22,11 +22,11 @@ function WallGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
if (toggleView || activeModule !== 'builder') {
setSelectedWall(null);
setSelectedDecal(null);
}
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -18,10 +18,10 @@ const calculateAssetTransformationOnWall = (
const projection = initialWallNormalized.clone().multiplyScalar(dotProduct);
const perpendicular = new THREE.Vector3().subVectors(assetVector, projection);
const distanceFromWall = perpendicular.length();
const distanceInWall = perpendicular.length();
const crossProduct = new THREE.Vector3().crossVectors(initialWallNormalized, perpendicular).y;
const signedDistance = distanceFromWall * (crossProduct >= 0 ? 1 : -1);
const signedDistance = distanceInWall * (crossProduct >= 0 ? 1 : -1);
const percentage = Math.max(0, Math.min(1, dotProduct / initialWallLength));

View File

@@ -23,7 +23,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const { raycaster, pointer, camera, scene, controls, gl } = useThree();
const { wallStore, wallAssetStore } = useSceneContext();
const { walls, getWallById } = wallStore();
const { updateWallAsset, removeWallAsset } = wallAssetStore();
const { updateWallAsset, removeWallAsset, getWallAssetById } = wallAssetStore();
const { toggleView } = useToggleView();
const { activeTool } = useActiveTool();
const { activeModule } = useModuleStore();
@@ -116,33 +116,14 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const canvasElement = gl.domElement;
const onPointerUp = (e: PointerEvent) => {
if (draggingRef.current) {
draggingRef.current = false;
if (controls) {
(controls as any).enabled = true;
}
if (selectedWallAsset) {
pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const intersect = intersects.find((i: any) => i.object.name.includes('WallReference'));
if (intersect && intersect.object.userData.wallUuid && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
const newPoint = closestPointOnLineSegment(
new THREE.Vector3(intersect.point.x, 0, intersect.point.z),
new THREE.Vector3(...intersect.object.userData.points[0].position),
new THREE.Vector3(...intersect.object.userData.points[1].position)
);
const wallRotation = intersect.object.rotation.clone();
const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
wallUuid: intersect.object.userData.wallUuid,
position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z],
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
});
const updatedWallAsset = getWallAssetById(wallAsset.modelUuid);
if (projectId && updatedWallAsset) {

View File

@@ -14,7 +14,7 @@ import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegm
function WallAssetCreator() {
const { socket } = useSocketStore();
const { pointer, camera, raycaster, scene, gl } = useThree();
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { wallAssetStore } = useSceneContext();
const { addWallAsset } = wallAssetStore();
@@ -84,7 +84,7 @@ function WallAssetCreator() {
}
};
if (!togglView && activeModule === 'builder') {
if (!toggleView && activeModule === 'builder') {
canvasElement.addEventListener('drop', onDrop);
}
@@ -92,7 +92,7 @@ function WallAssetCreator() {
canvasElement.removeEventListener('drop', onDrop);
};
}, [gl, camera, togglView, activeModule, socket, selectedItem, setSelectedItem]);
}, [gl, camera, toggleView, activeModule, socket, selectedItem, setSelectedItem]);
return (
<>

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { useActiveTool, useToggleView } from '../../../store/builder/store';
import { useToggleView, useToolMode } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';
@@ -10,10 +10,10 @@ import WallAssetInstances from './Instances/wallAssetInstances'
import { getWallAssetsApi } from '../../../services/factoryBuilder/asset/wallAsset/getWallAssetsApi';
function WallAssetGroup() {
const { togglView } = useToggleView();
const { setSelectedFloorAsset, setDeletableWallAsset } = useBuilderStore();
const { toggleView } = useToggleView();
const { setSelectedWallAsset, setDeletableWallAsset } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { wallAssetStore } = useSceneContext();
@@ -21,11 +21,11 @@ function WallAssetGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
setSelectedFloorAsset(null);
if (toggleView || activeModule !== 'builder' || toolMode !== 'cursor') {
setSelectedWallAsset(null);
}
setDeletableWallAsset(null);
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, toolMode])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -11,7 +11,7 @@ import ZoneInstances from './Instances/zoneInstances';
import { getZonesApi } from '../../../services/factoryBuilder/zone/getZonesApi';
function ZoneGroup() {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { setSelectedZone } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -22,10 +22,10 @@ function ZoneGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
if (toggleView || activeModule !== 'builder') {
setSelectedZone(null);
}
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -1,12 +1,13 @@
import { useFrame, useThree } from "@react-three/fiber";
import React, { useEffect, useState } from "react";
import { useKeyboardControls } from "@react-three/drei";
import * as CONSTANTS from "../../../types/world/worldConstants";
import { useCamMode, useToggleView } from "../../../store/builder/store";
import { useKeyboardControls } from "@react-three/drei";
import switchToThirdPerson from "./switchToThirdPerson";
import switchToFirstPerson from "./switchToFirstPerson";
import switchToThirdPerson from "./functions/switchToThirdPerson";
import switchToFirstPerson from "./functions/switchToFirstPerson";
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
import { firstPersonCamera } from "./firstPersonCamera";
import { firstPersonCamera } from "./functions/firstPersonCamera";
const CamMode: React.FC = () => {
const { camMode, setCamMode } = useCamMode();

View File

@@ -1,4 +1,4 @@
import * as CONSTANTS from "../../../types/world/worldConstants";
import * as CONSTANTS from "../../../../types/world/worldConstants";
interface FirstPersonCameraProps {
setIsTransitioning?: (value: boolean) => void;

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
import * as CONSTANTS from '../../../../types/world/worldConstants';
export default async function switchToFirstPerson(
controls: any,

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
import * as CONSTANTS from '../../../../types/world/worldConstants';
export default async function switchToThirdPerson(
controls: any,

View File

@@ -1,6 +1,6 @@
import { Socket } from "socket.io-client";
import * as THREE from "three";
import { getUserData } from "../../../functions/getUserData";
import { getUserData } from "../../../../functions/getUserData";
export default function updateCamPosition(
controls: any,

View File

@@ -3,20 +3,24 @@ import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import type { CameraControls } from "@react-three/drei";
export const useCameraShortcuts = (controlsRef: React.RefObject<CameraControls>) => {
const { camera } = useThree();
const CameraShortcutsControls = () => {
const { camera, controls } = useThree();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!controlsRef.current) return;
if (!controls) return;
// get current distance from camera to target
const cc = controls as CameraControls;
// get current target
const target = new THREE.Vector3();
controlsRef.current.getTarget(target);
cc.getTarget(target);
const distance = camera.position.distanceTo(target);
let pos: THREE.Vector3 | null = null;
const dir = new THREE.Vector3().subVectors(camera.position, target).normalize();
switch (e.key) {
case "1": // Front
pos = new THREE.Vector3(0, 0, distance).add(target);
@@ -27,13 +31,24 @@ export const useCameraShortcuts = (controlsRef: React.RefObject<CameraControls>)
case "7": // Top
pos = new THREE.Vector3(0, distance, 0).add(target);
break;
case "9": // Back
pos = new THREE.Vector3(0, 0, -distance).add(target);
case "9": {
// Opposite view logic
if (Math.abs(dir.z) > Math.abs(dir.x) && Math.abs(dir.z) > Math.abs(dir.y)) {
// Currently looking Front/Back → flip Z
pos = new THREE.Vector3(0, 0, -Math.sign(dir.z) * distance).add(target);
} else if (Math.abs(dir.x) > Math.abs(dir.z) && Math.abs(dir.x) > Math.abs(dir.y)) {
// Currently looking Right/Left → flip X
pos = new THREE.Vector3(-Math.sign(dir.x) * distance, 0, 0).add(target);
} else {
// Currently looking Top/Bottom → stay Top
pos = new THREE.Vector3(0, distance, 0).add(target);
}
break;
}
}
if (pos) {
controlsRef.current.setLookAt(
cc.setLookAt(
pos.x, pos.y, pos.z, // camera position
target.x, target.y, target.z, // keep same target
true // smooth transition
@@ -43,5 +58,9 @@ export const useCameraShortcuts = (controlsRef: React.RefObject<CameraControls>)
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [controlsRef, camera]);
}, [controls, camera]);
return null;
};
export default CameraShortcutsControls;

View File

@@ -1,12 +1,9 @@
import { useEffect, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
import { CameraControls, Html, ScreenSpace } from "@react-three/drei";
import {
useContextActionStore,
useRenameModeStore,
useSelectedAssets,
} from "../../../../store/builder/store";
import { useContextActionStore, useRenameModeStore, useSelectedAssets, useToggleView, useToolMode, } from "../../../../store/builder/store";
import ContextMenu from "../../../../components/ui/menu/contextMenu";
import useModuleStore from "../../../../store/useModuleStore";
function ContextControls() {
const { gl, controls } = useThree();
@@ -27,6 +24,9 @@ function ContextControls() {
delete: true,
});
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { toolMode } = useToolMode();
const { selectedAssets } = useSelectedAssets();
const { setContextAction } = useContextActionStore();
const { setIsRenameMode } = useRenameModeStore();
@@ -111,10 +111,7 @@ function ContextControls() {
event.preventDefault();
if (rightDrag.current) return;
if (selectedAssets.length > 0) {
setMenuPosition({
x: event.clientX - gl.domElement.width / 2,
y: event.clientY - gl.domElement.height / 2,
});
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2, });
setCanRender(true);
if (controls) {
(controls as CameraControls).enabled = false;
@@ -127,7 +124,7 @@ function ContextControls() {
}
};
if (selectedAssets.length > 0) {
if (selectedAssets.length > 0 && !toggleView && activeModule === "builder" && toolMode === 'cursor') {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
@@ -146,7 +143,7 @@ function ContextControls() {
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("contextmenu", handleContextClick);
};
}, [controls, gl, selectedAssets]);
}, [controls, gl, selectedAssets, toggleView, activeModule, toolMode]);
const handleAssetRename = () => {
setCanRender(false);

View File

@@ -3,22 +3,22 @@ import { useRef, useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import * as CONSTANTS from '../../../types/world/worldConstants';
import { useSocketStore, useToggleView, useResetCamera } from "../../../store/builder/store";
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
import updateCamPosition from "../camera/updateCameraPosition";
import CamMode from "../camera/camMode";
import SwitchView from "../camera/switchView";
import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D";
import TransformControl from "./transformControls/transformControls";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData";
import ContextControls from "./contextControls/contextControls";
import TransformControl from "./transformControls/transformControls";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D";
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
import { useCameraShortcuts } from "../../../hooks/useCameraShortcuts";
import CameraShortcutsControls from "../camera/shortcutsControls/cameraShortcutsControls";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData";
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
import updateCamPosition from "../camera/functions/updateCameraPosition";
export default function Controls() {
const controlsRef = useRef<CameraControls>(null);
@@ -117,7 +117,6 @@ export default function Controls() {
stopInterval();
};
}, [toggleView, state, socket]);
useCameraShortcuts(controlsRef);
return (
<>
@@ -140,6 +139,8 @@ export default function Controls() {
<CamMode />
<CameraShortcutsControls/>
</CameraControls>
<SelectionControls3D />

View File

@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
@@ -12,6 +12,7 @@ import { useProductContext } from "../../../../simulation/products/productContex
import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import useModuleStore from "../../../../../store/useModuleStore";
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
@@ -20,6 +21,8 @@ function MoveControls3D({ boundingBoxRef }: any) {
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { toolMode } = useToolMode();
const { activeModule } = useModuleStore();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
@@ -162,7 +165,7 @@ function MoveControls3D({ boundingBoxRef }: any) {
}
};
if (!toggleView) {
if (!toggleView && toolMode === 'cursor') {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
@@ -178,7 +181,14 @@ function MoveControls3D({ boundingBoxRef }: any) {
canvasElement?.removeEventListener("keyup", onKeyUp);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]);
}, [camera, controls, scene, toggleView, toolMode, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]);
useEffect(() => {
if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
resetToInitialPositions();
setMovedObjects([]);
}
}, [activeModule, toolMode, toggleView]);
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);

View File

@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
import { useParams } from "react-router-dom";
@@ -11,6 +11,7 @@ import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { handleAssetRotationSnap } from "./functions/handleAssetRotationSnap";
import useModuleStore from "../../../../../store/useModuleStore";
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
@@ -18,6 +19,8 @@ function RotateControls3D() {
const { camera, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { toolMode } = useToolMode();
const { activeModule } = useModuleStore();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
@@ -34,6 +37,7 @@ function RotateControls3D() {
const [initialRotations, setInitialRotations] = useState<Record<string, THREE.Euler>>({});
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [isRotating, setIsRotating] = useState(false);
const [isIndividualRotating, setIsIndividualRotating] = useState(false);
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
const rotationCenter = useRef<THREE.Vector3 | null>(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
@@ -117,6 +121,12 @@ function RotateControls3D() {
}
}
if (event.key.toLowerCase() === 'i') {
if (rotatedObjects.length > 0) {
setIsIndividualRotating(!isIndividualRotating);
}
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
resetToInitialRotations();
@@ -137,7 +147,7 @@ function RotateControls3D() {
prevPointerPosition.current = currentPointer.clone();
};
if (!toggleView) {
if (!toggleView && toolMode === 'cursor') {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
@@ -152,7 +162,14 @@ function RotateControls3D() {
canvasElement.removeEventListener("keydown", onKeyDown);
canvasElement?.removeEventListener("keyup", onKeyUp);
};
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations]);
}, [camera, scene, toggleView, toolMode, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent, initialPositions, initialRotations, isIndividualRotating]);
useEffect(() => {
if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
resetToInitialRotations();
setRotatedObjects([]);
}
}, [activeModule, toolMode, toggleView]);
const resetToInitialRotations = useCallback(() => {
setTimeout(() => {
@@ -204,10 +221,12 @@ function RotateControls3D() {
wasShiftHeldRef
});
if (!isIndividualRotating) {
const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
relativePosition.applyMatrix4(rotationMatrix);
obj.position.copy(center).add(relativePosition);
}
const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta);
obj.quaternion.multiply(rotationQuat);
@@ -384,8 +403,9 @@ function RotateControls3D() {
}
setIsRotating(false);
setIsIndividualRotating(false);
clearSelection();
}, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
}, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId, initialPositions, initialRotations]);
const clearSelection = () => {
setPastedObjects([]);
@@ -393,6 +413,7 @@ function RotateControls3D() {
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
setIsIndividualRotating(false);
};
return null;

View File

@@ -16,22 +16,23 @@ import DuplicationControls3D from "./duplicationControls3D";
import CopyPasteControls3D from "./copyPasteControls3D";
import MoveControls3D from "./moveControls3D";
import RotateControls3D from "./rotateControls3D";
import TransformControls3D from "./transformControls3D";
// import { deleteFloorItem } from '../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi';
const SelectionControls3D: React.FC = () => {
const { camera, controls, gl, scene, raycaster, pointer } = useThree();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { toolMode } = useToolMode();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const boundingBoxRef = useRef<THREE.Mesh>();
const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
const { contextAction, setContextAction } = useContextActionStore()
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
const { removeAsset, getAssetById, movedObjects, rotatedObjects, copiedObjects, pastedObjects, duplicatedObjects, setPastedObjects, setDuplicatedObjects } = assetStore();
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { selectedProductStore } = useProductContext();
@@ -202,7 +203,7 @@ const SelectionControls3D: React.FC = () => {
rightClickMoved.current = false;
};
if (!toggleView && activeModule === "builder" && toolMode === 'cursor') {
if (!toggleView && activeModule === "builder" && (toolMode === 'cursor' || toolMode === 'Move-Asset' || toolMode === 'Rotate-Asset')) {
helper.enabled = true;
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
@@ -227,7 +228,7 @@ const SelectionControls3D: React.FC = () => {
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, activeModule, toolMode]);
useEffect(() => {
if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
if (activeModule !== "builder" || (toolMode !== 'cursor' && toolMode !== 'Move-Asset' && toolMode !== 'Rotate-Asset') || toggleView) {
clearSelection();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -381,6 +382,8 @@ const SelectionControls3D: React.FC = () => {
<CopyPasteControls3D />
<TransformControls3D />
</>
);
};

View File

@@ -0,0 +1,380 @@
import * as THREE from 'three';
import { useRef, useEffect, useState, useCallback } from 'react';
import { useThree } from '@react-three/fiber';
import { TransformControls } from '@react-three/drei';
import { useSelectedAssets, useSocketStore, useToolMode } from '../../../../../store/builder/store';
import { useProductContext } from '../../../../simulation/products/productContext';
import { useVersionContext } from '../../../../builder/version/versionContext';
import { useSceneContext } from '../../../sceneContext';
import { useParams } from 'react-router-dom';
import { getUserData } from '../../../../../functions/getUserData';
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
function TransformControls3D() {
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { toolMode } = useToolMode();
const { camera, scene, gl } = useThree();
const transformControlsRef = useRef<any>(null);
const [visible, setVisible] = useState(false);
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
const { socket } = useSocketStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
const { push3D, subscribeUndoRedo } = undoRedo3DStore();
const { updateAsset, getAssetById } = assetStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const pivotPointRef = useRef<THREE.Vector3>(new THREE.Vector3());
const initialPositionsRef = useRef<Map<string, THREE.Vector3>>(new Map());
const initialRotationsRef = useRef<Map<string, THREE.Euler>>(new Map());
const initialPivotPositionRef = useRef<THREE.Vector3>(new THREE.Vector3());
const initialQuaternionsRef = useRef<Map<string, THREE.Quaternion>>(new Map());
const tempObjectRef = useRef<THREE.Object3D>(new THREE.Object3D());
const updateBackend = useCallback((
productName: string,
productUuid: string,
projectId: string,
eventData: any
) => {
upsertProductOrEventApi({
productName: productName,
productUuid: productUuid,
projectId: projectId,
eventDatas: eventData,
versionId: selectedVersion?.versionId || '',
});
}, [selectedVersion]);
const recalcGizmo = useCallback(() => {
if (selectedAssets.length === 0) return;
const bbox = new THREE.Box3();
selectedAssets.forEach(obj => bbox.expandByObject(obj));
bbox.getCenter(pivotPointRef.current);
initialPivotPositionRef.current.copy(pivotPointRef.current);
tempObjectRef.current.position.copy(pivotPointRef.current);
tempObjectRef.current.rotation.set(0, 0, 0);
tempObjectRef.current.scale.set(1, 1, 1);
initialPositionsRef.current.clear();
initialRotationsRef.current.clear();
initialQuaternionsRef.current.clear();
selectedAssets.forEach(obj => {
initialPositionsRef.current.set(obj.uuid, obj.position.clone());
initialRotationsRef.current.set(obj.uuid, obj.rotation.clone());
initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone());
});
}, [selectedAssets]);
useEffect(() => {
const unsubscribe = subscribeUndoRedo(() => {
setTimeout(() => {
recalcGizmo();
}, 10);
});
return unsubscribe;
}, [subscribeUndoRedo, recalcGizmo]);
const handleTransformationComplete = useCallback(() => {
if (selectedAssets.length === 0) return;
const updatedAssets = [...selectedAssets];
setSelectedAssets(updatedAssets);
selectedAssets.forEach(obj => {
const asset = getAssetById(obj.uuid);
if (!asset) return;
updateAsset(asset.modelUuid, {
position: [obj.position.x, obj.position.y, obj.position.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
});
if (asset.eventData) {
const eventData = eventStore.getState().getEventByModelUuid(asset.modelUuid);
const productData = productStore.getState().getEventByModelUuid(
selectedProduct.productUuid,
asset.modelUuid
);
if (eventData) {
eventStore.getState().updateEvent(asset.modelUuid, {
position: [obj.position.x, obj.position.y, obj.position.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
});
}
if (productData) {
const event = productStore
.getState()
.updateEvent(
selectedProduct.productUuid,
asset.modelUuid,
{
position: [obj.position.x, obj.position.y, obj.position.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
}
);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
}
}
//REST
// setAssetsApi(
// organization,
// asset.modelUuid,
// asset.modelName,
// [obj.position.x, 0, obj.position.z],
// { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
// asset.assetId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modelUuid: asset.modelUuid,
modelName: asset.modelName,
assetId: asset.assetId,
position: [obj.position.x, obj.position.y, obj.position.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
versionId: selectedVersion?.versionId || '',
userId,
projectId
};
socket.emit("v1:model-asset:add", data);
});
}, [selectedAssets, setSelectedAssets, getAssetById, updateAsset, eventStore, productStore, selectedProduct, updateBackend, projectId, organization, socket, selectedVersion, userId, push3D]);
useEffect(() => {
const temp = tempObjectRef.current;
scene.add(temp);
return () => {
scene.remove(temp);
};
}, [scene]);
useEffect(() => {
if (selectedAssets.length === 0) {
setVisible(false);
return;
}
const bbox = new THREE.Box3();
selectedAssets.forEach(obj => bbox.expandByObject(obj));
bbox.getCenter(pivotPointRef.current);
initialPivotPositionRef.current.copy(pivotPointRef.current);
tempObjectRef.current.position.copy(pivotPointRef.current);
tempObjectRef.current.rotation.set(0, 0, 0);
tempObjectRef.current.scale.set(1, 1, 1);
initialPositionsRef.current.clear();
initialRotationsRef.current.clear();
initialQuaternionsRef.current.clear();
selectedAssets.forEach(obj => {
initialPositionsRef.current.set(obj.uuid, obj.position.clone());
initialRotationsRef.current.set(obj.uuid, obj.rotation.clone());
initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone());
});
setVisible(true);
}, [selectedAssets]);
useEffect(() => {
const controls = transformControlsRef.current;
if (!controls || !visible) return;
controls.attach(tempObjectRef.current);
controls.setMode(toolMode === 'Move-Asset' ? 'translate' : 'rotate');
const onObjectChange = () => {
const temp = tempObjectRef.current;
if (!temp) return;
selectedAssets.forEach(obj => {
const initialPos = initialPositionsRef.current.get(obj.uuid);
const initialQuat = initialQuaternionsRef.current.get(obj.uuid);
if (!initialPos || !initialQuat) return;
if (controls.mode === 'translate') {
const delta = new THREE.Vector3().copy(temp.position).sub(initialPivotPositionRef.current);
obj.position.copy(initialPos).add(delta);
} else if (controls.mode === 'rotate') {
const deltaQuat = temp.quaternion.clone();
const relPos = initialPos.clone().sub(initialPivotPositionRef.current);
relPos.applyQuaternion(deltaQuat);
obj.position.copy(initialPivotPositionRef.current).add(relPos);
obj.quaternion.copy(deltaQuat).multiply(initialQuat);
}
});
};
const onMouseUp = () => {
const assetsToUpdate: AssetData[] = [];
const undoActions: UndoRedo3DAction[] = [];
selectedAssets.forEach((obj) => {
const asset = getAssetById(obj.uuid);
if (!asset) return;
const initialPos = initialPositionsRef.current.get(obj.uuid);
const initialRot = initialRotationsRef.current.get(obj.uuid);
if (!initialPos || !initialRot) return;
const assetData: Asset = {
...asset,
position: [initialPos.x, initialPos.y, initialPos.z],
rotation: [initialRot.x, initialRot.y, initialRot.z],
};
const newData: Asset = {
...asset,
position: [obj.position.x, obj.position.y, obj.position.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
};
assetsToUpdate.push({
type: "Asset",
assetData,
newData,
timeStap: new Date().toISOString(),
});
updateAsset(asset.modelUuid, {
position: [obj.position.x, obj.position.y, obj.position.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
});
});
if (assetsToUpdate.length > 0) {
if (assetsToUpdate.length === 1) {
undoActions.push({
module: "builder",
actionType: "Asset-Update",
asset: assetsToUpdate[0],
});
} else {
undoActions.push({
module: "builder",
actionType: "Assets-Update",
assets: assetsToUpdate,
});
}
push3D({
type: "Scene",
actions: undoActions,
});
}
selectedAssets.forEach(obj => {
initialPositionsRef.current.set(obj.uuid, obj.position.clone());
initialRotationsRef.current.set(obj.uuid, obj.rotation.clone());
initialQuaternionsRef.current.set(obj.uuid, obj.quaternion.clone());
});
if (selectedAssets.length > 0) {
const bbox = new THREE.Box3();
selectedAssets.forEach(obj => bbox.expandByObject(obj));
bbox.getCenter(pivotPointRef.current);
initialPivotPositionRef.current.copy(pivotPointRef.current);
tempObjectRef.current.position.copy(pivotPointRef.current);
tempObjectRef.current.rotation.set(0, 0, 0);
}
recalcGizmo();
handleTransformationComplete();
};
controls.addEventListener('objectChange', onObjectChange);
controls.addEventListener('mouseUp', onMouseUp);
return () => {
controls.removeEventListener('objectChange', onObjectChange);
controls.removeEventListener('mouseUp', onMouseUp);
controls.detach();
};
}, [selectedAssets, toolMode, visible, handleTransformationComplete]);
useEffect(() => {
const canvasElement = gl.domElement;
const handleKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination !== keyEvent) {
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
setKeyEvent(keyCombination);
} else {
setKeyEvent("");
}
}
};
const onKeyUp = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "") {
setKeyEvent("");
} else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
setKeyEvent(keyCombination);
}
}
if (visible) {
canvasElement.addEventListener('keydown', handleKeyDown);
canvasElement.addEventListener('keyup', onKeyUp);
}
return () => {
canvasElement.removeEventListener('keydown', handleKeyDown);
canvasElement.removeEventListener('keyup', onKeyUp);
}
}, [gl, visible, keyEvent]);
if (!visible || (toolMode !== 'Move-Asset' && toolMode !== 'Rotate-Asset')) {
return null;
}
return (
<TransformControls
space="world"
translationSnap={keyEvent === 'Ctrl' ? 0.5 : keyEvent === 'Ctrl+Shift' ? 0.1 : 0}
rotationSnap={keyEvent === 'Ctrl' ? THREE.MathUtils.degToRad(15) : keyEvent === 'Ctrl+Shift' ? THREE.MathUtils.degToRad(5) : 0}
ref={transformControlsRef}
camera={camera}
showX
showY
showZ
/>
);
}
export default TransformControls3D;

View File

@@ -1,17 +1,17 @@
import { TransformControls } from "@react-three/drei";
import * as THREE from "three";
import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store";
import { useThree } from "@react-three/fiber";
import { useEffect, useRef, useState } from "react";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
// import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
import { useParams } from "react-router-dom";
import { useThree } from "@react-three/fiber";
import { TransformControls } from "@react-three/drei";
import { useEffect, useRef, useState } from "react";
import { useProductContext } from "../../../simulation/products/productContext";
import { getUserData } from "../../../../functions/getUserData";
import { useSceneContext } from "../../sceneContext";
import { useVersionContext } from "../../../builder/version/versionContext";
import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
// import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
export default function TransformControl() {
const state = useThree();

View File

@@ -20,7 +20,7 @@ function UndoRedo3DControls() {
const { selectedVersion } = selectedVersionStore();
useEffect(() => {
// console.log(undoStack, redoStack);
console.log(undoStack, redoStack);
}, [undoStack, redoStack]);
useEffect(() => {

View File

@@ -1,25 +1,19 @@
import { DepthOfField, Bloom, EffectComposer, N8AO, Outline } from "@react-three/postprocessing";
import { useThree } from "@react-three/fiber";
import { BlendFunction } from "postprocessing";
import {
useDeletableFloorItem,
useSelectedWallItem,
useSelectedFloorItem,
} from "../../../store/builder/store";
import * as CONSTANTS from "../../../types/world/worldConstants";
import { useDeletableEventSphere, useSelectedEventSphere, useSelectedPoints } from "../../../store/simulation/useSimulationStore";
import { useEffect } from "react";
import { BlendFunction } from "postprocessing";
import { DepthOfField, Bloom, EffectComposer, N8AO, Outline } from "@react-three/postprocessing";
import { useDeletableFloorItem, useSelectedWallItem, useSelectedFloorItem, } from "../../../store/builder/store";
import { useDeletableEventSphere, useSelectedEventSphere, useSelectedPoints } from "../../../store/simulation/useSimulationStore";
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
import * as CONSTANTS from "../../../types/world/worldConstants";
export default function PostProcessing() {
const { scene } = useThree();
const { selectedPoints } = useSelectedPoints();
const { deletableFloorItem } = useDeletableFloorItem();
const { selectedWallItem } = useSelectedWallItem();
const { selectedFloorItem } = useSelectedFloorItem();
const { selectedEventSphere } = useSelectedEventSphere();
const { deletableEventSphere } = useDeletableEventSphere();
const { selectedAisle, selectedWall, selectedDecal, selectedFloor, selectedWallAsset, deletableWallAsset } = useBuilderStore();
const { selectedAisle, selectedWall, selectedDecal, selectedFloor, selectedWallAsset, deletableWallAsset, deletableDecal } = useBuilderStore();
function flattenChildren(children: any[]) {
const allChildren: any[] = [];
@@ -68,6 +62,10 @@ export default function PostProcessing() {
// console.log('selectedPoints: ', selectedPoints);
}, [selectedPoints])
useEffect(() => {
// console.log('deletableDecal: ', deletableDecal);
}, [deletableDecal])
return (
<EffectComposer autoClear={false}>
<N8AO
@@ -122,7 +120,7 @@ export default function PostProcessing() {
)}
{selectedAisle && (
<Outline
selection={flattenChildren(selectedAisle.children)}
selection={flattenChildren(selectedAisle.aisleMesh?.children || [])}
selectionLayer={10}
width={2000}
blendFunction={BlendFunction.ALPHA}
@@ -167,7 +165,7 @@ export default function PostProcessing() {
)}
{selectedDecal && (
<Outline
selection={selectedDecal}
selection={selectedDecal.decalMesh || undefined}
selectionLayer={10}
width={2000}
blendFunction={BlendFunction.ALPHA}
@@ -180,6 +178,21 @@ export default function PostProcessing() {
xRay={true}
/>
)}
{deletableDecal && (
<Outline
selection={deletableDecal}
selectionLayer={10}
width={3000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={5}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor}
hiddenEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor}
blur={true}
xRay={true}
/>
)}
{deletableFloorItem && (
<Outline
selection={flattenChildren(deletableFloorItem.children)}

View File

@@ -128,6 +128,7 @@ export const useSocketStore = create<any>((set: any, get: any) => ({
// });
// },
// }));
export const useLoadingProgress = create<{
loadingProgress: number;
setLoadingProgress: (x: number) => void;
@@ -162,6 +163,21 @@ export const useSelectedItem = create<any>((set: any) => ({
setSelectedItem: (x: any) => set(() => ({ selectedItem: x })),
}));
type DroppedDecalType = {
category: string;
decalName: string;
decalImage: string;
decalId: string;
};
export const useDroppedDecal = create<{
droppedDecal: DroppedDecalType | null;
setDroppedDecal: (x: DroppedDecalType | null) => void;
}>((set) => ({
droppedDecal: null,
setDroppedDecal: (x) => set({ droppedDecal: x }),
}));
export const useNavMesh = create<any>((set: any) => ({
navMesh: null,
setNavMesh: (x: any) => set({ navMesh: x }),
@@ -663,6 +679,6 @@ interface DecalStore {
// Create the Zustand store with types
export const useDecalStore = create<DecalStore>((set) => ({
selectedSubCategory: null,
selectedSubCategory: 'Safety',
setSelectedSubCategory: (subCategory: string | null) => set({ selectedSubCategory: subCategory }),
}));

View File

@@ -3,43 +3,42 @@ import { immer } from "zustand/middleware/immer";
interface AisleStore {
aisles: Aisles;
setAisles: (aisles: Aisles) => void;
addAisle: (aisle: Aisle) => void;
updateAisle: (uuid: string, updated: Partial<Aisle>) => void;
removeAisle: (uuid: string) => void;
setAisles: (aisles: Aisles) => Aisles;
addAisle: (aisle: Aisle) => Aisle;
updateAisle: (uuid: string, updated: Partial<Aisle>) => Aisle | undefined;
removeAisle: (uuid: string) => Aisle | undefined;
removePoint: (uuid: string) => Aisles;
clearAisles: () => void;
clearAisles: () => Aisles;
setPosition: (
pointUuid: string,
position: [number, number, number]
) => Aisle | undefined;
setLayer: (pointUuid: string, layer: number) => void;
setColor: (aisleUuid: string, color: AisleColors) => void;
setLayer: (pointUuid: string, layer: number) => Aisle | undefined;
setColor: (aisleUuid: string, color: AisleColors) => Aisle | undefined;
// Type-specific setters
setSolidAisleWidth: (aisleUuid: string, width: number) => void;
setSolidAisleWidth: (aisleUuid: string, width: number) => Aisle | undefined;
setDashedAisleProperties: (
aisleUuid: string,
props: { aisleWidth?: number; dashLength?: number; gapLength?: number }
) => void;
) => Aisle | undefined;
setDottedAisleProperties: (
aisleUuid: string,
props: { dotRadius?: number; gapLength?: number }
) => void;
setArrowAisleWidth: (aisleUuid: string, width: number) => void;
) => Aisle | undefined;
setArrowAisleWidth: (aisleUuid: string, width: number) => Aisle | undefined;
setArrowsAisleProperties: (
aisleUuid: string,
props: { aisleWidth?: number; aisleLength?: number; gapLength?: number }
) => void;
) => Aisle | undefined;
setArcAisleWidth: (
aisleUuid: string,
props: { aisleWidth?: number; isFlipped: boolean }
) => void;
setCircleAisleWidth: (aisleUuid: string, width: number) => void;
) => Aisle | undefined;
setCircleAisleWidth: (aisleUuid: string, width: number) => Aisle | undefined;
setJunctionAisleProperties: (
aisleUuid: string,
props: { aisleWidth?: number; isFlipped: boolean }
) => void;
) => Aisle | undefined;
getAisleById: (uuid: string) => Aisle | undefined;
getAislesByPointId: (uuid: string) => Aisle[] | [];
@@ -53,28 +52,43 @@ export const createAisleStore = () => {
immer((set, get) => ({
aisles: [],
setAisles: (aisles) =>
setAisles: (aisles) => {
set((state) => {
state.aisles = aisles;
}),
});
return aisles;
},
addAisle: (aisle) =>
addAisle: (aisle) => {
set((state) => {
state.aisles.push(aisle);
}),
});
return aisle;
},
updateAisle: (uuid, updated) =>
updateAisle: (uuid, updated) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === uuid);
if (aisle) {
Object.assign(aisle, updated);
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
removeAisle: (uuid) =>
removeAisle: (uuid) => {
let removedAisle: Aisle | undefined;
set((state) => {
state.aisles = state.aisles.filter((a) => a.aisleUuid !== uuid);
}),
const index = state.aisles.findIndex((a) => a.aisleUuid === uuid);
if (index !== -1) {
removedAisle = JSON.parse(JSON.stringify(state.aisles[index]));
state.aisles.splice(index, 1);
}
});
return removedAisle;
},
removePoint: (uuid) => {
const removedAisles: Aisle[] = [];
@@ -94,9 +108,11 @@ export const createAisleStore = () => {
},
clearAisles: () => {
const clearedAisles = get().aisles;
set((state) => {
state.aisles = [];
})
});
return clearedAisles;
},
setPosition: (pointUuid, position) => {
@@ -113,34 +129,46 @@ export const createAisleStore = () => {
return updatedAisle;
},
setLayer: (pointUuid, layer) =>
setLayer: (pointUuid, layer) => {
let updatedAisle: Aisle | undefined;
set((state) => {
for (const aisle of state.aisles) {
const point = aisle.points.find((p) => p.pointUuid === pointUuid);
if (point) {
point.layer = layer;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}
}),
});
return updatedAisle;
},
setColor: (aisleUuid, color) =>
setColor: (aisleUuid, color) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle) {
aisle.type.aisleColor = color;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
// Type-specific property setters
setSolidAisleWidth: (aisleUuid, width) =>
setSolidAisleWidth: (aisleUuid, width) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "solid-aisle") {
aisle.type.aisleWidth = width;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
setDashedAisleProperties: (aisleUuid, props) =>
setDashedAisleProperties: (aisleUuid, props) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "dashed-aisle") {
@@ -150,10 +178,14 @@ export const createAisleStore = () => {
aisle.type.dashLength = props.dashLength;
if (props.gapLength !== undefined)
aisle.type.gapLength = props.gapLength;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
setDottedAisleProperties: (aisleUuid, props) =>
setDottedAisleProperties: (aisleUuid, props) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "dotted-aisle") {
@@ -161,18 +193,26 @@ export const createAisleStore = () => {
aisle.type.dotRadius = props.dotRadius;
if (props.gapLength !== undefined)
aisle.type.gapLength = props.gapLength;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
setArrowAisleWidth: (aisleUuid, width) =>
setArrowAisleWidth: (aisleUuid, width) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "arrow-aisle") {
aisle.type.aisleWidth = width;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
setArrowsAisleProperties: (aisleUuid, props) =>
setArrowsAisleProperties: (aisleUuid, props) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "arrows-aisle") {
@@ -182,10 +222,14 @@ export const createAisleStore = () => {
aisle.type.aisleLength = props.aisleLength;
if (props.gapLength !== undefined)
aisle.type.gapLength = props.gapLength;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
setArcAisleWidth: (aisleUuid, props) =>
setArcAisleWidth: (aisleUuid, props) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "arc-aisle") {
@@ -193,18 +237,26 @@ export const createAisleStore = () => {
aisle.type.aisleWidth = props.aisleWidth;
if (props.isFlipped !== undefined)
aisle.type.isFlipped = props.isFlipped;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
setCircleAisleWidth: (aisleUuid, width) =>
setCircleAisleWidth: (aisleUuid, width) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "circle-aisle") {
aisle.type.aisleWidth = width;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
setJunctionAisleProperties: (aisleUuid, props) =>
setJunctionAisleProperties: (aisleUuid, props) => {
let updatedAisle: Aisle | undefined;
set((state) => {
const aisle = state.aisles.find((a) => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === "junction-aisle") {
@@ -212,8 +264,11 @@ export const createAisleStore = () => {
aisle.type.aisleWidth = props.aisleWidth;
if (props.isFlipped !== undefined)
aisle.type.isFlipped = props.isFlipped;
updatedAisle = JSON.parse(JSON.stringify(aisle));
}
}),
});
return updatedAisle;
},
getAisleById: (uuid) => {
return get().aisles.find((a) => a.aisleUuid === uuid);

View File

@@ -1,4 +1,4 @@
import { Object3D } from 'three';
import { Object3D, Vector3 } from 'three';
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
@@ -38,10 +38,16 @@ interface BuilderState {
zoneColor: string;
// Decal Settings
selectedDecal: Object3D | null;
selectedDecal: { decalMesh: Object3D | null, decalData: Decal } | null;
deletableDecal: Object3D | null;
decalDragState: {
isDragging: boolean,
draggingDecalUuid: string | null,
dragOffset: Vector3 | null,
},
// Aisle General
selectedAisle: Object3D | null;
selectedAisle: {aisleMesh: Object3D | null, aisleData: Aisle} | null;
aisleType: AisleTypes;
aisleWidth: number;
aisleColor: AisleColors;
@@ -86,10 +92,12 @@ interface BuilderState {
setZoneColor: (color: string) => void;
// Setters - Decal
setSelectedDecal: (decal: Object3D | null) => void;
setSelectedDecal: (decal: { decalMesh: Object3D | null, decalData: Decal } | null) => void;
setDeletableDecal: (decal: Object3D | null) => void;
setDecalDragState: (isDragging: boolean, draggingDecalUuid: string | null, dragOffset: Vector3 | null) => void;
// Setters - Aisle General
setSelectedAisle: (aisle: Object3D | null) => void;
setSelectedAisle: (aisle: {aisleMesh: Object3D | null, aisleData: Aisle} | null) => void;
setAisleType: (type: AisleTypes) => void;
setAisleWidth: (width: number) => void;
setAisleColor: (color: AisleColors) => void;
@@ -140,11 +148,17 @@ export const useBuilderStore = create<BuilderState>()(
zoneColor: 'blue',
selectedDecal: null,
deletableDecal: null,
decalDragState: {
isDragging: false,
draggingDecalUuid: null,
dragOffset: null,
},
selectedAisle: null,
aisleType: 'solid-aisle',
aisleWidth: 0.1,
aisleColor: 'yellow',
aisleColor: '#FBE50E',
dashLength: 0.5,
gapLength: 0.3,
@@ -287,15 +301,31 @@ export const useBuilderStore = create<BuilderState>()(
// === Setters: Decal ===
setSelectedDecal: (decal: Object3D | null) => {
setSelectedDecal: (decal: { decalMesh: Object3D | null, decalData: Decal } | null) => {
set((state) => {
state.selectedDecal = decal;
})
},
setDeletableDecal: (decal: Object3D | null) => {
set((state) => {
state.deletableDecal = decal;
})
},
setDecalDragState: (isDragging: boolean, draggingDecalUuid: string | null, dragOffset: Vector3 | null) => {
set((state) => {
state.decalDragState = {
isDragging,
draggingDecalUuid,
dragOffset,
}
})
},
// === Setters: Aisle General ===
setSelectedAisle: (aisle: Object3D | null) => {
setSelectedAisle: (aisle: {aisleMesh: Object3D | null, aisleData: Aisle} | null) => {
set((state) => {
state.selectedAisle = aisle;
});

View File

@@ -19,9 +19,9 @@ interface FloorStore {
setBevelStrength: (uuid: string, strength: number) => void;
setDepth: (uuid: string, depth: number) => void;
setMaterial: (uuid: string, sideMaterial: string, topMaterial: string) => void;
addDecal: (floors: string, decal: Decal) => void;
updateDecal: (decalUuid: string, decal: Decal) => void;
removeDecal: (decalUuid: string) => void;
addDecal: (floors: string, decal: Decal) => Decal | undefined;
updateDecal: (decalUuid: string, decal: Partial<Decal>) => Floor | undefined;
removeDecal: (decalUuid: string) => Floor | undefined;
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
updateDecalRotation: (decalUuid: string, rotation: number) => void;
updateDecalScale: (decalUuid: string, scale: number) => void;
@@ -196,28 +196,46 @@ export const createFloorStore = () => {
}
}),
addDecal: (floorUuid, decal) => set(state => {
addDecal: (floorUuid, decal) => {
let addedDecal: Decal | undefined;
set(state => {
const floor = state.floors.find(f => f.floorUuid === floorUuid);
if (floor) {
floor.decals.push(decal);
addedDecal = JSON.parse(JSON.stringify(decal));
}
}),
})
return addedDecal;
},
updateDecal: (decalUuid, updatedDecal) => set(state => {
updateDecal: (decalUuid, updatedDecal) => {
let affectedFloor: Floor | undefined;
set(state => {
for (const floor of state.floors) {
const index = floor.decals.findIndex(d => d.decalUuid === decalUuid);
if (index !== -1) {
floor.decals[index] = updatedDecal;
Object.assign(floor.decals[index], updatedDecal);
affectedFloor = JSON.parse(JSON.stringify(floor));
break;
}
}
}),
});
return affectedFloor;
},
removeDecal: (decalUuid) => set(state => {
removeDecal: (decalUuid) => {
let affectedFloor: Floor | undefined;
set(state => {
for (const floor of state.floors) {
const hasDecal = floor.decals.some(d => d.decalUuid === decalUuid);
if (hasDecal) {
floor.decals = floor.decals.filter(d => d.decalUuid !== decalUuid);
affectedFloor = JSON.parse(JSON.stringify(floor));
}
}),
}
});
return affectedFloor;
},
updateDecalPosition: (decalUuid, position) => set(state => {
for (const floor of state.floors) {

View File

@@ -13,6 +13,9 @@ type UndoRedo3DStore = {
peekUndo3D: () => UndoRedo3DTypes | undefined;
peekRedo3D: () => UndoRedo3DTypes | undefined;
subscribeUndoRedo: (cb: (action: { type: "undo" | "redo"; entry: UndoRedo3DTypes }) => void) => () => void;
_listeners: ((action: { type: "undo" | "redo"; entry: UndoRedo3DTypes }) => void)[];
};
export const createUndoRedo3DStore = () => {
@@ -20,15 +23,14 @@ export const createUndoRedo3DStore = () => {
immer((set, get) => ({
undoStack: [],
redoStack: [],
_listeners: [],
push3D: (entry) => {
set((state) => {
state.undoStack.push(entry);
if (state.undoStack.length > undoRedoConfig.undoRedoCount) {
state.undoStack.shift();
}
state.redoStack = [];
});
},
@@ -41,6 +43,9 @@ export const createUndoRedo3DStore = () => {
state.redoStack.unshift(lastAction);
}
});
if (lastAction) {
get()._listeners.forEach((cb) => cb({ type: "undo", entry: lastAction! }));
}
return lastAction;
},
@@ -52,6 +57,9 @@ export const createUndoRedo3DStore = () => {
state.undoStack.push(redoAction);
}
});
if (redoAction) {
get()._listeners.forEach((cb) => cb({ type: "redo", entry: redoAction! }));
}
return redoAction;
},
@@ -71,8 +79,19 @@ export const createUndoRedo3DStore = () => {
const stack = get().redoStack;
return stack.length > 0 ? stack[0] : undefined;
},
subscribeUndoRedo: (cb) => {
set((state) => {
state._listeners.push(cb);
});
return () => {
set((state) => {
state._listeners = state._listeners.filter((l) => l !== cb);
});
};
},
}))
);
}
};
export type UndoRedo3DStoreType = ReturnType<typeof createUndoRedo3DStore>;

View File

@@ -9,10 +9,10 @@ interface WallStore {
removeWall: (uuid: string) => void;
clearWalls: () => void;
removeWallByPoints: (Points: [Point, Point]) => Wall | undefined;
addDecal: (wallUuid: string, decal: Decal) => void;
updateDecal: (decalUuid: string, decal: Decal) => void;
removeDecal: (decalUuid: string) => void;
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
addDecal: (wallUuid: string, decal: Decal) => Decal | undefined;
updateDecal: (decalUuid: string, decal: Partial<Decal>) => Wall | undefined;
removeDecal: (decalUuid: string) => Wall | undefined;
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => Wall | undefined;
updateDecalRotation: (decalUuid: string, rotation: number) => void;
updateDecalScale: (decalUuid: string, scale: number) => void;
@@ -83,37 +83,60 @@ export const createWallStore = () => {
return removedWall;
},
addDecal: (wallUuid, decal) => set((state) => {
addDecal: (wallUuid, decal) => {
let addedDecal: Decal | undefined;
set((state) => {
const wallToUpdate = state.walls.find(w => w.wallUuid === wallUuid);
if (wallToUpdate) {
wallToUpdate.decals.push(decal);
addedDecal = JSON.parse(JSON.stringify(decal));
}
}),
})
return addedDecal;
},
updateDecal: (decalUuid, decal) => set((state) => {
updateDecal: (decalUuid, decal) => {
let affectedWall: Wall | undefined;
set((state) => {
for (const wall of state.walls) {
const decalToUpdate = wall.decals.find(d => d.decalUuid === decalUuid);
if (decalToUpdate) {
Object.assign(decalToUpdate, decal);
affectedWall = JSON.parse(JSON.stringify(wall));
}
}
}),
});
return affectedWall;
},
removeDecal: (decalUuid) => set((state) => {
removeDecal: (decalUuid) => {
let affectedWall: Wall | undefined;
set((state) => {
for (const wall of state.walls) {
const hasDecal = wall.decals.some(d => d.decalUuid === decalUuid);
if (hasDecal) {
wall.decals = wall.decals.filter(d => d.decalUuid !== decalUuid);
affectedWall = JSON.parse(JSON.stringify(wall));
}
}),
}
});
return affectedWall;
},
updateDecalPosition: (decalUuid, position) => set((state) => {
updateDecalPosition: (decalUuid, position) => {
let affectedWall: Wall | undefined;
set((state) => {
for (const wall of state.walls) {
const decal = wall.decals.find(d => d.decalUuid === decalUuid);
if (decal) {
decal.decalPosition = position;
affectedWall = JSON.parse(JSON.stringify(wall));
break;
}
}
}),
})
return affectedWall;
},
updateDecalRotation: (decalUuid, rotation) => set((state) => {
for (const wall of state.walls) {

View File

@@ -1500,6 +1500,28 @@
border-radius: #{$border-radius-large};
margin-right: 4px;
overflow: hidden;
&.yellow-black {
background-color: black;
background-size: 10px 10px;
background-image: repeating-linear-gradient(
45deg,
#FBE50E 0,
#FBE50E 2px,
black 0,
black 50%
);
}
&.white-black {
background-color: black;
background-size: 10px 10px;
background-image: repeating-linear-gradient(
45deg,
white 0,
white 2px,
black 0,
black 50%
);
}
}
.aisle-color {
@@ -1984,17 +2006,51 @@
max-height: 50vh;
overflow: auto;
h2,
.searched-content {
.header,
.searched-content,
.categories-header {
color: var(--text-color);
font-family: $large;
font-weight: $bold-weight;
padding: 8px;
padding: 4px 8px;
@include flex-space-between;
.back-button {
cursor: pointer;
position: sticky;
top: 0;
z-index: 4;
background: var(--background-color);
border-radius: 0 0 12px 12px;
backdrop-filter: blur(4px);
.search-for {
display: inline-block;
color: var(--accent-color);
max-width: 238px;
overflow: hidden;
text-overflow: ellipsis;
}
.back-button {
padding: 4px 12px;
border-radius: 20px;
transition: background 0.2s;
display: flex;
align-items: center;
.back-arrow {
margin-right: 2px;
transform: translateX(0) translateY(-1px) scale(1.6) rotate(90deg);
transition: all 0.2s;
}
&:hover {
background: var(--background-color-solid);
.back-arrow {
transform: translateX(-2px) translateY(-1px) scale(1.6)
rotate(90deg);
}
}
}
}
.categories-header,
.searched-content {
position: relative;
padding: 8px;
}
.categories-container {
@@ -2132,6 +2188,7 @@
border: 1px solid #564b69;
padding: 12px 10px;
border-radius: 15px;
margin-bottom: 4px;
.catogory-asset-filter-wrapper {
display: flex;

View File

@@ -144,7 +144,8 @@
}
.cards-container {
height: 100%;
height: auto;
max-height: 100%;
display: flex;
flex-wrap: wrap;
position: relative;

View File

@@ -88,6 +88,7 @@ interface Decal {
decalType: WallDecal | FloorDecal;
decalPosition: [number, number, number];
decalRotation: number;
decalOpacity: number;
decalScale: number;
}
@@ -150,7 +151,7 @@ type Zones = Zone[];
type AisleTypes = 'solid-aisle' | 'dashed-aisle' | 'stripped-aisle' | 'dotted-aisle' | 'arrow-aisle' | 'arrows-aisle' | 'arc-aisle' | 'circle-aisle' | 'junction-aisle';
type AisleColors = 'gray' | 'yellow' | 'green' | 'orange' | 'blue' | 'purple' | 'red' | '#66FF00' | 'yellow-black' | 'white-black'
type AisleColors = '#6F6F7A' | '#FBE50E' | '#43C06D' | '#FF711B' | '#488EF6' | '#AF52DE' | '#FF3B30' | '#66FF00' | 'yellow-black' | 'white-black'
interface SolidAisle {
aisleType: 'solid-aisle';

View File

@@ -1,15 +1,19 @@
const DB_NAME = 'GLTFStorage';
const STORE_NAME = 'models';
const DB_VERSION = 1;
export function initializeDB(): Promise<IDBDatabase> {
const ASSET_DB_NAME = 'GLTFStorage';
const ASSET_STORE_NAME = 'models';
const IMAGE_DB_NAME = 'ImageStorage';
const IMAGE_STORE_NAME = 'images';
export function initializeAssetDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
const request = indexedDB.open(ASSET_DB_NAME, DB_VERSION);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME);
if (!db.objectStoreNames.contains(ASSET_STORE_NAME)) {
db.createObjectStore(ASSET_STORE_NAME);
}
};
@@ -19,11 +23,11 @@ export function initializeDB(): Promise<IDBDatabase> {
}
export async function storeGLTF(key: string, file: Blob): Promise<void> {
const db = await initializeDB();
const db = await initializeAssetDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const transaction = db.transaction(ASSET_STORE_NAME, 'readwrite');
const store = transaction.objectStore(ASSET_STORE_NAME);
const request = store.put(file, key);
request.onsuccess = () => resolve();
@@ -32,11 +36,53 @@ export async function storeGLTF(key: string, file: Blob): Promise<void> {
}
export async function retrieveGLTF(key: string): Promise<Blob | undefined> {
const db = await initializeDB();
const db = await initializeAssetDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, 'readonly');
const store = transaction.objectStore(STORE_NAME);
const transaction = db.transaction(ASSET_STORE_NAME, 'readonly');
const store = transaction.objectStore(ASSET_STORE_NAME);
const request = store.get(key);
request.onsuccess = () => resolve(request.result as Blob | undefined);
request.onerror = () => reject(request.error);
});
}
export function initializeImageDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(IMAGE_DB_NAME, DB_VERSION);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(IMAGE_STORE_NAME)) {
db.createObjectStore(IMAGE_STORE_NAME);
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
export async function storeImage(key: string, file: Blob): Promise<void> {
const db = await initializeImageDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(IMAGE_STORE_NAME, 'readwrite');
const store = transaction.objectStore(IMAGE_STORE_NAME);
const request = store.put(file, key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
export async function retrieveImage(key: string): Promise<Blob | undefined> {
const db = await initializeImageDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(IMAGE_STORE_NAME, 'readonly');
const store = transaction.objectStore(IMAGE_STORE_NAME);
const request = store.get(key);
request.onsuccess = () => resolve(request.result as Blob | undefined);