Compare commits

..

5 Commits

46 changed files with 374 additions and 1280 deletions

View File

@@ -216,139 +216,3 @@ const DashboardProjects: React.FC = () => {
export default DashboardProjects; export default DashboardProjects;
// const MyProjects = () => {
// const { projectSocket } = useSocketStore();
// const { userId, organization } = getUserData();
// const fetchAllProjects = async () => {
// try {
// const projects = await getAllProjects(userId, organization);
// if (!projects || !projects.Projects) return;
// if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) {
// setWorkspaceProjects(projects);
// }
// } catch (error) {
// console.error("Error fetching projects:", error);
// }
// };
// const handleDeleteProject = async (projectId: any) => {
// try {
// // const deletedProject = await deleteProject(
// // projectId,
// // userId,
// // organization
// // );
// // console.log('deletedProject: ', deletedProject);
// const deleteProjects = {
// projectId,
// organization,
// userId,
// };
// //socket for deleting the project
// if (projectSocket) {
// projectSocket.emit("v1:project:delete", deleteProjects);
// } else {
// console.error("Socket is not connected.");
// }
// setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => {
// if (!Array.isArray(prevDiscardedProjects?.Projects)) {
// return prevDiscardedProjects;
// }
// const updatedProjectDatas = prevDiscardedProjects.Projects.filter(
// (project) => project._id !== projectId
// );
// return {
// ...prevDiscardedProjects,
// Projects: updatedProjectDatas,
// };
// });
// setIsSearchActive(false);
// } catch (error) {
// console.error("Error deleting project:", error);
// }
// };
// const handleDuplicateWorkspaceProject = async (
// projectId: string,
// projectName: string,
// thumbnail: string
// ) => {
// // await handleDuplicateProjects({
// // userId,
// // organization,
// // projectId,
// // projectName,
// // projectSocket,
// // thumbnail,
// // setWorkspaceProjects,
// // setIsSearchActive
// // });
// const duplicateProjectData = {
// userId,
// thumbnail,
// organization,
// projectUuid: projectId,
// projectName,
// };
// projectSocket.emit("v1:project:Duplicate", duplicateProjectData);
// };
// const renderProjects = () => {
// if (activeFolder !== "myProjects") return null;
// const projectList = workspaceProjects[Object.keys(workspaceProjects)[0]];
// if (!projectList?.length) {
// return <div className="empty-state">No projects found</div>;
// }
// return projectList.map((project) => (
// <DashboardCard
// key={project._id}
// projectName={project.projectName}
// thumbnail={project.thumbnail}
// projectId={project._id}
// createdAt={project.createdAt}
// handleDeleteProject={handleDeleteProject}
// setIsSearchActive={setIsSearchActive}
// handleDuplicateWorkspaceProject={handleDuplicateWorkspaceProject}
// setProjectDuplicateData={setProjectDuplicateData}
// />
// ));
// };
// const renderSharedProjects = () => {
// if (activeFolder !== "shared") return null;
// const projectList = workspaceProjects[Object.keys(workspaceProjects)[0]];
// if (!projectList?.length) {
// return <div className="empty-state">No projects found</div>;
// }
// return projectList.map((project) => (
// <DashboardCard
// key={project._id}
// projectName={project.projectName}
// thumbnail={project.thumbnail}
// projectId={project._id}
// createdAt={project.createdAt}
// handleDeleteProject={handleDeleteProject}
// setIsSearchActive={setIsSearchActive}
// handleDuplicateWorkspaceProject={handleDuplicateWorkspaceProject}
// setProjectDuplicateData={setProjectDuplicateData}
// />
// ));
// };
// useEffect(() => {
// if (!isSearchActive) {
// fetchAllProjects();
// }
// }, [isSearchActive]);
// return null
// }

View File

@@ -5,8 +5,6 @@ import { RemoveIcon } from "../../../icons/ExportCommonIcons";
import PositionInput from "../customInput/PositionInputs"; import PositionInput from "../customInput/PositionInputs";
import RotationInput from "../customInput/RotationInput"; import RotationInput from "../customInput/RotationInput";
import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store"; import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import { center } from "@turf/turf";
interface UserData { interface UserData {
id: number; // Unique identifier for the user data id: number; // Unique identifier for the user data
@@ -20,10 +18,6 @@ const AssetProperties: React.FC = () => {
const { selectedFloorItem } = useSelectedFloorItem(); const { selectedFloorItem } = useSelectedFloorItem();
const { objectPosition } = useObjectPosition(); const { objectPosition } = useObjectPosition();
const { objectRotation } = useObjectRotation(); const { objectRotation } = useObjectRotation();
const { assetStore } = useSceneContext();
const { assets, setCurrentAnimation } = assetStore()
const [hoveredIndex, setHoveredIndex] = useState<any>(null);
const [isPlaying, setIsplaying] = useState(false);
// Function to handle adding new user data // Function to handle adding new user data
const handleAddUserData = () => { const handleAddUserData = () => {
const newUserData: UserData = { const newUserData: UserData = {
@@ -51,12 +45,6 @@ const AssetProperties: React.FC = () => {
); );
}; };
const handleAnimationClick = (animation: string) => {
if (selectedFloorItem) {
const isPlaying = selectedFloorItem.animationState?.playing || false;
setCurrentAnimation(selectedFloorItem.uuid, animation, !isPlaying);
}
}
return ( return (
<div className="asset-properties-container"> <div className="asset-properties-container">
{/* Name */} {/* Name */}
@@ -108,40 +96,6 @@ const AssetProperties: React.FC = () => {
+ Add + Add
</div> </div>
</section> </section>
<div style={{ display: "flex", flexDirection: "column", outline: "1px solid var(--border-color)" }}>
{selectedFloorItem.uuid && <div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>Animations</div>}
{assets.map((asset) => (
<div key={asset.modelUuid} className="asset-item">
{asset.modelUuid === selectedFloorItem.uuid &&
asset.animations &&
asset.animations.length > 0 &&
asset.animations.map((animation, index) => (
<div
key={index}
style={{ gap: "15px", cursor: "pointer", padding: "5px" }}
>
<div
onClick={() => handleAnimationClick(animation)}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
style={{
height: "20px",
width: "100%",
borderRadius: "5px",
background:
hoveredIndex === index
? "#7b4cd3"
: "transparent",
}}
>
{animation.charAt(0).toUpperCase() +
animation.slice(1).toLowerCase()}
</div>
</div>
))}
</div>
))}
</div>
</div> </div>
); );
}; };

View File

@@ -133,17 +133,11 @@ const SelectedWallProperties = () => {
<InputWithDropDown <InputWithDropDown
label="Height" label="Height"
value={height} value={height}
min={1}
max={25}
step={1}
onChange={handleHeightChange} onChange={handleHeightChange}
/> />
<InputWithDropDown <InputWithDropDown
label="Thickness" label="Thickness"
value={thickness} value={thickness}
min={0.1}
max={2}
step={0.1}
onChange={handleThicknessChange} onChange={handleThicknessChange}
/> />
</div> </div>

View File

@@ -63,17 +63,11 @@ const WallProperties = () => {
<InputWithDropDown <InputWithDropDown
label="Height" label="Height"
value={`${wallHeight}`} value={`${wallHeight}`}
min={1}
max={25}
step={1}
onChange={(val) => handleHeightChange(val)} onChange={(val) => handleHeightChange(val)}
/> />
<InputWithDropDown <InputWithDropDown
label="Thickness" label="Thickness"
value={`${wallThickness}`} value={`${wallThickness}`}
min={0.1}
max={2}
step={0.1}
onChange={(val) => handleThicknessChange(val)} onChange={(val) => handleThicknessChange(val)}
/> />
</div> </div>

View File

@@ -9,7 +9,6 @@ import { useActiveUsers } from "../../store/builder/store";
import { getUserData } from "../../functions/getUserData"; import { getUserData } from "../../functions/getUserData";
import { getProjectSharedList } from "../../services/factoryBuilder/collab/getProjectSharedList"; import { getProjectSharedList } from "../../services/factoryBuilder/collab/getProjectSharedList";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { projection } from "@turf/turf";
import { shareAccess } from "../../services/factoryBuilder/collab/shareAccess"; import { shareAccess } from "../../services/factoryBuilder/collab/shareAccess";
import { getAvatarColor } from "../../modules/collaboration/functions/getAvatarColor"; import { getAvatarColor } from "../../modules/collaboration/functions/getAvatarColor";

View File

@@ -6,7 +6,6 @@ import { useProjectName } from "../../store/builder/store";
import { getAllProjects } from "../../services/dashboard/getAllProjects"; import { getAllProjects } from "../../services/dashboard/getAllProjects";
import { useComparisonProduct } from "../../store/simulation/useSimulationStore"; import { useComparisonProduct } from "../../store/simulation/useSimulationStore";
import { getUserData } from "../../functions/getUserData"; import { getUserData } from "../../functions/getUserData";
import { recentlyViewed } from "../../services/dashboard/recentlyViewed";
import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject"; import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject";
interface LoadingPageProps { interface LoadingPageProps {
@@ -20,23 +19,6 @@ const LoadingPage: React.FC<LoadingPageProps> = ({ progress }) => {
const { userId, organization } = getUserData(); const { userId, organization } = getUserData();
const validatedProgress = Math.min(100, Math.max(0, progress)); const validatedProgress = Math.min(100, Math.max(0, progress));
// useEffect(() => {
// if (!userId) return;
// // getAllProjects(userId, organization).then((projects) => {
// // sharedWithMeProjects().then((shared) => {
// // console.log('filterProject: ', shared);
// // const filterProject = (projects?.Projects || shared)?.find((val: any) => val.projectUuid === projectId || val._id === projectId)
// // console.log('filterProject: ', filterProject);
// // // setProjectName(filterProject?.projectName)
// // })
// // }).catch(() => {
// // console.error("Error fetching projects")
// // })
// }, []);
useEffect(() => { useEffect(() => {
if (!userId) { if (!userId) {
console.error("User data not found in localStorage"); console.error("User data not found in localStorage");

View File

@@ -1,21 +1,26 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import useLayoutStore from "../../store/builder/uselayoutStore"; import useLayoutStore from "../../store/builder/uselayoutStore";
import { useDfxUpload } from "../../store/builder/store"; import { useActiveLayer, useDfxUpload } from "../../store/builder/store";
import DxfParser from "dxf-parser"; import DxfParser from "dxf-parser";
import { getWallPointsFromBlueprint } from "../../modules/builder/dfx/functions/getWallPointsFromBlueprint"; import { getWallPointsFromBlueprint } from "../../modules/builder/dfx/functions/getWallPointsFromBlueprint";
import { convertDXFToThree } from "../../modules/builder/dfx/functions/convertDxfToThree"; import { convertDXFToThree } from "../../modules/builder/dfx/functions/convertDxfToThree";
import { AIIcon } from "../icons/ExportCommonIcons"; import { AIIcon } from "../icons/ExportCommonIcons";
import { useBuilderStore } from "../../store/builder/useBuilderStore";
import { useSceneContext } from "../../modules/scene/sceneContext";
type DXFData = any; type DXFData = any;
const SelectFloorPlan: React.FC = () => { const SelectFloorPlan: React.FC = () => {
// Access layout state and state setters // Access layout state and state setters
const { currentLayout, setLayout } = useLayoutStore(); const { currentLayout, setLayout } = useLayoutStore();
// Access DXF-related states and setters // Access DXF-related states and setters
const { setDfxUploaded, setDfxGenerate, setObjValue, objValue } = const { setDfxUploaded, setDxfWallGenerate, setObjValue, objValue } =
useDfxUpload(); useDfxUpload();
const { activeLayer } = useActiveLayer();
const { wallThickness, wallHeight, insideMaterial, outsideMaterial } = useBuilderStore();
const { wallStore } = useSceneContext();
const { addWall } = wallStore();
// Local state to store the parsed DXF file // Local state to store the parsed DXF file
const [parsedFile, setParsedFile] = useState<DXFData | undefined>(undefined); const [parsedFile, setParsedFile] = useState<DXFData | undefined>(undefined);
const { walls } = wallStore();
// Flag to trigger generation after file upload // Flag to trigger generation after file upload
const [generate, setGenerate] = useState<boolean>(false); const [generate, setGenerate] = useState<boolean>(false);
@@ -58,8 +63,9 @@ const SelectFloorPlan: React.FC = () => {
if (parsedFile !== undefined) { if (parsedFile !== undefined) {
getWallPointsFromBlueprint({ getWallPointsFromBlueprint({
parsedData: parsedFile, parsedData: parsedFile,
setDfxGenerate, setDxfWallGenerate,
objValue, objValue,
wallThickness, wallHeight, outsideMaterial, insideMaterial, activeLayer, addWall, walls
}); });
} }
}, [generate]); }, [generate]);

View File

@@ -1,6 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { getSearchUsers } from "../../../services/factoryBuilder/collab/getSearchUsers";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { getSearchUsers } from "../../../services/factoryBuilder/collab/getSearchUsers";
import { shareProject } from "../../../services/factoryBuilder/collab/shareProject"; import { shareProject } from "../../../services/factoryBuilder/collab/shareProject";
import { getUserData } from "../../../functions/getUserData"; import { getUserData } from "../../../functions/getUserData";
@@ -11,121 +12,120 @@ interface UserData {
} }
interface MultiEmailProps { interface MultiEmailProps {
users: any, users: Array<any>;
getData: any, getData: () => void;
} }
const MultiEmailInvite: React.FC<MultiEmailProps> = ({ users, getData }) => { const MultiEmailInvite: React.FC<MultiEmailProps> = ({ users, getData }) => {
const [emails, setEmails] = useState<any>([]); const [selectedUsers, setSelectedUsers] = useState<UserData[]>([]);
const [searchedEmail, setSearchedEmail] = useState<UserData[]>([]); const [searchResults, setSearchResults] = useState<UserData[]>([]);
const [inputFocus, setInputFocus] = useState(false);
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const [isFocused, setIsFocused] = useState(false);
const { projectId } = useParams(); const { projectId } = useParams();
const { userId } = getUserData(); const { userId } = getUserData();
const handleAddEmail = async (selectedUser: UserData) => {
if (!projectId || !selectedUser) return
const trimmedEmail = inputValue.trim();
setEmails((prev: any[]) => {
if (!selectedUser) return prev;
const isNotCurrentUser = selectedUser._id !== userId;
const alreadyExistsInEmails = prev.some(email => email._id === selectedUser._id);
const alreadyExistsInUsers = users.some((val: any) => val.userId === selectedUser._id);
if (isNotCurrentUser && !alreadyExistsInEmails && !alreadyExistsInUsers) {
return [...prev, selectedUser];
}
return prev; const handleSearchInput = async (e: React.ChangeEvent<HTMLInputElement>) => {
}); const value = e.target.value.trim();
setInputValue(""); // Clear the input field after adding setInputValue(value);
};
const handleSearchMail = async (e: any) => { if (value.length < 3) return;
setInputValue(e.target.value);
const trimmedEmail = e.target.value.trim();
if (trimmedEmail.length < 3) return;
try { try {
const searchedMail = await getSearchUsers(trimmedEmail); const result = await getSearchUsers(value);
const filteredEmail = searchedMail.sharchMail?.filtered; const filtered = result?.sharchMail?.filtered || [];
if (filteredEmail) { setSearchResults(filtered);
setSearchedEmail(filteredEmail) } catch (err) {
} console.error("Search failed:", err);
} catch (error) {
console.error("Failed to search mail:", error);
} }
}; };
const handleAddUser = (user: UserData) => {
if (!user || user._id === userId) return;
const handleKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => { const isAlreadySelected = selectedUsers.some(u => u._id === user._id);
if (e.key === "Enter" || e.key === "," && searchedEmail.length > 0) { const isAlreadyShared = users.some(u => u.userId === user._id);
if (!isAlreadySelected && !isAlreadyShared) {
setSelectedUsers(prev => [...prev, user]);
}
setInputValue("");
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if ((e.key === "Enter" || e.key === ",") && searchResults.length > 0) {
e.preventDefault(); e.preventDefault();
handleAddEmail(searchedEmail[0]); handleAddUser(searchResults[0]);
} }
}; };
const handleRemoveEmail = (idToRemove: string) => { const handleRemoveUser = (userIdToRemove: string) => {
setEmails((prev: any) => prev.filter((email: any) => email._id !== idToRemove)); setSelectedUsers(prev => prev.filter(user => user._id !== userIdToRemove));
}; };
const validateEmail = (email: string) => { const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email); return emailRegex.test(email);
}; };
const handleInvite = () => { const handleAddEmail = async () => {
if (!projectId) return; if (!projectId) return;
try {
emails.forEach((user: any) => {
shareProject(user._id, projectId)
.then((res) => {
console.log("sharedProject:", res);
}) try {
.catch((err) => { await Promise.all(
console.error("Error sharing project:", err); selectedUsers.map(user =>
}); shareProject(user._id, projectId)
setEmails([]) .then(res => console.log("Shared:", res))
setInputValue("") .catch(err => console.error("Share error:", err))
}); )
setTimeout(() => { );
getData()
}, 1000); setSelectedUsers([]);
setInputValue("");
setTimeout(getData, 1000);
} catch (error) { } catch (error) {
console.error("General error:", error); console.error("Invite error:", error);
} }
}; };
return ( return (
<div className="multi-email-invite-input-container"> <div className="multi-email-invite-input-container">
<div className={`multi-email-invite-input${inputFocus ? " active" : ""}`}> <div className={`multi-email-invite-input${isFocused ? " active" : ""}`}>
{emails.map((email: any, index: number) => ( {selectedUsers.map(user => (
<div key={index} className="entered-emails"> <div key={user._id} className="entered-emails">
{email.Email} {user.Email}
<span onClick={() => handleRemoveEmail(email._id)}>&times;</span> <span onClick={() => handleRemoveUser(user._id)}>&times;</span>
</div> </div>
))} ))}
<input <input
type="text" type="text"
value={inputValue} value={inputValue}
onChange={(e) => handleSearchMail(e)} onChange={handleSearchInput}
onFocus={() => setInputFocus(true)} onFocus={() => setIsFocused(true)}
// onBlur={() => setInputFocus(false)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder="Enter email and press Enter or comma to seperate" placeholder="Enter email and press Enter or comma"
/> />
</div> </div>
<div onClick={handleInvite} className="invite-button">
<div onClick={handleAddEmail} className="invite-button">
Add Add
</div> </div>
{inputFocus && inputValue.length > 2 && searchedEmail && searchedEmail.length > 0 && (
{isFocused && inputValue.length > 2 && searchResults.length > 0 && (
<div className="users-list-container"> <div className="users-list-container">
{/* list available users here */} {searchResults.map(user => (
{searchedEmail.map((val: any, i: any) => ( <div
<div onClick={(e) => { key={user._id}
handleAddEmail(val) onClick={() => {
setInputFocus(false) handleAddUser(user);
}} key={i} > setIsFocused(false);
{val?.Email} }}
>
{user.Email}
</div> </div>
))} ))}
</div> </div>

View File

@@ -7,7 +7,7 @@ import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import defaultMaterial from '../../../assets/textures/floor/wall-tex.png'; import defaultMaterial from '../../../assets/textures/floor/wall-tex.png';
import useModuleStore from '../../../store/useModuleStore'; import useModuleStore from '../../../store/useModuleStore';
function DecalInstance({ visible = true, decal, zPosition = decal.decalPosition[2] }: { visible?: boolean, decal: Decal, zPosition?: number }) { function DecalInstance({ visible = true, decal }: { visible?: boolean, decal: Decal }) {
const { setSelectedWall, setSelectedFloor, selectedDecal, setSelectedDecal } = useBuilderStore(); const { setSelectedWall, setSelectedFloor, selectedDecal, setSelectedDecal } = useBuilderStore();
const { togglView } = useToggleView(); const { togglView } = useToggleView();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
@@ -17,7 +17,7 @@ function DecalInstance({ visible = true, decal, zPosition = decal.decalPosition[
<Decal <Decal
// debug // debug
visible={visible} visible={visible}
position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]} position={decal.decalPosition}
rotation={[0, 0, decal.decalRotation]} rotation={[0, 0, decal.decalRotation]}
scale={[decal.decalScale, decal.decalScale, 0.01]} scale={[decal.decalScale, decal.decalScale, 0.01]}
userData={decal} userData={decal}

View File

@@ -1,4 +1,4 @@
import * as THREE from "three" import * as THREE from "three"
import { useEffect } from 'react' import { useEffect } from 'react'
import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi'; import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi';
import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store'; import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store';
@@ -269,7 +269,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
useEffect(() => { useEffect(() => {
const canvasElement = gl.domElement; const canvasElement = gl.domElement;
const onDrop = (event: DragEvent) => { const onDrop = (event: any) => {
if (!event.dataTransfer?.files[0]) return; if (!event.dataTransfer?.files[0]) return;
if (selectedItem.id !== "" && event.dataTransfer?.files[0] && selectedItem.category !== 'Fenestration') { if (selectedItem.id !== "" && event.dataTransfer?.files[0] && selectedItem.category !== 'Fenestration') {

View File

@@ -6,7 +6,7 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
import { AssetBoundingBox } from '../../functions/assetBoundingBox'; import { AssetBoundingBox } from '../../functions/assetBoundingBox';
import { CameraControls, Html } from '@react-three/drei'; import { CameraControls } from '@react-three/drei';
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore'; import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore';
import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore'; import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore';
@@ -23,7 +23,7 @@ function Model({ asset }: { readonly asset: Asset }) {
const { subModule } = useSubModuleStore(); const { subModule } = useSubModuleStore();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { assetStore, eventStore, productStore } = useSceneContext(); const { assetStore, eventStore, productStore } = useSceneContext();
const { assets, removeAsset, setAnimations } = assetStore(); const { removeAsset } = assetStore();
const { setTop } = useTopData(); const { setTop } = useTopData();
const { setLeft } = useLeftData(); const { setLeft } = useLeftData();
const { getIsEventInProduct } = productStore(); const { getIsEventInProduct } = productStore();
@@ -46,9 +46,6 @@ function Model({ asset }: { readonly asset: Asset }) {
const { selectedVersion } = selectedVersionStore(); const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams(); const { projectId } = useParams();
const { userId, organization } = getUserData(); const { userId, organization } = getUserData();
const [animationNames, setAnimationNames] = useState<string[]>([]);
const mixerRef = useRef<THREE.AnimationMixer>();
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
useEffect(() => { useEffect(() => {
setDeletableFloorItem(null); setDeletableFloorItem(null);
@@ -63,45 +60,11 @@ function Model({ asset }: { readonly asset: Asset }) {
const loadModel = async () => { const loadModel = async () => {
try { try {
// Check Cache // Check Cache
// const assetId = asset.assetId;
// const cachedModel = THREE.Cache.get(assetId);
// if (cachedModel) {
// setGltfScene(cachedModel.scene.clone());
// calculateBoundingBox(cachedModel.scene);
// return;
// }
// Check Cache
// const assetId = asset.assetId;
// console.log('assetId: ', assetId);
// const cachedModel = THREE.Cache.get(assetId);
// console.log('cachedModel: ', cachedModel);
// if (cachedModel) {
// setGltfScene(cachedModel.scene.clone());
// calculateBoundingBox(cachedModel.scene);
// return;
// }
const assetId = asset.assetId; const assetId = asset.assetId;
const cachedModel = THREE.Cache.get(assetId); const cachedModel = THREE.Cache.get(assetId);
if (cachedModel) { if (cachedModel) {
const clonedScene = cachedModel.scene.clone(); setGltfScene(cachedModel.scene.clone());
clonedScene.animations = cachedModel.animations || []; calculateBoundingBox(cachedModel.scene);
setGltfScene(clonedScene);
calculateBoundingBox(clonedScene);
if (cachedModel.animations && clonedScene.animations.length > 0) {
const animationName = clonedScene.animations.map((clip: any) => clip.name);
setAnimationNames(animationName)
setAnimations(asset.modelUuid, animationName)
mixerRef.current = new THREE.AnimationMixer(clonedScene);
clonedScene.animations.forEach((animation: any) => {
const action = mixerRef.current!.clipAction(animation);
actions.current[animation.name] = action;
});
} else {
console.log('No animations');
}
return; return;
} }
@@ -296,32 +259,6 @@ function Model({ asset }: { readonly asset: Asset }) {
clearSelectedAsset() clearSelectedAsset()
} }
} }
useFrame((_, delta) => {
if (mixerRef.current) {
mixerRef.current.update(delta);
}
});
useEffect(() => {
const handlePlay = (clipName: string) => {
console.log('clipName: ', clipName, asset.animationState);
if (!mixerRef.current) return;
Object.values(actions.current).forEach((action) => action.stop());
const action = actions.current[clipName];
if (action && asset.animationState?.playing) {
action.reset().setLoop(THREE.LoopOnce, 1).play();
console.log(`Playing: ${clipName}`);
} else {
console.warn(`No action found for: ${clipName}`);
}
};
handlePlay(asset.animationState?.current || '');
}, [asset])
return ( return (
<group <group
@@ -369,18 +306,7 @@ function Model({ asset }: { readonly asset: Asset }) {
<AssetBoundingBox boundingBox={boundingBox} /> <AssetBoundingBox boundingBox={boundingBox} />
) )
)} )}
{/* <group > </group>
<Html>
<div style={{ position: 'absolute', }}>
{animationNames.map((name) => (
<button key={name} onClick={() => handlePlay(name)} style={{ margin: 4 }}>
{name}
</button>
))}
</div>
</Html>
</group> */}
</group >
); );
} }

View File

@@ -2,8 +2,8 @@
import * as THREE from "three"; import * as THREE from "three";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { Geometry } from "@react-three/csg"; import { Bvh } from "@react-three/drei";
////////// Zustand State Imports ////////// ////////// Zustand State Imports //////////
@@ -39,14 +39,16 @@ import ZoneGroup from "./zone/zoneGroup";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useBuilderStore } from "../../store/builder/useBuilderStore"; import { useBuilderStore } from "../../store/builder/useBuilderStore";
import { getUserData } from "../../functions/getUserData"; import { getUserData } from "../../functions/getUserData";
import WallAssetGroup from "./wallAsset/wallAssetGroup";
export default function Builder() { export default function Builder() {
const state = useThree<Types.ThreeState>(); const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
const plane = useRef<THREE.Mesh>(null);
const csgRef = useRef<any>(null);
const { toggleView } = useToggleView(); // Assigning the scene and camera from the Three.js state to the references.
const plane = useRef<THREE.Mesh>(null); // Reference for a plane object for raycaster reference.
const grid = useRef() as any; // Reference for a grid object for raycaster reference.
const { toggleView } = useToggleView(); // State for toggling between 2D and 3D.
const { setToolMode } = useToolMode(); const { setToolMode } = useToolMode();
const { setRoofVisibility } = useRoofVisibility(); const { setRoofVisibility } = useRoofVisibility();
const { setWallVisibility } = useWallVisibility(); const { setWallVisibility } = useWallVisibility();
@@ -57,6 +59,14 @@ export default function Builder() {
const { setHoveredPoint, setHoveredLine } = useBuilderStore(); const { setHoveredPoint, setHoveredLine } = useBuilderStore();
const { userId, organization } = getUserData(); const { userId, organization } = getUserData();
// const loader = new GLTFLoader();
// const dracoLoader = new DRACOLoader();
// dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
// loader.setDRACOLoader(dracoLoader);
////////// All Toggle's //////////
useEffect(() => { useEffect(() => {
if (!toggleView) { if (!toggleView) {
setHoveredLine(null); setHoveredLine(null);
@@ -81,32 +91,30 @@ export default function Builder() {
fetchVisibility(); fetchVisibility();
}, []); }, []);
useFrame(() => { ////////// Return //////////
if (csgRef.current) {
csgRef.current.update();
}
})
return ( return (
<> <>
<Ground plane={plane} /> <Ground grid={grid} plane={plane} />
<SocketResponses /> <SocketResponses />
{/* <WallsAndWallItems
CSGGroup={CSGGroup}
setSelectedItemsIndex={setSelectedItemsIndex}
selectedItemsIndex={selectedItemsIndex}
currentWallItem={currentWallItem}
csg={csg}
lines={lines}
hoveredDeletableWallItem={hoveredDeletableWallItem}
/> */}
<AssetsGroup plane={plane} /> <AssetsGroup plane={plane} />
<mesh name='Walls-And-WallAssets-Group'>
<Geometry ref={csgRef} useGroups>
<WallGroup />
<WallAssetGroup />
</Geometry>
</mesh>
<AislesGroup /> <AislesGroup />
<WallGroup />
<FloorGroup /> <FloorGroup />
<ZoneGroup /> <ZoneGroup />

View File

@@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useDfxUpload, useSocketStore, useToggleView, useUpdateScene } from '../../../store/builder/store'; import { useActiveLayer, useDfxUpload, useSocketStore, useToggleView, useUpdateScene } from '../../../store/builder/store';
import { LineBasicMaterial, Line } from 'three'; import { LineBasicMaterial, Line } from 'three';
import { TransformControls } from '@react-three/drei'; import { TransformControls } from '@react-three/drei';
import { getWallPointsFromBlueprint } from './functions/getWallPointsFromBlueprint'; import { getWallPointsFromBlueprint } from './functions/getWallPointsFromBlueprint';
@@ -7,6 +7,8 @@ import * as Types from '../../../types/world/worldTypes';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { getUserData } from '../../../functions/getUserData'; import { getUserData } from '../../../functions/getUserData';
import { useVersionContext } from '../version/versionContext'; import { useVersionContext } from '../version/versionContext';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useSceneContext } from '../../scene/sceneContext';
/** /**
* DxfFile component handles the rendering and manipulation of DXf file data in a 3D scene. * DxfFile component handles the rendering and manipulation of DXf file data in a 3D scene.
@@ -15,48 +17,44 @@ import { useVersionContext } from '../version/versionContext';
*/ */
const DxfFile = () => { const DxfFile = () => {
// State management hooks // State management hooks
const { dfxuploaded, dfxWallGenerate, setObjValue, objValue } = useDfxUpload(); const { dfxuploaded, dfxWallGenerate, setObjValue, objValue, setDfxUploaded } = useDfxUpload();
const { toggleView } = useToggleView(); const { toggleView } = useToggleView();
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const { selectedVersionStore } = useVersionContext(); const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore(); const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams(); const { projectId } = useParams();
const { userId, organization } = getUserData(); const { userId, organization } = getUserData();
const { activeLayer } = useActiveLayer();
const { wallThickness, wallHeight, insideMaterial, outsideMaterial, } = useBuilderStore();
// Refs for storing line objects // Refs for storing line objects
const lineRefs = useRef<Line[]>([]); const lineRefs = useRef<Line[]>([]);
const { wallStore } = useSceneContext();
const { addWall, } = wallStore();
const { walls } = wallStore();
/** /**
* Effect hook that runs when DXF wall generation is triggered. * Effect hook that runs when DXF wall generation is triggered.
* Loads initial points and lines from the DXF data and updates the scene. * Loads initial points and lines from the DXF data and updates the scene.
*/ */
useEffect(() => { useEffect(() => {
if (dfxWallGenerate) { if (dfxWallGenerate) {
// Store generated lines in ref dfxWallGenerate.map((wall: Wall) => {
// lines.current.push(...dfxWallGenerate); const data = {
// dfxWallGenerate.map((line: any) => { wallData: wall,
// const lineData = arrayLineToObject(line as Types.Line); projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
addWall(wall);
socket.emit('v1:model-Wall:add', data);
// API
// //REST // if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall);
// // setLine(organization, lineData.layer!, lineData.line!, lineData.type!); // }
})
// //SOCKET
// const input = {
// organization,
// layer: lineData.layer,
// line: lineData.line,
// type: lineData.type,
// socketId: socket.id,
// versionId: selectedVersion?.versionId || '',
// projectId,
// userId
// }
// socket.emit('v1:Line:create', input);
// })
} }
}, [dfxWallGenerate]); }, [dfxWallGenerate]);
@@ -78,10 +76,15 @@ const DxfFile = () => {
// Recalculate wall points based on new position // Recalculate wall points based on new position
getWallPointsFromBlueprint({ getWallPointsFromBlueprint({
objValue: { x: position.x, y: position.y, z: position.z }, objValue: { x: position.x, y: position.y, z: position.z },
setDfxGenerate: () => { }, setDxfWallGenerate: () => { },
wallThickness, wallHeight, outsideMaterial, insideMaterial, activeLayer, addWall, walls
}); });
}; };
useEffect(() => {
if (!toggleView) {
setDfxUploaded([])
}
}, [toggleView])
return ( return (
<> <>
{/* Render DXF lines with transform controls when DXF data is available and view is toggled */} {/* Render DXF lines with transform controls when DXF data is available and view is toggled */}

View File

@@ -1,11 +1,21 @@
import { MathUtils, Vector3, BufferGeometry } from "three"; import { MathUtils, Vector3, BufferGeometry } from "three";
import { useActiveLayer } from "../../../../store/builder/store";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { wait } from "@testing-library/user-event/dist/utils";
type DXFData = any; // Replace with actual DXF data type type DXFData = any; // Replace with actual DXF data type
type DXFEntity = any; // Replace with actual DXF entity type type DXFEntity = any; // Replace with actual DXF entity type
type WallLineVertex = [Vector3, string, number, string]; // Represents a wall segment with start point, ID, weight, and type type WallLineVertex = [Vector3, string, number, string]; // Represents a wall segment with start point, ID, weight, and type
interface Props { interface Props {
parsedData?: DXFData; // Parsed DXF file data parsedData?: DXFData; // Parsed DXF file data
setDfxGenerate?: (walls: WallLineVertex[][]) => void; // Callback to set generated walls setDxfWallGenerate?: any; // Callback to set generated walls
objValue: any; // Object position values for offset calculation objValue: any; // Object position values for offset calculation
wallThickness: number;
wallHeight: number;
outsideMaterial: string;
insideMaterial: string;
activeLayer: number; // Active layer for wall points
addWall: (wall: Wall) => void; // Function to add a wall to the scene
walls: Wall[]; // Array of walls to be processed
} }
/** /**
@@ -15,20 +25,36 @@ interface Props {
* *
* @param {Props} params - Configuration parameters * @param {Props} params - Configuration parameters
* @param {DXFData} params.parsedData - Parsed DXF file data * @param {DXFData} params.parsedData - Parsed DXF file data
* @param {Function} params.setDfxGenerate - Callback to store generated walls * @param {Function} params.setDxfWallGenerate - Callback to store generated walls
* @param {Object} params.objValue - Contains x,y,z offsets for position adjustment * @param {Object} params.objValue - Contains x,y,z offsets for position adjustment
*/ */
export function getWallPointsFromBlueprint({ export function getWallPointsFromBlueprint({
parsedData, parsedData,
setDfxGenerate, setDxfWallGenerate,
objValue, objValue,
wallThickness,
wallHeight,
outsideMaterial,
insideMaterial,
activeLayer,
addWall,
walls,
}: Props) { }: Props) {
// Early return if no data is provided // Early return if no data is provided
if (!parsedData) return; if (!parsedData) return;
if (!parsedData.entities) return; if (!parsedData.entities) return;
const unit = 1000; // Conversion factor from millimeters to meters const unit = 1000; // Conversion factor from millimeters to meters
const wallVertex: WallLineVertex[][] = []; // Stores all generated wall segments const wallVertex: any[] = []; // Array to store wall vertices
const findExistingPoint = (vec: Vector3): Point | undefined => {
for (const wall of wallVertex) {
for (const pt of wall.points) {
const pos = new Vector3(...pt.position);
if (pos.equals(vec)) return pt;
}
}
return undefined;
};
// Process each entity in the DXF file // Process each entity in the DXF file
parsedData.entities.forEach((entity: DXFEntity) => { parsedData.entities.forEach((entity: DXFEntity) => {
@@ -47,31 +73,41 @@ export function getWallPointsFromBlueprint({
-entity.vertices[1].y / unit -entity.vertices[1].y / unit
).add(new Vector3(objValue.x, 0, objValue.z)); ).add(new Vector3(objValue.x, 0, objValue.z));
// Check if points already exist to avoid duplicates // Create start and end points
const existingStart = wallVertex const existingStart = findExistingPoint(startVec);
.flat() const startPoint: Point = existingStart || {
.find((v) => v[0].equals(startVec)); pointUuid: MathUtils.generateUUID(),
const startPoint: WallLineVertex = existingStart || [ pointType: "Wall",
startVec, position: [startVec.x, startVec.y, startVec.z],
MathUtils.generateUUID(), // Generate unique ID for new points layer: activeLayer,
1, // Default weight };
"WallLine", // Type identifier
];
const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); const existingEnd = findExistingPoint(endVec);
const endPoint: WallLineVertex = existingEnd || [ const endPoint: Point = existingEnd || {
endVec, pointUuid: MathUtils.generateUUID(),
MathUtils.generateUUID(), pointType: "Wall",
1, position: [endVec.x, endVec.y, endVec.z],
"WallLine", layer: activeLayer,
]; };
// Add the line segment to our collection // Create the wall
wallVertex.push([startPoint, endPoint]); const wallSet: Wall = {
wallUuid: MathUtils.generateUUID(),
points: [startPoint, endPoint], // Store start and end points
outsideMaterial: insideMaterial,
insideMaterial: outsideMaterial,
wallThickness: wallThickness,
wallHeight: wallHeight,
decals: [],
};
wallVertex.push(wallSet); // Store wall segment in array
// Add the wall to the scene
// addWall(wallSet);
} }
// Handle LWPOLYLINE entities (connected line segments) // Handle LWPOLYLINE entities (connected line segments)
else if (entity.type === "LWPOLYLINE" && entity.vertices) { else if (entity.type === "LWPOLYLINE" && entity.vertices) {
let firstPoint: WallLineVertex | undefined; // Store first point for closing the polyline let firstPoint: Point | undefined; // Store first point for closing the polyline
// Process each vertex pair in the polyline // Process each vertex pair in the polyline
for (let i = 0; i < entity.vertices.length - 1; i++) { for (let i = 0; i < entity.vertices.length - 1; i++) {
@@ -88,37 +124,60 @@ export function getWallPointsFromBlueprint({
-entity.vertices[i + 1].y / unit -entity.vertices[i + 1].y / unit
).add(new Vector3(objValue.x, 0, objValue.z)); ).add(new Vector3(objValue.x, 0, objValue.z));
// Check for existing points // Create start and end points
const existingStart = wallVertex const existingStart = findExistingPoint(startVec);
.flat() const startPoint: Point = existingStart || {
.find((v) => v[0].equals(startVec)); pointUuid: MathUtils.generateUUID(), // Generate unique ID for new points
const startPoint: WallLineVertex = existingStart || [ pointType: "Wall", // Type identifier
startVec, position: [startVec.x, startVec.y, startVec.z], // Position in 3D space
MathUtils.generateUUID(), layer: activeLayer,
1, };
"WallLine",
];
const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); const existingEnd = findExistingPoint(endVec);
const endPoint: WallLineVertex = existingEnd || [ const endPoint: Point = existingEnd || {
endVec, pointUuid: MathUtils.generateUUID(), // Generate unique ID for new points
MathUtils.generateUUID(), pointType: "Wall", // Type identifier
1, position: [endVec.x, endVec.y, endVec.z], // Position in 3D space
"WallLine", layer: activeLayer,
]; };
wallVertex.push([startPoint, endPoint]); // Create the wall segment
const wallSet: Wall = {
wallUuid: MathUtils.generateUUID(),
points: [startPoint, endPoint], // Store start and end points
outsideMaterial: insideMaterial,
insideMaterial: outsideMaterial,
wallThickness: wallThickness,
wallHeight: wallHeight,
decals: [],
};
// Add the wall segment
// addWall(wallSet);
wallVertex.push(wallSet);
// Store first point and create closing segment if this is the last vertex // Store first point and create closing segment if this is the last vertex
if (i === 0) firstPoint = startPoint; if (i === 0) firstPoint = startPoint;
if (i === entity.vertices.length - 2 && firstPoint) { if (i === entity.vertices.length - 2 && firstPoint) {
wallVertex.push([endPoint, firstPoint]); const closingWallSet: Wall = {
wallUuid: MathUtils.generateUUID(),
points: [endPoint, firstPoint], // Create closing segment
outsideMaterial: insideMaterial,
insideMaterial: outsideMaterial,
wallThickness: wallThickness,
wallHeight: wallHeight,
decals: [],
};
// Add the closing wall
wallVertex.push(closingWallSet);
// addWall(closingWallSet);
} }
} }
} }
// Handle ARC entities // Handle ARC entities
else if (entity.type === "ARC") { else if (entity.type === "ARC") {
const { center, radius, startAngle, endAngle } = entity; const { center, radius, startAngle, endAngle } = entity;
// Validate required ARC properties // Validate required ARC properties
if ( if (
!center || !center ||
@@ -154,34 +213,46 @@ export function getWallPointsFromBlueprint({
const startVec = arcPoints[i]; const startVec = arcPoints[i];
const endVec = arcPoints[i + 1]; const endVec = arcPoints[i + 1];
// Check for existing points // Create start and end points
const existingStart = wallVertex const existingStart = findExistingPoint(startVec);
.flat() const startPoint: Point = existingStart || {
.find((v) => v[0].equals(startVec)); pointUuid: MathUtils.generateUUID(),
const startPoint: WallLineVertex = existingStart || [ pointType: "Wall",
startVec, position: [startVec.x, startVec.y, startVec.z],
MathUtils.generateUUID(), layer: activeLayer,
1, };
"WallLine",
];
const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); const existingEnd = findExistingPoint(endVec);
const endPoint: WallLineVertex = existingEnd || [ const endPoint: Point = existingEnd || {
endVec, pointUuid: MathUtils.generateUUID(),
MathUtils.generateUUID(), pointType: "Wall",
1, position: [endVec.x, endVec.y, endVec.z],
"WallLine", layer: activeLayer,
]; };
wallVertex.push([startPoint, endPoint]); // Create the wall segment
const wallSet: Wall = {
wallUuid: MathUtils.generateUUID(),
points: [startPoint, endPoint],
outsideMaterial: insideMaterial,
insideMaterial: outsideMaterial,
wallThickness: wallThickness,
wallHeight: wallHeight,
decals: [],
};
// Add the wall segment
// addWall(wallSet);
wallVertex.push(wallSet);
} }
} }
// Log unsupported entity types // Log unsupported entity types
else { else {
console.error("Unsupported entity type:", entity.type); console.error("Unsupported entity type:", entity.type);
} }
}); });
console.log("wallVertex: ", wallVertex);
// Return the generated walls through callback if provided // Return the generated walls through callback if provided
setDfxGenerate && setDfxGenerate(wallVertex); setDxfWallGenerate && setDxfWallGenerate(wallVertex);
} }

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useActiveTool, useToggleView } from '../../../store/builder/store' import { useToggleView } from '../../../store/builder/store'
import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useVersionContext } from '../version/versionContext'; import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext'; import { useSceneContext } from '../../scene/sceneContext';
@@ -13,7 +13,6 @@ function FloorGroup() {
const { togglView } = useToggleView(); const { togglView } = useToggleView();
const { setSelectedFloor, setSelectedDecal } = useBuilderStore(); const { setSelectedFloor, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
const { selectedVersionStore } = useVersionContext(); const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore(); const { selectedVersion } = selectedVersionStore();
const { floorStore } = useSceneContext(); const { floorStore } = useSceneContext();
@@ -25,7 +24,7 @@ function FloorGroup() {
setSelectedFloor(null); setSelectedFloor(null);
setSelectedDecal(null); setSelectedDecal(null);
} }
}, [togglView, activeModule, activeTool]) }, [togglView, activeModule])
useEffect(() => { useEffect(() => {
if (projectId && selectedVersion) { if (projectId && selectedVersion) {

View File

@@ -1,12 +0,0 @@
import { Vector3 } from "three";
export default function closestPointOnLineSegment(p: Vector3, a: Vector3, b: Vector3) {
const ab = new Vector3().subVectors(b, a);
const ap = new Vector3().subVectors(p, a);
const abLengthSq = ab.lengthSq();
const dot = ap.dot(ab);
const t = Math.max(0, Math.min(1, dot / abLengthSq));
return new Vector3().copy(a).add(ab.multiplyScalar(t));
}

View File

@@ -154,7 +154,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
<MeshDiscardMaterial /> <MeshDiscardMaterial />
{wall.decals.map((decal) => ( {wall.decals.map((decal) => (
<DecalInstance zPosition={wall.wallThickness / 2 + 0.001} visible={visible} key={decal.decalUuid} decal={decal} /> <DecalInstance visible={visible} key={decal.decalUuid} decal={decal} />
))} ))}
</mesh> </mesh>
</mesh> </mesh>

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three'; import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three';
import { Geometry } from '@react-three/csg';
import { Html, Extrude } from '@react-three/drei'; import { Html, Extrude } from '@react-three/drei';
import { useLoader } from '@react-three/fiber'; import { useLoader } from '@react-three/fiber';
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from '../../../scene/sceneContext';
@@ -43,9 +44,13 @@ function WallInstances() {
<> <>
{!toggleView && walls.length > 1 && ( {!toggleView && walls.length > 1 && (
<> <>
{walls.map((wall) => ( <mesh name='Walls-Group'>
<WallInstance key={wall.wallUuid} wall={wall} /> <Geometry useGroups>
))} {walls.map((wall) => (
<WallInstance key={wall.wallUuid} wall={wall} />
))}
</Geometry>
</mesh>
<group name='Wall-Floors-Group'> <group name='Wall-Floors-Group'>
{rooms.map((room, index) => ( {rooms.map((room, index) => (

View File

@@ -71,6 +71,7 @@ function WallCreator() {
if (wallIntersect && !pointIntersects) { if (wallIntersect && !pointIntersects) {
const wall = getWallByPoints(wallIntersect.object.userData.points); const wall = getWallByPoints(wallIntersect.object.userData.points);
console.log('wall: ', wall);
if (wall) { if (wall) {
const ThroughPoint = wallIntersect.object.userData.path.getPoints(Constants.lineConfig.lineIntersectionPoints); const ThroughPoint = wallIntersect.object.userData.path.getPoints(Constants.lineConfig.lineIntersectionPoints);
let intersectionPoint = getClosestIntersection(ThroughPoint, wallIntersect.point); let intersectionPoint = getClosestIntersection(ThroughPoint, wallIntersect.point);
@@ -294,7 +295,9 @@ function WallCreator() {
position: [position.x, position.y, position.z], position: [position.x, position.y, position.z],
layer: activeLayer layer: activeLayer
}; };
console.log('newPoint: ', newPoint);
console.log('snappedPoint: ', snappedPoint);
if (snappedPosition && snappedPoint) { if (snappedPosition && snappedPoint) {
newPoint.pointUuid = snappedPoint.pointUuid; newPoint.pointUuid = snappedPoint.pointUuid;
newPoint.position = snappedPosition; newPoint.position = snappedPosition;
@@ -316,6 +319,7 @@ function WallCreator() {
} }
} }
console.log('tempPoints: ', tempPoints);
if (tempPoints.length === 0) { if (tempPoints.length === 0) {
setTempPoints([newPoint]); setTempPoints([newPoint]);
setIsCreating(true); setIsCreating(true);
@@ -330,6 +334,7 @@ function WallCreator() {
decals: [] decals: []
}; };
addWall(wall); addWall(wall);
console.log('wall: ', wall);
// API // API

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useActiveTool, useToggleView } from '../../../store/builder/store'; import { useToggleView } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useVersionContext } from '../version/versionContext'; import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext'; import { useSceneContext } from '../../scene/sceneContext';
@@ -14,7 +14,6 @@ function WallGroup() {
const { togglView } = useToggleView(); const { togglView } = useToggleView();
const { setSelectedWall, setSelectedDecal } = useBuilderStore(); const { setSelectedWall, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
const { selectedVersionStore } = useVersionContext(); const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore(); const { selectedVersion } = selectedVersionStore();
const { wallStore } = useSceneContext(); const { wallStore } = useSceneContext();
@@ -26,7 +25,7 @@ function WallGroup() {
setSelectedWall(null); setSelectedWall(null);
setSelectedDecal(null); setSelectedDecal(null);
} }
}, [togglView, activeModule, activeTool]) }, [togglView, activeModule])
useEffect(() => { useEffect(() => {
if (projectId && selectedVersion) { if (projectId && selectedVersion) {

View File

@@ -1,313 +0,0 @@
import * as THREE from 'three';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useThree } from '@react-three/fiber';
import { Base, Geometry, Subtraction } from '@react-three/csg';
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import useModuleStore from '../../../../../store/useModuleStore';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import { useActiveTool, useSocketStore, useToggleView } from '../../../../../store/builder/store';
import { useParams } from 'react-router-dom';
import { useVersionContext } from '../../../version/versionContext';
import { getUserData } from '../../../../../functions/getUserData';
import closestPointOnLineSegment from '../../../line/helpers/getClosestPointOnLineSegment';
import { upsertWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
import { deleteWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const { socket } = useSocketStore();
const { raycaster, pointer, camera, scene, controls, gl } = useThree();
const { wallStore, wallAssetStore } = useSceneContext();
const { walls, getWallById } = wallStore();
const { updateWallAsset, removeWallAsset } = wallAssetStore();
const { toggleView } = useToggleView();
const { activeTool } = useActiveTool();
const { activeModule } = useModuleStore();
const { selectedWallAsset, setSelectedWallAsset, setDeletableWallAsset, deletableWallAsset } = useBuilderStore();
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
const groupRef = useRef<THREE.Group>(null);
const wall = useMemo(() => getWallById(wallAsset.wallUuid), [getWallById, wallAsset.wallUuid, walls]);
const draggingRef = useRef(false);
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
useEffect(() => {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
const loadModel = async () => {
try {
// Check Cache
const assetId = wallAsset.assetId;
const cachedModel = THREE.Cache.get(assetId);
if (cachedModel) {
setGltfScene(cachedModel.scene.clone());
calculateBoundingBox(cachedModel.scene);
return;
}
// Check IndexedDB
const indexedDBModel = await retrieveGLTF(assetId);
if (indexedDBModel) {
const blobUrl = URL.createObjectURL(indexedDBModel);
loader.load(blobUrl, (gltf) => {
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
},
undefined,
(error) => {
echo.error(`[IndexedDB] Error loading ${wallAsset.modelName}:`);
URL.revokeObjectURL(blobUrl);
}
);
return;
}
// Fetch from Backend
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
const handleBackendLoad = async (gltf: GLTF) => {
try {
const response = await fetch(modelUrl);
const modelBlob = await response.blob();
await storeGLTF(assetId, modelBlob);
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
} catch (error) {
console.error(`[Backend] Error storing/loading ${wallAsset.modelName}:`, error);
}
};
loader.load(
modelUrl,
handleBackendLoad,
undefined,
(error) => {
echo.error(`[Backend] Error loading ${wallAsset.modelName}:`);
}
);
} catch (err) {
console.error("Failed to load model:", wallAsset.assetId, err);
}
};
const calculateBoundingBox = (scene: THREE.Object3D) => {
const box = new THREE.Box3().setFromObject(scene);
setBoundingBox(box);
};
loadModel();
}, []);
useEffect(() => {
const canvasElement = gl.domElement;
const onPointerUp = () => {
draggingRef.current = false;
if (controls) {
(controls as any).enabled = true;
}
};
const onPointerMove = (e: any) => {
if (!draggingRef.current || !wall || !selectedWallAsset) return;
if (controls) {
(controls as any).enabled = false;
}
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) {
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 === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
});
if (projectId && updatedWallAsset) {
// API
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
// SOCKET
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:wall-asset:add', data);
}
}
};
if (selectedWallAsset && !toggleView && activeModule === 'builder') {
canvasElement.addEventListener('mousemove', onPointerMove);
canvasElement.addEventListener('pointerup', onPointerUp);
}
return () => {
canvasElement.removeEventListener('mousemove', onPointerMove);
canvasElement.removeEventListener('pointerup', onPointerUp);
};
}, [gl, camera, toggleView, activeModule, selectedWallAsset, socket])
const handlePointerClick = useCallback((wallAsset: WallAsset) => {
if (activeTool === 'delete' && deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) {
const removedWallAsset = removeWallAsset(wallAsset.modelUuid);
if (projectId && removedWallAsset) {
// API
// deleteWallAssetApi(projectId, selectedVersion?.versionId || '', removedWallAsset.modelUuid, removedWallAsset.wallUuid);
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: removedWallAsset.modelUuid,
wallUuid: removedWallAsset.wallUuid
}
socket.emit('v1:wall-asset:delete', data);
}
}
}, [activeTool, activeModule, deletableWallAsset, socket])
const handlePointerOver = useCallback((wallAsset: WallAsset, currentObject: THREE.Object3D) => {
if (activeTool === "delete" && activeModule === 'builder') {
if (deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) {
return;
} else {
setDeletableWallAsset(currentObject);
}
}
}, [activeTool, activeModule, deletableWallAsset]);
const handlePointerOut = useCallback((wallAsset: WallAsset) => {
if (activeTool === "delete" && deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) {
setDeletableWallAsset(null);
}
}, [activeTool, deletableWallAsset]);
if (!gltfScene || !boundingBox || !wall) { return null }
const size = new THREE.Vector3();
boundingBox.getSize(size);
const center = new THREE.Vector3();
boundingBox.getCenter(center);
return (
<>
<group
key={wallAsset.modelUuid}
name={wallAsset.modelName}
ref={groupRef}
position={wallAsset.position}
rotation={wallAsset.rotation}
visible={wallAsset.isVisible}
userData={wallAsset}
>
<Subtraction position={[center.x, center.y, center.z]} scale={[size.x, size.y, wall.wallThickness + 0.05]}>
<Geometry>
<Base geometry={new THREE.BoxGeometry()} />
</Geometry>
</Subtraction>
{gltfScene && (
<mesh
onPointerDown={(e) => {
if (!toggleView && activeModule === 'builder' && selectedWallAsset && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
draggingRef.current = true;
e.stopPropagation();
setSelectedWallAsset(groupRef.current);
if (controls) {
(controls as any).enabled = false;
}
}
}}
onClick={(e) => {
if (!toggleView && activeModule === 'builder' && activeTool !== 'delete') {
if (e.object) {
e.stopPropagation();
let currentObject = e.object as THREE.Object3D;
while (currentObject) {
if (currentObject.name === "Scene") {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
}
setSelectedWallAsset(currentObject);
}
} else if (!toggleView && activeModule === 'builder' && activeTool === 'delete') {
handlePointerClick(wallAsset);
}
}}
onPointerMissed={() => {
if (selectedWallAsset && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
setSelectedWallAsset(null);
}
}}
onPointerEnter={(e) => {
if (!toggleView) {
e.stopPropagation();
let currentObject = e.object as THREE.Object3D;
while (currentObject) {
if (currentObject.name === "Scene") {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
}
handlePointerOver(wallAsset, currentObject);
}
}}
onPointerOut={(e) => {
if (!toggleView) {
e.stopPropagation();
handlePointerOut(wallAsset);
}
}}
userData={wallAsset}
>
<primitive userData={wallAsset} object={gltfScene} />
</mesh>
)}
</group>
</>
)
}
export default WallAssetInstance

View File

@@ -1,30 +0,0 @@
import { useEffect } from 'react';
import { useSceneContext } from '../../../scene/sceneContext'
import { useToggleView } from '../../../../store/builder/store';
import WallAssetInstance from './Instances/wallAssetInstance';
function WallAssetInstances() {
const { wallAssetStore } = useSceneContext();
const { wallAssets } = wallAssetStore();
const { toggleView } = useToggleView();
useEffect(() => {
// console.log('wallAssets: ', wallAssets);
}, [wallAssets])
return (
<>
{!toggleView && wallAssets.length > 0 && (
<>
{wallAssets.map((wallAsset) => (
<WallAssetInstance key={wallAsset.modelUuid} wallAsset={wallAsset} />
))}
</>
)}
</>
)
}
export default WallAssetInstances

View File

@@ -1,103 +0,0 @@
import { useThree } from '@react-three/fiber';
import { useEffect } from 'react'
import useModuleStore from '../../../store/useModuleStore';
import { useSelectedItem, useSocketStore, useToggleView } from '../../../store/builder/store';
import { MathUtils, Vector3 } from 'three';
import { useSceneContext } from '../../scene/sceneContext';
import { useParams } from 'react-router-dom';
import { useVersionContext } from '../version/versionContext';
import { getUserData } from '../../../functions/getUserData';
import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegment';
import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
function WallAssetCreator() {
const { socket } = useSocketStore();
const { pointer, camera, raycaster, scene, gl } = useThree();
const { togglView } = useToggleView();
const { activeModule } = useModuleStore();
const { wallAssetStore } = useSceneContext();
const { addWallAsset } = wallAssetStore();
const { selectedItem, setSelectedItem } = useSelectedItem();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
useEffect(() => {
const canvasElement = gl.domElement;
const onDrop = (event: DragEvent) => {
if (!event.dataTransfer?.files[0]) return;
if (selectedItem.id !== "" && event.dataTransfer?.files[0] && selectedItem.category === 'Fenestration') {
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 intersect = intersects.find((intersect) => intersect.object.name.includes('WallReference'));
if (intersect) {
const wall = intersect.object.userData as Wall;
const closestPoint = closestPointOnLineSegment(
new Vector3(intersect.point.x, 0, intersect.point.z),
new Vector3(...wall.points[0].position),
new Vector3(...wall.points[1].position)
)
const wallRotation = intersect.object.rotation.clone();
const newWallAsset: WallAsset = {
modelName: selectedItem.name,
modelUuid: MathUtils.generateUUID(),
wallUuid: wall.wallUuid,
wallAssetType: selectedItem.subCategory,
assetId: selectedItem.id,
position: [closestPoint.x, selectedItem.subCategory === "fixed-move" ? 0 : intersect.point.y, closestPoint.z],
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
isLocked: false,
isVisible: true,
opacity: 1,
};
addWallAsset(newWallAsset);
if (projectId) {
// API
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', newWallAsset);
// SOCKET
const data = {
wallAssetData: newWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:wall-asset:add', data);
}
}
}
};
if (!togglView && activeModule === 'builder') {
canvasElement.addEventListener('drop', onDrop);
}
return () => {
canvasElement.removeEventListener('drop', onDrop);
};
}, [gl, camera, togglView, activeModule, socket, selectedItem, setSelectedItem]);
return (
<>
</>
)
}
export default WallAssetCreator

View File

@@ -1,56 +0,0 @@
import { useEffect } from 'react';
import { useActiveTool, useToggleView } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';
import { useParams } from 'react-router-dom';
import useModuleStore from '../../../store/useModuleStore';
import WallAssetCreator from './wallAssetCreator'
import WallAssetInstances from './Instances/wallAssetInstances'
import { getWallAssetsApi } from '../../../services/factoryBuilder/asset/wallAsset/getWallAssetsApi';
function WallAssetGroup() {
const { togglView } = useToggleView();
const { setSelectedFloorAsset, setDeletableWallAsset } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { wallAssetStore } = useSceneContext();
const { setWallAssets } = wallAssetStore();
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
setSelectedFloorAsset(null);
}
setDeletableWallAsset(null);
}, [togglView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {
getWallAssetsApi(projectId, selectedVersion?.versionId || '').then((wallAssets) => {
console.log('wallAssets: ', wallAssets);
if (wallAssets && wallAssets.length > 0) {
setWallAssets(wallAssets);
} else {
setWallAssets([]);
}
}).catch((err) => {
console.log(err);
})
}
}, [projectId, selectedVersion?.versionId])
return (
<>
<WallAssetCreator />
<WallAssetInstances />
</>
)
}
export default WallAssetGroup

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useActiveTool, useToggleView } from '../../../store/builder/store'; import { useToggleView } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useVersionContext } from '../version/versionContext'; import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext'; import { useSceneContext } from '../../scene/sceneContext';
@@ -14,7 +14,6 @@ function ZoneGroup() {
const { togglView } = useToggleView(); const { togglView } = useToggleView();
const { setSelectedZone } = useBuilderStore(); const { setSelectedZone } = useBuilderStore();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
const { selectedVersionStore } = useVersionContext(); const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore(); const { selectedVersion } = selectedVersionStore();
const { zoneStore } = useSceneContext(); const { zoneStore } = useSceneContext();
@@ -25,11 +24,12 @@ function ZoneGroup() {
if (togglView || activeModule !== 'builder') { if (togglView || activeModule !== 'builder') {
setSelectedZone(null); setSelectedZone(null);
} }
}, [togglView, activeModule, activeTool]) }, [togglView, activeModule])
useEffect(() => { useEffect(() => {
if (projectId && selectedVersion) { if (projectId && selectedVersion) {
getZonesApi(projectId, selectedVersion?.versionId || '').then((zones) => { getZonesApi(projectId, selectedVersion?.versionId || '').then((zones) => {
console.log('zones: ', zones);
if (zones && zones.length > 0) { if (zones && zones.length > 0) {
setZones(zones); setZones(zones);
} else { } else {

View File

@@ -1,17 +1,6 @@
import { useEffect } from 'react'; import React from 'react';
import { useSocketStore } from '../../../store/builder/store';
export default function SocketResponses() { export default function SocketResponses() {
const { socket } = useSocketStore();
useEffect(() => {
socket.on("v1:wall-asset:response:delete", (data: any) => {
});
return () => {
socket.off("v1:wall-asset:response:delete");
}
}, [socket])
return ( return (
<> <>

View File

@@ -225,7 +225,7 @@ const SelectionControls: React.FC = () => {
selectedObjects.forEach((object) => { selectedObjects.forEach((object) => {
let currentObject: THREE.Object3D | null = object; let currentObject: THREE.Object3D | null = object;
while (currentObject) { while (currentObject) {
if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType) { if (currentObject.userData.modelUuid) {
Objects.add(currentObject); Objects.add(currentObject);
break; break;
} }

View File

@@ -1,13 +1,14 @@
import { useTileDistance, useToggleView } from "../../../store/builder/store"; import { useTileDistance, useToggleView } from "../../../store/builder/store";
import * as CONSTANTS from "../../../types/world/worldConstants"; import * as CONSTANTS from "../../../types/world/worldConstants";
const Ground = ({ plane }: any) => { const Ground = ({ grid, plane }: any) => {
const { toggleView } = useToggleView(); const { toggleView } = useToggleView();
const { planeValue, gridValue } = useTileDistance(); const { planeValue, gridValue } = useTileDistance();
return ( return (
<mesh name="Ground"> <mesh name="Ground">
<mesh <mesh
ref={grid}
name="Grid" name="Grid"
position={!toggleView ? CONSTANTS.gridConfig.position3D : CONSTANTS.gridConfig.position2D} position={!toggleView ? CONSTANTS.gridConfig.position3D : CONSTANTS.gridConfig.position2D}
> >

View File

@@ -15,7 +15,7 @@ export default function PostProcessing() {
const { selectedWallItem } = useSelectedWallItem(); const { selectedWallItem } = useSelectedWallItem();
const { selectedFloorItem } = useSelectedFloorItem(); const { selectedFloorItem } = useSelectedFloorItem();
const { selectedEventSphere } = useSelectedEventSphere(); const { selectedEventSphere } = useSelectedEventSphere();
const { selectedAisle, selectedWall, selectedDecal, selectedFloor, selectedWallAsset, deletableWallAsset } = useBuilderStore(); const { selectedAisle, selectedWall, selectedDecal, selectedFloor } = useBuilderStore();
function flattenChildren(children: any[]) { function flattenChildren(children: any[]) {
const allChildren: any[] = []; const allChildren: any[] = [];
@@ -48,14 +48,6 @@ export default function PostProcessing() {
// console.log('selectedFloor: ', selectedFloor); // console.log('selectedFloor: ', selectedFloor);
}, [selectedFloor]) }, [selectedFloor])
useEffect(() => {
// console.log('selectedWallAsset: ', selectedWallAsset);
}, [selectedWallAsset])
useEffect(() => {
// console.log('deletableWallAsset: ', deletableWallAsset);
}, [deletableWallAsset])
return ( return (
<EffectComposer autoClear={false}> <EffectComposer autoClear={false}>
<N8AO <N8AO
@@ -67,36 +59,6 @@ export default function PostProcessing() {
denoiseRadius={6} denoiseRadius={6}
denoiseSamples={16} denoiseSamples={16}
/> />
{selectedWallAsset && (
<Outline
selection={flattenChildren(selectedWallAsset.children)}
selectionLayer={10}
width={2000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={5}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
blur={true}
xRay={true}
/>
)}
{deletableWallAsset && (
<Outline
selection={flattenChildren(deletableWallAsset.children)}
selectionLayer={10}
width={2000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={5}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor}
hiddenEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor}
blur={true}
xRay={true}
/>
)}
{selectedAisle && ( {selectedAisle && (
<Outline <Outline
selection={flattenChildren(selectedAisle.children)} selection={flattenChildren(selectedAisle.children)}

View File

@@ -40,10 +40,10 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
if (!canvas) return; if (!canvas) return;
const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png"); const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png");
const updateProjects = { const updateProjects = {
projectId: project?.projectUuid, projectId: project.projectUuid,
organization, organization,
userId, userId,
projectName: project?.projectName, projectName: project.projectName,
thumbnail: screenshotDataUrl, thumbnail: screenshotDataUrl,
}; };
if (projectSocket) { if (projectSocket) {

View File

@@ -12,13 +12,12 @@ interface PolygonGeneratorProps {
export default function PolygonGenerator({ export default function PolygonGenerator({
groupRef, groupRef,
}: PolygonGeneratorProps) { }: PolygonGeneratorProps) {
const { aisleStore } = useSceneContext(); const { aisleStore, wallStore } = useSceneContext();
const { aisles } = aisleStore(); const { aisles } = aisleStore();
const { scene } = useThree(); const { scene } = useThree();
const { walls } = wallStore();
useEffect(() => { useEffect(() => {
// let allLines = arrayLinesToObject(lines.current);
// const wallLines = allLines?.filter((line) => line?.type === "WallLine");
const result = aisles const result = aisles
.filter( .filter(
(aisle) => (aisle) =>
@@ -61,9 +60,12 @@ export default function PolygonGenerator({
}); });
}); });
// const wallPoints = wallLines
// .map((pair) => pair?.line.map((vals) => vals.position)) const wallPoints: THREE.Vector3[][] = walls
// .filter((wall): wall is THREE.Vector3[] => !!wall); .map((wall) =>
wall.points.map((pt) => new THREE.Vector3(pt.position[0], pt.position[1], pt.position[2]))
)
.filter(points => points.length === 2);
if (!result || result.some((line) => !line)) { if (!result || result.some((line) => !line)) {
@@ -81,7 +83,7 @@ export default function PolygonGenerator({
const polygons = turf.polygonize(turf.featureCollection(validLineFeatures)); const polygons = turf.polygonize(turf.featureCollection(validLineFeatures));
// renderWallGeometry(wallPoints); renderWallGeometry(wallPoints);
if (polygons.features.length > 0) { if (polygons.features.length > 0) {
polygons.features.forEach((feature) => { polygons.features.forEach((feature) => {
@@ -116,7 +118,7 @@ export default function PolygonGenerator({
}); });
} }
}, [ aisles, scene]); }, [aisles, scene, walls]);
const renderWallGeometry = (walls: THREE.Vector3[][]) => { const renderWallGeometry = (walls: THREE.Vector3[][]) => {
walls.forEach((wall) => { walls.forEach((wall) => {

View File

@@ -29,7 +29,6 @@ import { SceneProvider } from "../modules/scene/sceneContext";
import { getVersionHistoryApi } from "../services/factoryBuilder/versionControl/getVersionHistoryApi"; import { getVersionHistoryApi } from "../services/factoryBuilder/versionControl/getVersionHistoryApi";
import { useVersionHistoryStore } from "../store/builder/useVersionHistoryStore"; import { useVersionHistoryStore } from "../store/builder/useVersionHistoryStore";
import { VersionProvider } from "../modules/builder/version/versionContext"; import { VersionProvider } from "../modules/builder/version/versionContext";
import { recentlyViewed } from "../services/dashboard/recentlyViewed";
import { sharedWithMeProjects } from "../services/dashboard/sharedWithMeProject"; import { sharedWithMeProjects } from "../services/dashboard/sharedWithMeProject";
const Project: React.FC = () => { const Project: React.FC = () => {
@@ -81,7 +80,6 @@ const Project: React.FC = () => {
fetchProjects(); fetchProjects();
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!projectId) return; if (!projectId) return;
getVersionHistoryApi(projectId).then((data) => { getVersionHistoryApi(projectId).then((data) => {

View File

@@ -1,40 +0,0 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
export const deleteWallAssetApi = async (
projectId: string,
versionId: string,
modelUuid: string,
wallUuid: string,
) => {
try {
const response = await fetch(`${url_Backend_dwinzo}/api/V1/delete/wallasset`, {
method: "PATCH",
headers: {
Authorization: "Bearer <access_token>",
"Content-Type": "application/json",
token: localStorage.getItem("token") || "",
refresh_token: localStorage.getItem("refreshToken") || "",
},
body: JSON.stringify({ projectId, versionId, modelUuid, wallUuid }),
});
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) {
console.error("Failed to delete wall asset:", response.statusText);
}
const result = await response.json();
return result;
} catch (error) {
echo.error("Failed to delete wall asset");
if (error instanceof Error) {
console.log(error.message);
} else {
console.log("An unknown error occurred");
}
}
};

View File

@@ -1,37 +0,0 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
export const getWallAssetsApi = async (
projectId: string,
versionId: string,
) => {
try {
const response = await fetch(`${url_Backend_dwinzo}/api/V1/wallassets/${projectId}/${versionId}`, {
method: "GET",
headers: {
Authorization: "Bearer <access_token>",
"Content-Type": "application/json",
token: localStorage.getItem("token") || "",
refresh_token: localStorage.getItem("refreshToken") || "",
},
});
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) {
console.error("Failed to get wall assets:", response.statusText);
}
const result = await response.json();
return result;
} catch (error) {
echo.error("Failed to get wall assets");
if (error instanceof Error) {
console.log(error.message);
} else {
console.log("An unknown error occurred");
}
}
};

View File

@@ -1,39 +0,0 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
export const upsertWallAssetApi = async (
projectId: string,
versionId: string,
wallAssetData: WallAsset
) => {
try {
const response = await fetch(`${url_Backend_dwinzo}/api/V1/wall-asset`, {
method: "POST",
headers: {
Authorization: "Bearer <access_token>",
"Content-Type": "application/json",
token: localStorage.getItem("token") || "",
refresh_token: localStorage.getItem("refreshToken") || "",
},
body: JSON.stringify({ projectId, versionId, wallAssetData }),
});
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) {
console.error("Failed to upsert wall asset:", response.statusText);
}
const result = await response.json();
return result;
} catch (error) {
echo.error("Failed to upsert wall asset");
if (error instanceof Error) {
console.log(error.message);
} else {
console.log("An unknown error occurred");
}
}
};

View File

@@ -14,6 +14,11 @@ export const getProjectSharedList = async (projectId: string) => {
}, },
} }
); );
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
//console.log("New token received:", newAccessToken);
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to get users"); throw new Error("Failed to get users");

View File

@@ -14,6 +14,11 @@ export const getSearchUsers = async (searchMail: string) => {
}, },
} }
); );
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
//console.log("New token received:", newAccessToken);
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to get users"); throw new Error("Failed to get users");

View File

@@ -9,7 +9,7 @@ export const shareAccess = async (
const body: any = { const body: any = {
projectId, projectId,
targetUserId, targetUserId,
newAccessPoint newAccessPoint,
}; };
try { try {
@@ -26,6 +26,11 @@ export const shareAccess = async (
body: JSON.stringify(body), body: JSON.stringify(body),
} }
); );
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
//console.log("New token received:", newAccessToken);
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) { if (!response.ok) {
console.error("Failed to clearPanel in the zone"); console.error("Failed to clearPanel in the zone");

View File

@@ -12,6 +12,12 @@ export const shareProject = async (addUserId: string, projectId: string) => {
}, },
body: JSON.stringify({ addUserId, projectId }), body: JSON.stringify({ addUserId, projectId }),
}); });
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
//console.log("New token received:", newAccessToken);
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) { if (!response.ok) {
console.error("Failed to add project"); console.error("Failed to add project");
} }

View File

@@ -6,7 +6,7 @@ export const upsertZoneApi = async (
zoneData: Zone zoneData: Zone
) => { ) => {
try { try {
const response = await fetch(`${url_Backend_dwinzo}/api/V1/upsertZone`, { const response = await fetch(`${url_Backend_dwinzo}/api/V1/UpsertZone`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: "Bearer <access_token>", Authorization: "Bearer <access_token>",

View File

@@ -587,7 +587,7 @@ export const useDfxUpload = create<any>((set: any) => ({
dfxWallGenerate: [], dfxWallGenerate: [],
objValue: { x: 0, y: 0, z: 0 }, objValue: { x: 0, y: 0, z: 0 },
setDfxUploaded: (x: any) => set({ dfxuploaded: x }), setDfxUploaded: (x: any) => set({ dfxuploaded: x }),
setDfxGenerate: (x: any) => set({ dfxWallGenerate: x }), setDxfWallGenerate: (x: any) => set({ dfxWallGenerate: x }),
setObjValue: (x: any) => set({ objValue: x }), setObjValue: (x: any) => set({ objValue: x }),
})); }));

View File

@@ -21,7 +21,7 @@ interface AssetsStore {
setOpacity: (modelUuid: string, opacity: number) => void; setOpacity: (modelUuid: string, opacity: number) => void;
// Animation controls // Animation controls
setAnimations: (modelUuid: string, animations: string[]) => void; setAnimation: (modelUuid: string, animation: string) => void;
setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void; setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void;
addAnimation: (modelUuid: string, animation: string) => void; addAnimation: (modelUuid: string, animation: string) => void;
removeAnimation: (modelUuid: string, animation: string) => void; removeAnimation: (modelUuid: string, animation: string) => void;
@@ -143,13 +143,14 @@ export const createAssetStore = () => {
}, },
// Animation controls // Animation controls
setAnimations: (modelUuid, animations) => { setAnimation: (modelUuid, animation) => {
set((state) => { set((state) => {
const asset = state.assets.find(a => a.modelUuid === modelUuid); const asset = state.assets.find(a => a.modelUuid === modelUuid);
if (asset) { if (asset) {
asset.animations = animations;
if (!asset.animationState) { if (!asset.animationState) {
asset.animationState = { current: '', playing: false }; asset.animationState = { current: animation, playing: false };
} else {
asset.animationState.current = animation;
} }
} }
}); });

View File

@@ -9,13 +9,6 @@ interface BuilderState {
snappedPosition: [number, number, number] | null; snappedPosition: [number, number, number] | null;
hoveredLine: [Point, Point] | null; hoveredLine: [Point, Point] | null;
// Wall Asset
selectedWallAsset: Object3D | null;
deletableWallAsset: Object3D | null;
// Floor Asset
selectedFloorAsset: Object3D | null;
// Wall Settings // Wall Settings
selectedWall: Object3D | null; selectedWall: Object3D | null;
wallThickness: number; wallThickness: number;
@@ -58,13 +51,6 @@ interface BuilderState {
setSnappedPosition: (position: [number, number, number] | null) => void; setSnappedPosition: (position: [number, number, number] | null) => void;
setHoveredLine: (line: [Point, Point] | null) => void; setHoveredLine: (line: [Point, Point] | null) => void;
// Setters - Wall Asset
setSelectedWallAsset: (asset: Object3D | null) => void;
setDeletableWallAsset: (asset: Object3D | null) => void;
// Setters - Floor Asset
setSelectedFloorAsset: (asset: Object3D | null) => void;
// Setters - Wall // Setters - Wall
setSelectedWall: (wall: Object3D | null) => void; setSelectedWall: (wall: Object3D | null) => void;
setWallThickness: (thickness: number) => void; setWallThickness: (thickness: number) => void;
@@ -114,11 +100,6 @@ export const useBuilderStore = create<BuilderState>()(
snappedPosition: null, snappedPosition: null,
hoveredLine: null, hoveredLine: null,
selectedWallAsset: null,
deletableWallAsset: null,
selectedFloorAsset: null,
selectedWall: null, selectedWall: null,
wallThickness: 0.5, wallThickness: 0.5,
wallHeight: 7, wallHeight: 7,
@@ -175,28 +156,6 @@ export const useBuilderStore = create<BuilderState>()(
}) })
}, },
// === Setters: Wall Asset ===
setSelectedWallAsset(asset: Object3D | null) {
set((state) => {
state.selectedWallAsset = asset;
});
},
setDeletableWallAsset(asset: Object3D | null) {
set((state) => {
state.deletableWallAsset = asset;
});
},
// === Setters: Floor Asset ===
setSelectedFloorAsset(asset: Object3D | null) {
set((state) => {
state.selectedFloorAsset = asset;
});
},
// === Setters: Wall === // === Setters: Wall ===
setSelectedWall: (wall: Object3D | null) => { setSelectedWall: (wall: Object3D | null) => {

View File

@@ -5,9 +5,8 @@ interface WallAssetStore {
wallAssets: WallAsset[]; wallAssets: WallAsset[];
setWallAssets: (assets: WallAsset[]) => void; setWallAssets: (assets: WallAsset[]) => void;
addWallAsset: (asset: WallAsset) => void; addWallAsset: (asset: WallAsset) => void;
updateWallAsset: (uuid: string, updated: Partial<WallAsset>) => WallAsset | undefined; updateWallAsset: (uuid: string, updated: Partial<WallAsset>) => void;
setWallAssetPosition: (uuid: string, position: [number, number, number]) => void; removeWallAsset: (uuid: string) => void;
removeWallAsset: (uuid: string) => WallAsset | undefined;
clearWallAssets: () => void; clearWallAssets: () => void;
setVisibility: (uuid: string, isVisible: boolean) => void; setVisibility: (uuid: string, isVisible: boolean) => void;
@@ -31,36 +30,16 @@ export const createWallAssetStore = () => {
state.wallAssets.push(asset); state.wallAssets.push(asset);
}), }),
updateWallAsset: (uuid, updated) => { updateWallAsset: (uuid, updated) => set(state => {
let updatedWallAsset: WallAsset | undefined;
set(state => {
const asset = state.wallAssets.find(a => a.modelUuid === uuid);
if (asset) {
Object.assign(asset, updated);
updatedWallAsset = JSON.parse(JSON.stringify(asset));
}
});
return updatedWallAsset;
},
setWallAssetPosition: (uuid, position) => set(state => {
const asset = state.wallAssets.find(a => a.modelUuid === uuid); const asset = state.wallAssets.find(a => a.modelUuid === uuid);
if (asset) { if (asset) {
asset.position = position; Object.assign(asset, updated);
} }
}), }),
removeWallAsset: (uuid) => { removeWallAsset: (uuid) => set(state => {
let removedAsset: WallAsset | undefined; state.wallAssets = state.wallAssets.filter(a => a.modelUuid !== uuid);
set(state => { }),
const asset = state.wallAssets.find(a => a.modelUuid === uuid);
if (asset) {
removedAsset = JSON.parse(JSON.stringify(asset));
state.wallAssets = state.wallAssets.filter(a => a.modelUuid !== uuid);
}
});
return removedAsset;
},
clearWallAssets: () => { clearWallAssets: () => {
set(state => { set(state => {

View File

@@ -51,11 +51,9 @@ type Assets = Asset[];
interface WallAsset { interface WallAsset {
modelUuid: string; modelUuid: string;
modelName: string; modelName: string;
wallAssetType: string;
assetId: string; assetId: string;
wallUuid: string; wallUuid: string;
position: [number, number, number]; position: [number, number, number];
rotation: [number, number, number];
isLocked: boolean; isLocked: boolean;
isVisible: boolean; isVisible: boolean;
opacity: number; opacity: number;

View File

@@ -1,10 +1,14 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/useModuleStore"; import useModuleStore, {
useSubModuleStore,
useThreeDStore,
} from "../../store/useModuleStore";
import { usePlayerStore, useToggleStore } from "../../store/useUIToggleStore"; import { usePlayerStore, useToggleStore } from "../../store/useUIToggleStore";
import useVersionHistoryVisibleStore, { import useVersionHistoryVisibleStore, {
useActiveSubTool, useActiveSubTool,
useActiveTool, useActiveTool,
useAddAction, useAddAction,
useDfxUpload,
useRenameModeStore, useRenameModeStore,
useSaveVersion, useSaveVersion,
useSelectedComment, useSelectedComment,
@@ -49,7 +53,7 @@ const KeyPressListener: React.FC = () => {
const { setCreateNewVersion } = useVersionHistoryStore(); const { setCreateNewVersion } = useVersionHistoryStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { setSelectedComment } = useSelectedComment(); const { setSelectedComment } = useSelectedComment();
const { setDfxUploaded, setDxfWallGenerate } = useDfxUpload();
const isTextInput = (element: Element | null): boolean => const isTextInput = (element: Element | null): boolean =>
element instanceof HTMLInputElement || element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement || element instanceof HTMLTextAreaElement ||
@@ -160,14 +164,14 @@ const KeyPressListener: React.FC = () => {
setCreateNewVersion(true); setCreateNewVersion(true);
setVersionHistoryVisible(true); setVersionHistoryVisible(true);
setSubModule("properties"); setSubModule("properties");
setActiveModule('builder'); setActiveModule("builder");
break; break;
case "Ctrl+H": case "Ctrl+H":
if (!isPlaying) { if (!isPlaying) {
setVersionHistoryVisible(true); setVersionHistoryVisible(true);
setSubModule("properties"); setSubModule("properties");
setActiveModule('builder'); setActiveModule("builder");
} }
break; break;
@@ -195,6 +199,7 @@ const KeyPressListener: React.FC = () => {
clearComparisonProduct(); clearComparisonProduct();
setIsLogListVisible(false); setIsLogListVisible(false);
setIsRenameMode(false); setIsRenameMode(false);
setDfxUploaded([]);
setSelectedComment(null); setSelectedComment(null);
} }