Merge remote-tracking branch 'origin/main-dev' into feature/layout-comparison-version

This commit is contained in:
2025-09-09 09:40:04 +05:30
35 changed files with 1419 additions and 1546 deletions

View File

@@ -3,11 +3,7 @@ import { createPortal } from "react-dom";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import img from "../../assets/image/image.png"; import img from "../../assets/image/image.png";
import { getUserData } from "../../functions/getUserData"; import { getUserData } from "../../functions/getUserData";
import { import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store";
useLoadingProgress,
useProjectName,
useSocketStore,
} from "../../store/builder/store";
import OuterClick from "../../utils/outerClick"; import OuterClick from "../../utils/outerClick";
import { KebabIcon } from "../icons/ExportCommonIcons"; import { KebabIcon } from "../icons/ExportCommonIcons";
import { getAllProjects } from "../../services/dashboard/getAllProjects"; import { getAllProjects } from "../../services/dashboard/getAllProjects";
@@ -15,323 +11,275 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects";
// import { updateProject } from "../../services/dashboard/updateProject"; // import { updateProject } from "../../services/dashboard/updateProject";
interface DashBoardCardProps { interface DashBoardCardProps {
projectName: string; projectName: string;
thumbnail: string; thumbnail: string;
projectId: string; projectId: string;
createdAt?: string; createdAt?: string;
isViewed?: string; isViewed?: string;
createdBy?: { _id: string; userName: string }; createdBy?: { _id: string; userName: string };
handleDeleteProject?: (projectId: string) => Promise<void>; handleDeleteProject?: (projectId: string) => Promise<void>;
handleTrashDeleteProject?: (projectId: string) => Promise<void>; handleTrashDeleteProject?: (projectId: string) => Promise<void>;
handleRestoreProject?: (projectId: string) => Promise<void>; handleRestoreProject?: (projectId: string) => Promise<void>;
handleDuplicateWorkspaceProject?: ( handleDuplicateWorkspaceProject?: (projectId: string, projectName: string, thumbnail: string, userId?: string) => Promise<void>;
projectId: string, handleDuplicateRecentProject?: (projectId: string, projectName: string, thumbnail: string) => Promise<void>;
projectName: string, active?: "shared" | "trash" | "recent" | string;
thumbnail: string, setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
userId?: string setRecentDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
) => Promise<void>; setProjectDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
handleDuplicateRecentProject?: ( setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
projectId: string, openKebabProjectId: string | null;
projectName: string, setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
thumbnail: string
) => Promise<void>;
active?: "shared" | "trash" | "recent" | string;
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
openKebabProjectId: string | null;
setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
} }
type RelativeTimeFormatUnit = type RelativeTimeFormatUnit = "year" | "month" | "week" | "day" | "hour" | "minute" | "second";
| "year"
| "month"
| "week"
| "day"
| "hour"
| "minute"
| "second";
const kebabOptionsMap: Record<string, string[]> = { const kebabOptionsMap: Record<string, string[]> = {
default: ["rename", "delete", "duplicate", "open in new tab"], default: ["rename", "delete", "duplicate", "open in new tab"],
trash: ["restore", "delete"], trash: ["restore", "delete"],
shared: ["duplicate", "open in new tab"], shared: ["duplicate", "open in new tab"],
}; };
const DashboardCard: React.FC<DashBoardCardProps> = ({ const DashboardCard: React.FC<DashBoardCardProps> = ({
projectName,
thumbnail,
projectId,
active,
handleDeleteProject,
handleRestoreProject,
handleTrashDeleteProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
createdAt,
createdBy,
setRecentDuplicateData,
setProjectDuplicateData,
setActiveFolder,
openKebabProjectId,
setOpenKebabProjectId,
}) => {
const navigate = useNavigate();
const { setProjectName } = useProjectName();
const { userId, organization, userName } = getUserData();
const { projectSocket } = useSocketStore();
const { setLoadingProgress } = useLoadingProgress();
const isKebabOpen = openKebabProjectId === projectId;
const [renameValue, setRenameValue] = useState(projectName);
const [isRenaming, setIsRenaming] = useState(false);
const kebabRef = useRef<HTMLDivElement>(null);
// Close kebab when clicking outside
OuterClick({
contextClassName: [`tag-${projectId}`],
setMenuVisible: () => {
if (isKebabOpen) setOpenKebabProjectId(null);
},
});
const navigateToProject = useCallback(() => {
if (active === "trash") return;
setLoadingProgress(1);
setProjectName(projectName);
navigate(`/projects/${projectId}`);
}, [
active,
projectId,
projectName, projectName,
navigate, thumbnail,
setLoadingProgress, projectId,
setProjectName, active,
]); handleDeleteProject,
handleRestoreProject,
handleTrashDeleteProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
createdAt,
createdBy,
setRecentDuplicateData,
setProjectDuplicateData,
setActiveFolder,
openKebabProjectId,
setOpenKebabProjectId,
}) => {
const navigate = useNavigate();
const { setProjectName } = useProjectName();
const { userId, organization, userName } = getUserData();
const { projectSocket } = useSocketStore();
const { setLoadingProgress } = useLoadingProgress();
const getOptions = useCallback(() => { const isKebabOpen = openKebabProjectId === projectId;
if (active === "trash") return kebabOptionsMap.trash; const [renameValue, setRenameValue] = useState(projectName);
if (active === "shared" || (createdBy && createdBy._id !== userId)) { const [isRenaming, setIsRenaming] = useState(false);
return kebabOptionsMap.shared; const kebabRef = useRef<HTMLDivElement>(null);
}
return kebabOptionsMap.default;
}, [active, createdBy, userId]);
const handleProjectName = useCallback( // Close kebab when clicking outside
async (newName: string) => { OuterClick({
setRenameValue(newName); contextClassName: [`tag-${projectId}`],
if (!projectId) return; setMenuVisible: () => {
if (isKebabOpen) setOpenKebabProjectId(null);
},
});
try { const navigateToProject = useCallback(() => {
const projects = await getAllProjects(userId, organization); if (active === "trash") return;
const projectUuid = projects?.Projects?.find( setLoadingProgress(1);
(val: any) => val.projectUuid === projectId || val._id === projectId setProjectName(projectName);
); navigate(`/projects/${projectId}`);
if (!projectUuid) return; }, [active, projectId, projectName, navigate, setLoadingProgress, setProjectName]);
const updatePayload = { const getOptions = useCallback(() => {
projectId: projectUuid._id, if (active === "trash") return kebabOptionsMap.trash;
organization, if (active === "shared" || (createdBy && createdBy._id !== userId)) {
userId, return kebabOptionsMap.shared;
projectName: newName, }
return kebabOptionsMap.default;
}, [active, createdBy, userId]);
const handleProjectName = useCallback(
async (newName: string) => {
setRenameValue(newName);
if (!projectId) return;
try {
const projects = await getAllProjects(userId, organization);
const projectUuid = projects?.Projects?.find((val: any) => val.projectUuid === projectId || val._id === projectId);
if (!projectUuid) return;
const updatePayload = {
projectId: projectUuid._id,
organization,
userId,
projectName: newName,
};
if (projectSocket) {
projectSocket.emit("v1:project:update", updatePayload);
}
} catch {
// silent fail
}
},
[projectId, userId, organization, projectSocket]
);
const handleOptionClick = useCallback(
async (option: string) => {
switch (option) {
case "delete":
await (active === "trash" ? handleTrashDeleteProject?.(projectId) : handleDeleteProject?.(projectId));
break;
case "restore":
await handleRestoreProject?.(projectId);
break;
case "open in new tab":
setProjectName(projectName);
window.open(`/projects/${projectId}`, "_blank");
break;
case "rename":
setIsRenaming(true);
break;
case "duplicate":
if (handleDuplicateWorkspaceProject) {
setProjectDuplicateData?.({ projectId, projectName, thumbnail });
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId);
if (active === "shared") {
setActiveFolder?.("myProjects");
}
} else if (handleDuplicateRecentProject) {
setRecentDuplicateData?.({
projectId,
projectName,
thumbnail,
userId,
});
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
}
break;
}
},
[
projectId,
projectName,
thumbnail,
userId,
active,
handleDeleteProject,
handleTrashDeleteProject,
handleRestoreProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
setProjectName,
setProjectDuplicateData,
setRecentDuplicateData,
setActiveFolder,
]
);
const getRelativeTime = useCallback((dateString: string): string => {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
const intervals: Record<RelativeTimeFormatUnit, number> = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1,
}; };
if (projectSocket) { const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
projectSocket.emit("v1:project:update", updatePayload); for (const [unit, seconds] of Object.entries(intervals)) {
const diff = Math.floor(diffInSeconds / seconds);
if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit);
} }
} catch { return "just now";
// silent fail }, []);
}
},
[projectId, userId, organization, projectSocket]
);
const handleOptionClick = useCallback( const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 });
async (option: string) => {
switch (option) { useEffect(() => {
case "delete": if (isKebabOpen && kebabRef.current) {
await (handleDeleteProject?.(projectId) ?? const rect = kebabRef.current.getBoundingClientRect();
handleTrashDeleteProject?.(projectId)); setKebabPosition({
break; top: rect.bottom + window.scrollY,
case "restore": left: rect.left + window.scrollX - 80,
await handleRestoreProject?.(projectId);
break;
case "open in new tab":
setProjectName(projectName);
window.open(`/projects/${projectId}`, "_blank");
break;
case "rename":
setIsRenaming(true);
break;
case "duplicate":
if (handleDuplicateWorkspaceProject) {
setProjectDuplicateData?.({ projectId, projectName, thumbnail });
await handleDuplicateWorkspaceProject(
projectId,
projectName,
thumbnail,
userId
);
if (active === "shared") {
setActiveFolder?.("myProjects");
}
} else if (handleDuplicateRecentProject) {
setRecentDuplicateData?.({
projectId,
projectName,
thumbnail,
userId,
}); });
await handleDuplicateRecentProject( }
projectId, }, [isKebabOpen]);
projectName,
thumbnail
);
}
break;
}
},
[
projectId,
projectName,
thumbnail,
userId,
active,
handleDeleteProject,
handleTrashDeleteProject,
handleRestoreProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
setProjectName,
setProjectDuplicateData,
setRecentDuplicateData,
setActiveFolder,
]
);
const getRelativeTime = useCallback((dateString: string): string => { return (
const date = new Date(dateString); <div className="dashboard-card-container" onClick={navigateToProject} title={projectName}>
const now = new Date(); <div className="dashboard-card-wrapper">
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); <div className="preview-container">
<img src={thumbnail || img} alt={`${projectName} thumbnail`} />
</div>
const intervals: Record<RelativeTimeFormatUnit, number> = { <div className="project-details-container" onClick={(e) => e.stopPropagation()}>
year: 31536000, <div className="project-details">
month: 2592000, {isRenaming ? (
week: 604800, <input
day: 86400, value={renameValue}
hour: 3600, onChange={(e) => handleProjectName(e.target.value)}
minute: 60, onBlur={() => {
second: 1, setIsRenaming(false);
}; setProjectName(renameValue);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
setIsRenaming(false);
setProjectName(renameValue);
}
}}
aria-label="Rename project"
autoFocus
/>
) : (
<span>{renameValue}</span>
)}
{createdAt && (
<div className="project-data">
{active === "trash" ? "Trashed" : "Edited"} {getRelativeTime(createdAt)}
</div>
)}
</div>
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); <div className="users-list-container" ref={kebabRef}>
for (const [unit, seconds] of Object.entries(intervals)) { <div className="user-profile">{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}</div>
const diff = Math.floor(diffInSeconds / seconds); <button
if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit); className="kebab-wrapper"
} onClick={(e) => {
return "just now"; e.stopPropagation();
}, []); setOpenKebabProjectId(isKebabOpen ? null : projectId);
}}
const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 }); aria-haspopup="true"
aria-expanded={isKebabOpen}
useEffect(() => { aria-label="Project options"
if (isKebabOpen && kebabRef.current) { >
const rect = kebabRef.current.getBoundingClientRect(); <KebabIcon />
setKebabPosition({ </button>
top: rect.bottom + window.scrollY, </div>
left: rect.left + window.scrollX - 80, </div>
});
}
}, [isKebabOpen]);
return (
<div
className="dashboard-card-container"
onClick={navigateToProject}
title={projectName}
>
<div className="dashboard-card-wrapper">
<div className="preview-container">
<img src={thumbnail || img} alt={`${projectName} thumbnail`} />
</div>
<div
className="project-details-container"
onClick={(e) => e.stopPropagation()}
>
<div className="project-details">
{isRenaming ? (
<input
value={renameValue}
onChange={(e) => handleProjectName(e.target.value)}
onBlur={() => {
setIsRenaming(false);
setProjectName(renameValue);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
setIsRenaming(false);
setProjectName(renameValue);
}
}}
aria-label="Rename project"
autoFocus
/>
) : (
<span>{renameValue}</span>
)}
{createdAt && (
<div className="project-data">
{active === "trash" ? "Trashed" : "Edited"}{" "}
{getRelativeTime(createdAt)}
</div>
)}
</div>
<div className="users-list-container" ref={kebabRef}>
<div className="user-profile">
{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}
</div> </div>
<button
className="kebab-wrapper"
onClick={(e) => {
e.stopPropagation();
setOpenKebabProjectId(isKebabOpen ? null : projectId);
}}
aria-haspopup="true"
aria-expanded={isKebabOpen}
aria-label="Project options"
>
<KebabIcon />
</button>
</div>
</div>
</div>
{isKebabOpen && {isKebabOpen &&
createPortal( createPortal(
<div <div className={`kebab-options-wrapper tag-${projectId}`} style={{ position: "fixed", zIndex: 9999, ...kebabPosition }}>
className={`kebab-options-wrapper tag-${projectId}`} {getOptions().map((option) => (
style={{ position: "fixed", zIndex: 9999, ...kebabPosition }} <button
> key={option}
{getOptions().map((option) => ( className="option"
<button onClick={(e) => {
key={option} e.stopPropagation();
className="option" handleOptionClick(option);
onClick={(e) => { }}
e.stopPropagation(); >
handleOptionClick(option); {option}
}} </button>
> ))}
{option} </div>,
</button> document.body
))} )}
</div>, </div>
document.body );
)}
</div>
);
}; };
export default DashboardCard; export default DashboardCard;

View File

@@ -1,13 +1,5 @@
import React from "react"; import React from "react";
import { import { DocumentationIcon, HelpIcon, HomeIcon, LogoutIcon, NotificationIcon, ProjectsIcon, TutorialsIcon } from "../icons/DashboardIcon";
DocumentationIcon,
HelpIcon,
HomeIcon,
LogoutIcon,
NotificationIcon,
ProjectsIcon,
TutorialsIcon,
} from "../icons/DashboardIcon";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import darkThemeImage from "../../assets/image/darkThemeProject.png"; import darkThemeImage from "../../assets/image/darkThemeProject.png";
import lightThemeImage from "../../assets/image/lightThemeProject.png"; import lightThemeImage from "../../assets/image/lightThemeProject.png";
@@ -54,7 +46,6 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
projectUuid: projectId, projectUuid: projectId,
}; };
console.log('addProject: ', addProject);
projectSocket.emit("v1:project:add", addProject); projectSocket.emit("v1:project:add", addProject);
} else { } else {
// API // API
@@ -72,11 +63,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
<div className="side-pannel-header"> <div className="side-pannel-header">
<div className="user-container"> <div className="user-container">
<div className="user-profile">{userName?.charAt(0).toUpperCase()}</div> <div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
<div className="user-name"> <div className="user-name">{userName ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() : "Anonymous"}</div>
{userName
? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()
: "Anonymous"}
</div>
</div> </div>
<div className="notifications-container"> <div className="notifications-container">
<NotificationIcon /> <NotificationIcon />
@@ -87,26 +74,15 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
</div> </div>
<div className="side-bar-content-container"> <div className="side-bar-content-container">
<div className="side-bar-options-container"> <div className="side-bar-options-container">
<button <button className={activeTab === "Home" ? "option-list active" : "option-list"} onClick={() => setActiveTab("Home")}>
className={activeTab === "Home" ? "option-list active" : "option-list"}
onClick={() => setActiveTab("Home")}
>
<HomeIcon /> <HomeIcon />
Home Home
</button> </button>
<button <button className={activeTab === "Projects" ? "option-list active" : "option-list"} title="Projects" onClick={() => setActiveTab("Projects")}>
className={activeTab === "Projects" ? "option-list active" : "option-list"}
title="Projects"
onClick={() => setActiveTab("Projects")}
>
<ProjectsIcon /> <ProjectsIcon />
Projects Projects
</button> </button>
<button <button className={activeTab === "Trash" ? "option-list active" : "option-list"} title="Trash" onClick={() => setActiveTab("Trash")}>
className={activeTab === "Trash" ? "option-list active" : "option-list"}
title="Trash"
onClick={() => setActiveTab("Trash")}
>
<TrashIcon /> <TrashIcon />
Trash Trash
</button> </button>
@@ -123,9 +99,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
Tutorials Tutorials
</button> </button>
<button <button
className={ className={activeTab === "Documentation" ? "option-list active" : "option-list"}
activeTab === "Documentation" ? "option-list active" : "option-list"
}
title="coming soon" title="coming soon"
disabled disabled
onClick={() => { onClick={() => {

View File

@@ -4,7 +4,7 @@ import { useLogger } from "../ui/log/LoggerContext";
import { GetLogIcon } from "./getLogIcons"; import { GetLogIcon } from "./getLogIcons";
import { CurserLeftIcon, CurserMiddleIcon, CurserRightIcon } from "../icons/LogIcons"; import { CurserLeftIcon, CurserMiddleIcon, CurserRightIcon } from "../icons/LogIcons";
import ShortcutHelper from "./shortcutHelper"; import ShortcutHelper from "./shortcutHelper";
import useVersionHistoryVisibleStore, { useShortcutStore } from "../../store/builder/store"; import { useShortcutStore } from "../../store/builder/store";
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore"; import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
import useModuleStore, { useSubModuleStore } from "../../store/ui/useModuleStore"; import useModuleStore, { useSubModuleStore } from "../../store/ui/useModuleStore";
import { mouseActionHelper } from "../../utils/mouseUtils/mouseHelper"; import { mouseActionHelper } from "../../utils/mouseUtils/mouseHelper";
@@ -12,123 +12,118 @@ import { useMouseNoteStore } from "../../store/ui/useUIToggleStore";
import { useSceneContext } from "../../modules/scene/sceneContext"; import { useSceneContext } from "../../modules/scene/sceneContext";
const Footer: React.FC = () => { const Footer: React.FC = () => {
const { logs, setIsLogListVisible } = useLogger(); const { logs, setIsLogListVisible } = useLogger();
const lastLog = logs[logs.length - 1] || null; const lastLog = logs[logs.length - 1] || null;
const { setActiveModule } = useModuleStore(); const { setActiveModule } = useModuleStore();
const { setSubModule } = useSubModuleStore(); const { setSubModule } = useSubModuleStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { isPlaying } = usePlayButtonStore();
const { isPlaying } = usePlayButtonStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore();
const { showShortcuts, setShowShortcuts } = useShortcutStore(); const { versionStore } = useSceneContext();
const { versionStore } = useSceneContext(); const { selectedVersion, setVersionHistoryVisible } = versionStore();
const { selectedVersion } = versionStore();
const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore(); const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore();
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine); const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
// -------------------- Online/Offline Handlers -------------------- // -------------------- Online/Offline Handlers --------------------
const handleOnline = useCallback(() => { const handleOnline = useCallback(() => {
echo.success("You are back Online"); echo.success("You are back Online");
setIsOnline(true); setIsOnline(true);
}, []); }, []);
const handleOffline = useCallback(() => { const handleOffline = useCallback(() => {
echo.warn("Changes made now might not be saved"); echo.warn("Changes made now might not be saved");
echo.error("You are now Offline."); echo.error("You are now Offline.");
setIsOnline(false); setIsOnline(false);
}, []); }, []);
useEffect(() => { useEffect(() => {
window.addEventListener("online", handleOnline); window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline); window.addEventListener("offline", handleOffline);
return () => { return () => {
window.removeEventListener("online", handleOnline); window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline); window.removeEventListener("offline", handleOffline);
}; };
}, [handleOnline, handleOffline]); }, [handleOnline, handleOffline]);
// -------------------- Mouse Buttons -------------------- // -------------------- Mouse Buttons --------------------
const mouseButtons = useMemo( const mouseButtons = useMemo(
() => [ () => [
{ icon: <CurserLeftIcon />, label: Leftnote || "Pan", mouse: "left" }, { icon: <CurserLeftIcon />, label: Leftnote || "Pan", mouse: "left" },
{ icon: <CurserMiddleIcon />, label: Middlenote || "Scroll Zoom", mouse: "middle" }, { icon: <CurserMiddleIcon />, label: Middlenote || "Scroll Zoom", mouse: "middle" },
{ icon: <CurserRightIcon />, label: Rightnote || "Orbit / Cancel action", mouse: "right" }, { icon: <CurserRightIcon />, label: Rightnote || "Orbit / Cancel action", mouse: "right" },
], ],
[Leftnote, Middlenote, Rightnote] [Leftnote, Middlenote, Rightnote]
); );
// -------------------- Mouse Helper -------------------- // -------------------- Mouse Helper --------------------
useEffect(() => { useEffect(() => {
const cleanup = mouseActionHelper(); const cleanup = mouseActionHelper();
return () => cleanup(); return () => cleanup();
}, []); }, []);
return ( return (
<div className="footer-container"> <div className="footer-container">
<div className="footer-wrapper"> <div className="footer-wrapper">
{/* Mouse Button Info */} {/* Mouse Button Info */}
<div className="selection-wrapper"> <div className="selection-wrapper">
{mouseButtons.map(({ icon, label, mouse }) => ( {mouseButtons.map(({ icon, label, mouse }) => (
<div className="selector-wrapper" key={mouse}> <div className="selector-wrapper" key={mouse}>
<div className="icon">{icon}</div> <div className="icon">{icon}</div>
<div className="selector">{label}</div> <div className="selector">{label}</div>
</div>
))}
</div>
{/* Logs and Version */}
<div className="logs-wrapper">
<div className="bg-dummy left-top" />
<div className="bg-dummy right-bottom" />
<div className="log-container">
<button id="log-details-buttton" className={`logs-detail ${lastLog?.type ?? ""}`} onClick={() => setIsLogListVisible(true)}>
{lastLog ? (
<>
<span className="log-icon">{GetLogIcon(lastLog.type)}</span>
<span className="log-message">{lastLog.message}</span>
</>
) : (
"There are no logs to display at the moment."
)}
</button>
</div>
<div
className="version"
onClick={() => {
setVersionHistoryVisible(true);
setSubModule("properties");
setActiveModule("builder");
}}
>
{selectedVersion?.version ?? "v 0.0.0"}
<div className="icon">
<HelpIcon />
</div>
</div>
<div className={`wifi-connection ${isOnline ? "connected" : "disconnected"}`}>
<div className="icon">
<WifiIcon />
</div>
<div className="tooltip">{isOnline ? "Online" : "Offline"}</div>
</div>
</div>
</div> </div>
))}
{/* Shortcut Helper */}
{!isPlaying && showShortcuts && (
<div className="shortcut-helper-overlay visible">
<ShortcutHelper setShowShortcuts={setShowShortcuts} />
</div>
)}
</div> </div>
);
{/* Logs and Version */}
<div className="logs-wrapper">
<div className="bg-dummy left-top" />
<div className="bg-dummy right-bottom" />
<div className="log-container">
<button
id="log-details-buttton"
className={`logs-detail ${lastLog?.type ?? ""}`}
onClick={() => setIsLogListVisible(true)}
>
{lastLog ? (
<>
<span className="log-icon">{GetLogIcon(lastLog.type)}</span>
<span className="log-message">{lastLog.message}</span>
</>
) : (
"There are no logs to display at the moment."
)}
</button>
</div>
<div
className="version"
onClick={() => {
setVersionHistoryVisible(true);
setSubModule("properties");
setActiveModule("builder");
}}
>
{selectedVersion?.version ?? "v 0.0.0"}
<div className="icon">
<HelpIcon />
</div>
</div>
<div className={`wifi-connection ${isOnline ? "connected" : "disconnected"}`}>
<div className="icon">
<WifiIcon />
</div>
<div className="tooltip">{isOnline ? "Online" : "Offline"}</div>
</div>
</div>
</div>
{/* Shortcut Helper */}
{!isPlaying && showShortcuts && (
<div className="shortcut-helper-overlay visible">
<ShortcutHelper setShowShortcuts={setShowShortcuts} />
</div>
)}
</div>
);
}; };
export default Footer; export default Footer;

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Header from "./Header"; import Header from "./Header";
import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore"; import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore";
import { AnalysisIcon, FilePackageIcon, MechanicsIcon, PropertiesIcon, SimulationIcon, } from "../../icons/SimulationIcons"; import { AnalysisIcon, FilePackageIcon, MechanicsIcon, PropertiesIcon, SimulationIcon } from "../../icons/SimulationIcons";
import { useToggleStore } from "../../../store/ui/useUIToggleStore"; import { useToggleStore } from "../../../store/ui/useUIToggleStore";
import Visualization from "./visualization/Visualization"; import Visualization from "./visualization/Visualization";
import Analysis from "./analysis/Analysis"; import Analysis from "./analysis/Analysis";
import Simulations from "./simulation/Simulations"; import Simulations from "./simulation/Simulations";
import useVersionHistoryVisibleStore, { useIsComparing, useToolMode } from "../../../store/builder/store"; import { useIsComparing, useToolMode } from "../../../store/builder/store";
import { useSelectedEventData, useSelectedEventSphere, } from "../../../store/simulation/useSimulationStore"; import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
import { useBuilderStore } from "../../../store/builder/useBuilderStore"; import { useBuilderStore } from "../../../store/builder/useBuilderStore";
import GlobalProperties from "./properties/GlobalProperties"; import GlobalProperties from "./properties/GlobalProperties";
import AssetProperties from "./properties/AssetProperties"; import AssetProperties from "./properties/AssetProperties";
@@ -22,6 +22,7 @@ import SelectedFloorProperties from "./properties/SelectedFloorProperties";
import SelectedDecalProperties from "./properties/SelectedDecalProperties"; import SelectedDecalProperties from "./properties/SelectedDecalProperties";
import SelectedAisleProperties from "./properties/SelectedAisleProperties"; import SelectedAisleProperties from "./properties/SelectedAisleProperties";
import ResourceManagement from "./resourceManagement/ResourceManagement"; import ResourceManagement from "./resourceManagement/ResourceManagement";
import { useSceneContext } from "../../../modules/scene/sceneContext";
type DisplayComponent = type DisplayComponent =
| "versionHistory" | "versionHistory"
@@ -51,7 +52,8 @@ const SideBarRight: React.FC = () => {
const { selectedWall, selectedFloor, selectedAisle, selectedFloorAsset } = useBuilderStore(); const { selectedWall, selectedFloor, selectedAisle, selectedFloorAsset } = useBuilderStore();
const { selectedEventData } = useSelectedEventData(); const { selectedEventData } = useSelectedEventData();
const { selectedEventSphere } = useSelectedEventSphere(); const { selectedEventSphere } = useSelectedEventSphere();
const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { versionStore } = useSceneContext();
const { viewVersionHistory, setVersionHistoryVisible } = versionStore();
const { isComparing } = useIsComparing(); const { isComparing } = useIsComparing();
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none"); const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
@@ -207,8 +209,7 @@ const SideBarRight: React.FC = () => {
<> <>
<button <button
id="sidebar-action-list-properties" id="sidebar-action-list-properties"
className={`sidebar-action-list ${subModule === "properties" ? "active" : "" className={`sidebar-action-list ${subModule === "properties" ? "active" : ""}`}
}`}
onClick={() => { onClick={() => {
setSubModule("properties"); setSubModule("properties");
setVersionHistoryVisible(false); setVersionHistoryVisible(false);
@@ -224,8 +225,7 @@ const SideBarRight: React.FC = () => {
<> <>
<button <button
id="sidebar-action-list-simulation" id="sidebar-action-list-simulation"
className={`sidebar-action-list ${subModule === "simulations" ? "active" : "" className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""}`}
}`}
onClick={() => { onClick={() => {
setSubModule("simulations"); setSubModule("simulations");
setVersionHistoryVisible(false); setVersionHistoryVisible(false);
@@ -236,8 +236,7 @@ const SideBarRight: React.FC = () => {
</button> </button>
<button <button
id="sidebar-action-list-mechanics" id="sidebar-action-list-mechanics"
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : "" className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""}`}
}`}
onClick={() => { onClick={() => {
setSubModule("mechanics"); setSubModule("mechanics");
setVersionHistoryVisible(false); setVersionHistoryVisible(false);
@@ -248,8 +247,7 @@ const SideBarRight: React.FC = () => {
</button> </button>
<button <button
id="sidebar-action-list-analysis" id="sidebar-action-list-analysis"
className={`sidebar-action-list ${subModule === "analysis" ? "active" : "" className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""}`}
}`}
onClick={() => { onClick={() => {
setSubModule("analysis"); setSubModule("analysis");
setVersionHistoryVisible(false); setVersionHistoryVisible(false);
@@ -261,23 +259,19 @@ const SideBarRight: React.FC = () => {
</> </>
)} )}
{(activeModule === "builder" || {(activeModule === "builder" || activeModule === "simulation") && (
activeModule === "simulation") && ( <button
<button id="sidebar-action-list-properties"
id="sidebar-action-list-properties" className={`sidebar-action-list ${subModule === "resourceManagement" ? "active" : ""}`}
className={`sidebar-action-list ${subModule === "resourceManagement" ? "active" : "" onClick={() => {
}`} setSubModule("resourceManagement");
onClick={() => { setVersionHistoryVisible(false);
setSubModule("resourceManagement"); }}
setVersionHistoryVisible(false); >
}} <div className="tooltip">Resource Management</div>
> <FilePackageIcon isActive={subModule === "resourceManagement"} />
<div className="tooltip">Resource Management</div> </button>
<FilePackageIcon )}
isActive={subModule === "resourceManagement"}
/>
</button>
)}
</div> </div>
)} )}

View File

@@ -2,7 +2,6 @@ import { useParams } from "react-router-dom";
import { AddIcon, ArrowIcon, CloseIcon, KebabIcon, LocationIcon } from "../../../icons/ExportCommonIcons"; import { AddIcon, ArrowIcon, CloseIcon, KebabIcon, LocationIcon } from "../../../icons/ExportCommonIcons";
import { useSubModuleStore } from "../../../../store/ui/useModuleStore"; import { useSubModuleStore } from "../../../../store/ui/useModuleStore";
import { useSceneContext } from "../../../../modules/scene/sceneContext"; import { useSceneContext } from "../../../../modules/scene/sceneContext";
import useVersionHistoryVisibleStore from "../../../../store/builder/store";
import RenameInput from "../../../ui/inputs/RenameInput"; import RenameInput from "../../../ui/inputs/RenameInput";
import { getVersionDataApi } from "../../../../services/factoryBuilder/versionControl/getVersionDataApi"; import { getVersionDataApi } from "../../../../services/factoryBuilder/versionControl/getVersionDataApi";
@@ -10,8 +9,7 @@ import { getVersionDataApi } from "../../../../services/factoryBuilder/versionCo
const VersionHistory = () => { const VersionHistory = () => {
const { setSubModule } = useSubModuleStore(); const { setSubModule } = useSubModuleStore();
const { versionStore } = useSceneContext(); const { versionStore } = useSceneContext();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { versionHistory, setCreateNewVersion, selectedVersion, setSelectedVersion, setVersionHistoryVisible } = versionStore();
const { versionHistory, setCreateNewVersion, selectedVersion, setSelectedVersion } = versionStore();
const { projectId } = useParams(); const { projectId } = useParams();
const addNewVersion = () => { const addNewVersion = () => {
@@ -21,16 +19,16 @@ const VersionHistory = () => {
const handleSelectVersion = (version: Version) => { const handleSelectVersion = (version: Version) => {
if (!projectId) return; if (!projectId) return;
getVersionDataApi(projectId, version.versionId).then((versionData) => { getVersionDataApi(projectId, version.versionId)
setSelectedVersion(version); .then((versionData) => {
}).catch((err) => { setSelectedVersion(version);
echo.error(err); })
}) .catch((err) => {
echo.error(err);
});
}; };
const handleVersionNameChange = (newName: string, versionId: string) => { const handleVersionNameChange = (newName: string, versionId: string) => {};
};
return ( return (
<div className="version-history-container"> <div className="version-history-container">
@@ -38,11 +36,7 @@ const VersionHistory = () => {
<div className="version-history-header"> <div className="version-history-header">
<div className="version-history-title">Version History</div> <div className="version-history-title">Version History</div>
<div className="version-history-icons"> <div className="version-history-icons">
<button <button id="add-version" className="icon add-icon" onClick={addNewVersion}>
id="add-version"
className="icon add-icon"
onClick={addNewVersion}
>
<AddIcon /> <AddIcon />
</button> </button>
<div id="version-kebab" className="icon kebab-icon"> <div id="version-kebab" className="icon kebab-icon">
@@ -64,9 +58,7 @@ const VersionHistory = () => {
{/* Shortcut Info */} {/* Shortcut Info */}
<div className="version-history-shortcut-info"> <div className="version-history-shortcut-info">
<div className="info-icon">i</div> <div className="info-icon">i</div>
<div className="shortcut-text"> <div className="shortcut-text">Press Ctrl + Alt + S to add to version history while editing</div>
Press Ctrl + Alt + S to add to version history while editing
</div>
</div> </div>
{/* Current Version Display */} {/* Current Version Display */}
@@ -76,12 +68,8 @@ const VersionHistory = () => {
<LocationIcon /> <LocationIcon />
</div> </div>
<div className="location-details"> <div className="location-details">
<div className="current-version"> <div className="current-version">Current Version ({selectedVersion.version})</div>
Current Version ({selectedVersion.version}) <div className="saved-history-count">{versionHistory.length} Saved History</div>
</div>
<div className="saved-history-count">
{versionHistory.length} Saved History
</div>
</div> </div>
</div> </div>
)} )}
@@ -93,16 +81,8 @@ const VersionHistory = () => {
) : ( ) : (
versionHistory.map((version) => { versionHistory.map((version) => {
const key = `version-${version.versionId}`; const key = `version-${version.versionId}`;
return ( return <VersionHistoryItem key={key} version={version} onSelect={handleSelectVersion} onRename={handleVersionNameChange} />;
<VersionHistoryItem
key={key}
version={version}
onSelect={handleSelectVersion}
onRename={handleVersionNameChange}
/>
);
}) })
)} )}
</div> </div>
</div> </div>
@@ -119,22 +99,14 @@ type VersionHistoryItemProps = {
const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ version, onSelect, onRename }) => { const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ version, onSelect, onRename }) => {
return ( return (
<button <button className="saved-version">
className="saved-version" <div className="version-name" onClick={() => onSelect(version)}>
>
<div
className="version-name"
onClick={() => onSelect(version)}
>
v {version.version} v {version.version}
</div> </div>
<div className="version-details"> <div className="version-details">
<div className="details"> <div className="details">
<span className="timestamp"> <span className="timestamp">
<RenameInput <RenameInput value={version.versionName ? version.versionName : version.timeStamp} onRename={(newName) => onRename(newName, version.versionId)} />
value={version.versionName ? version.versionName : version.timeStamp}
onRename={(newName) => onRename(newName, version.versionId)}
/>
</span> </span>
<span className="saved-by"> <span className="saved-by">
<div className="user-profile">{version.createdBy[0]}</div> <div className="user-profile">{version.createdBy[0]}</div>

View File

@@ -1,104 +1,84 @@
import React from "react"; import React from "react";
import useModuleStore from "../../store/ui/useModuleStore"; import useModuleStore from "../../store/ui/useModuleStore";
import { import { BuilderIcon, CartIcon, SimulationIcon, VisualizationIcon } from "../icons/ExportModuleIcons";
BuilderIcon, import { useToggleStore } from "../../store/ui/useUIToggleStore";
CartIcon, import { useSceneContext } from "../../modules/scene/sceneContext";
SimulationIcon,
VisualizationIcon,
} from "../icons/ExportModuleIcons";
import {useToggleStore} from "../../store/ui/useUIToggleStore";
import useVersionStore from "../../store/builder/store";
const ModuleToggle: React.FC = () => { const ModuleToggle: React.FC = () => {
const { activeModule, setActiveModule } = useModuleStore(); const { activeModule, setActiveModule } = useModuleStore();
const { setToggleUI } = useToggleStore(); const { setToggleUI } = useToggleStore();
const { setVersionHistoryVisible } = useVersionStore(); const { versionStore } = useSceneContext();
const { setVersionHistoryVisible } = versionStore();
return ( return (
<div className="module-toggle-container"> <div className="module-toggle-container">
<button <button
id={"builder"} id={"builder"}
className={`module-list ${activeModule === "builder" ? "active" : ""}`} className={`module-list ${activeModule === "builder" ? "active" : ""}`}
onClick={() => { onClick={() => {
setActiveModule("builder"); setActiveModule("builder");
setVersionHistoryVisible(false); setVersionHistoryVisible(false);
setToggleUI( setToggleUI(
localStorage.getItem("navBarUiLeft") localStorage.getItem("navBarUiLeft") ? localStorage.getItem("navBarUiLeft") === "true" : true,
? localStorage.getItem("navBarUiLeft") === "true" localStorage.getItem("navBarUiRight") ? localStorage.getItem("navBarUiRight") === "true" : true
: true, );
localStorage.getItem("navBarUiRight") }}
? localStorage.getItem("navBarUiRight") === "true" >
: true <div className="icon">
); <BuilderIcon isActive={activeModule === "builder"} />
}} </div>
> <div className="module">Builder</div>
<div className="icon"> </button>
<BuilderIcon isActive={activeModule === "builder"} /> <button
id={"simulation"}
className={`module-list ${activeModule === "simulation" ? "active" : ""}`}
onClick={() => {
setActiveModule("simulation");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft") ? localStorage.getItem("navBarUiLeft") === "true" : true,
localStorage.getItem("navBarUiRight") ? localStorage.getItem("navBarUiRight") === "true" : true
);
}}
>
<div className="icon">
<SimulationIcon isActive={activeModule === "simulation"} />
</div>
<div className="module">Simulation</div>
</button>
<button
id={"visualization"}
className={`module-list ${activeModule === "visualization" ? "active" : ""}`}
onClick={() => {
setActiveModule("visualization");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft") ? localStorage.getItem("navBarUiLeft") === "true" : true,
localStorage.getItem("navBarUiRight") ? localStorage.getItem("navBarUiRight") === "true" : true
);
}}
>
<div className="icon">
<VisualizationIcon isActive={activeModule === "visualization"} />
</div>
<div className="module">Visualization</div>
</button>
<button
id={"market"}
className={`module-list ${activeModule === "market" ? "active" : ""}`}
onClick={() => {
setActiveModule("market");
setVersionHistoryVisible(false);
setToggleUI(false, false);
}}
>
<div className="icon">
<CartIcon isActive={activeModule === "market"} />
</div>
<div className="module">Market Place</div>
</button>
</div> </div>
<div className="module">Builder</div> );
</button>
<button
id={"simulation"}
className={`module-list ${
activeModule === "simulation" ? "active" : ""
}`}
onClick={() => {
setActiveModule("simulation");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft")
? localStorage.getItem("navBarUiLeft") === "true"
: true,
localStorage.getItem("navBarUiRight")
? localStorage.getItem("navBarUiRight") === "true"
: true
);
}}
>
<div className="icon">
<SimulationIcon isActive={activeModule === "simulation"} />
</div>
<div className="module">Simulation</div>
</button>
<button
id={"visualization"}
className={`module-list ${
activeModule === "visualization" ? "active" : ""
}`}
onClick={() => {
setActiveModule("visualization");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft")
? localStorage.getItem("navBarUiLeft") === "true"
: true,
localStorage.getItem("navBarUiRight")
? localStorage.getItem("navBarUiRight") === "true"
: true
);
}}
>
<div className="icon">
<VisualizationIcon isActive={activeModule === "visualization"} />
</div>
<div className="module">Visualization</div>
</button>
<button
id={"market"}
className={`module-list ${activeModule === "market" ? "active" : ""}`}
onClick={() => {
setActiveModule("market");
setVersionHistoryVisible(false);
setToggleUI(false, false);
}}
>
<div className="icon">
<CartIcon isActive={activeModule === "market"} />
</div>
<div className="module">Market Place</div>
</button>
</div>
);
}; };
export default ModuleToggle; export default ModuleToggle;

View File

@@ -6,7 +6,7 @@ import { handleSaveTemplate } from "../../modules/visualization/functions/handle
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore"; import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
import useTemplateStore from "../../store/ui/useTemplateStore"; import useTemplateStore from "../../store/ui/useTemplateStore";
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
import { useActiveTool, useAddAction, useSocketStore, useToggleView, useToolMode, useActiveSubTool, useShortcutStore } from "../../store/builder/store"; import { useActiveTool, useSocketStore, useToggleView, useToolMode, useActiveSubTool, useShortcutStore } from "../../store/builder/store";
import { useToggleStore } from "../../store/ui/useUIToggleStore"; import { useToggleStore } from "../../store/ui/useUIToggleStore";
import { use3DWidget, useFloatingWidget } from "../../store/visualization/useDroppedObjectsStore"; import { use3DWidget, useFloatingWidget } from "../../store/visualization/useDroppedObjectsStore";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
@@ -34,7 +34,7 @@ const Tools: React.FC = () => {
const { toggleThreeD, setToggleThreeD } = useThreeDStore(); const { toggleThreeD, setToggleThreeD } = useThreeDStore();
const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { showShortcuts } = useShortcutStore(); const { showShortcuts } = useShortcutStore();
const { activeTool, setActiveTool, setToolMode, setAddAction } = useStoreHooks(); const { activeTool, setActiveTool, setToolMode } = useStoreHooks();
const { setSelectedWallAsset } = useBuilderStore(); const { setSelectedWallAsset } = useBuilderStore();
const { setActiveSubTool, activeSubTool } = useActiveSubTool(); const { setActiveSubTool, activeSubTool } = useActiveSubTool();
const { setToggleUI } = useToggleStore(); const { setToggleUI } = useToggleStore();
@@ -86,7 +86,6 @@ const Tools: React.FC = () => {
const resetTools = () => { const resetTools = () => {
setToolMode(null); setToolMode(null);
setAddAction(null);
}; };
const updateToolBehavior = (tool: string, is2D: boolean) => { const updateToolBehavior = (tool: string, is2D: boolean) => {
@@ -116,7 +115,7 @@ const Tools: React.FC = () => {
setToolMode("MeasurementScale"); setToolMode("MeasurementScale");
break; break;
case "Add pillar": case "Add pillar":
if (!is2D) setAddAction("Pillar"); if (!is2D) setToolMode("Pillar");
break; break;
case "delete": case "delete":
is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete"); is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete");
@@ -131,7 +130,6 @@ const Tools: React.FC = () => {
setToggleUI(toggleTo2D, toggleTo2D); setToggleUI(toggleTo2D, toggleTo2D);
if (toggleTo2D) { if (toggleTo2D) {
setSelectedWallAsset(null); setSelectedWallAsset(null);
setAddAction(null);
} }
setActiveTool("cursor"); setActiveTool("cursor");
setActiveSubTool("cursor"); setActiveSubTool("cursor");
@@ -242,7 +240,19 @@ const Tools: React.FC = () => {
<div className={`tools-container ${showShortcuts ? "visible" : ""}`}> <div className={`tools-container ${showShortcuts ? "visible" : ""}`}>
<div className="activeDropicon"> <div className="activeDropicon">
{/* Tool Picker (cursor, delete, etc.) */} {/* Tool Picker (cursor, delete, etc.) */}
{["cursor", "free-hand", "delete"].map((tool) => activeSubTool === tool && <ToolButton key={tool} toolId={tool} icon={getIconByTool(tool)} tooltip={`${tool} (${getTooltipShortcut(tool)})`} active={activeTool === tool} onClick={() => setActiveTool(tool)} />)} {["cursor", "free-hand", "delete"].map(
(tool) =>
activeSubTool === tool && (
<ToolButton
key={tool}
toolId={tool}
icon={getIconByTool(tool)}
tooltip={`${tool} (${getTooltipShortcut(tool)})`}
active={activeTool === tool}
onClick={() => setActiveTool(tool)}
/>
)
)}
{/* Dropdown Menu */} {/* Dropdown Menu */}
{activeModule !== "visualization" && ( {activeModule !== "visualization" && (
<button id="drop-down-button" title="drop-down" className="drop-down-option-button" ref={dropdownRef} onClick={() => setOpenDrop(!openDrop)}> <button id="drop-down-button" title="drop-down" className="drop-down-option-button" ref={dropdownRef} onClick={() => setOpenDrop(!openDrop)}>
@@ -308,7 +318,6 @@ const useStoreHooks = () => {
return { return {
...useActiveTool(), ...useActiveTool(),
...useToolMode(), ...useToolMode(),
...useAddAction(),
}; };
}; };

View File

@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { ArrowIcon } from "../../icons/ExportCommonIcons"; import { ArrowIcon } from "../../icons/ExportCommonIcons";
import { toggleTheme } from "../../../utils/theme"; import { toggleTheme } from "../../../utils/theme";
import useVersionHistoryVisibleStore, { useShortcutStore } from "../../../store/builder/store"; import { useShortcutStore } from "../../../store/builder/store";
import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore"; import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore";
import { useSceneContext } from "../../../modules/scene/sceneContext"; import { useSceneContext } from "../../../modules/scene/sceneContext";
@@ -25,8 +25,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>({}); const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>({});
const { versionStore } = useSceneContext(); const { versionStore } = useSceneContext();
const { setCreateNewVersion } = versionStore(); const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { setActiveModule } = useModuleStore(); const { setActiveModule } = useModuleStore();
const { setSubModule } = useSubModuleStore(); const { setSubModule } = useSubModuleStore();
const { showShortcuts, setShowShortcuts } = useShortcutStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore();
@@ -79,7 +78,16 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
{ label: "Import" }, { label: "Import" },
{ label: "Close File" }, { label: "Close File" },
], ],
Edit: [{ label: "Undo", shortcut: "Ctrl + Z" }, { label: "Redo", shortcut: "Ctrl + Shift + Z" }, { label: "Undo History" }, { label: "Redo History" }, { label: "Find", shortcut: "Ctrl + F" }, { label: "Delete" }, { label: "Select by..." }, { label: "Keymap" }], Edit: [
{ label: "Undo", shortcut: "Ctrl + Z" },
{ label: "Redo", shortcut: "Ctrl + Shift + Z" },
{ label: "Undo History" },
{ label: "Redo History" },
{ label: "Find", shortcut: "Ctrl + F" },
{ label: "Delete" },
{ label: "Select by..." },
{ label: "Keymap" },
],
View: [ View: [
{ label: "Grid" }, { label: "Grid" },
{ {
@@ -172,7 +180,13 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
<div className="dropdown-menu"> <div className="dropdown-menu">
{items.map((item) => {items.map((item) =>
item.submenu ? ( item.submenu ? (
<button id={item.label} key={item.label} className="menu-item-container" onMouseEnter={() => setActiveSubMenu(item.label)} onMouseLeave={() => setActiveSubMenu(null)}> <button
id={item.label}
key={item.label}
className="menu-item-container"
onMouseEnter={() => setActiveSubMenu(item.label)}
onMouseLeave={() => setActiveSubMenu(null)}
>
<div className="menu-item"> <div className="menu-item">
<span>{item.label}</span> <span>{item.label}</span>
<span className="dropdown-icon"> <span className="dropdown-icon">

View File

@@ -19,7 +19,7 @@ function AisleCreator() {
const { activeLayer } = useActiveLayer(); const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext(); const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addAisle, getAislePointById } = aisleStore(); const { addAisle, getAislePointById, getConnectedPoints } = aisleStore();
const { push2D } = undoRedo2DStore(); const { push2D } = undoRedo2DStore();
const drag = useRef(false); const drag = useRef(false);
const isLeftMouseDown = useRef(false); const isLeftMouseDown = useRef(false);
@@ -76,7 +76,9 @@ function AisleCreator() {
newPoint.layer = snappedPoint.layer; newPoint.layer = snappedPoint.layer;
} }
if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { const connectedPoints = getConnectedPoints(tempPoints[0]?.pointUuid || "");
if (snappedPoint && (snappedPoint.pointUuid === tempPoints[0]?.pointUuid || connectedPoints.some((point) => point.pointUuid === snappedPoint.pointUuid))) {
return; return;
} }
@@ -194,7 +196,32 @@ function AisleCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addAisle, getAislePointById, aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint, selectedVersion?.versionId]); }, [
gl,
camera,
scene,
raycaster,
pointer,
plane,
toggleView,
toolMode,
activeLayer,
socket,
tempPoints,
isCreating,
addAisle,
getAislePointById,
aisleType,
aisleWidth,
aisleColor,
dashLength,
gapLength,
dotRadius,
aisleLength,
snappedPosition,
snappedPoint,
selectedVersion?.versionId,
]);
return ( return (
<> <>

View File

@@ -7,7 +7,7 @@ import * as CONSTANTS from "../../../../types/world/worldConstants";
import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator"; import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator";
import { getUserData } from "../../../../functions/getUserData"; import { getUserData } from "../../../../functions/getUserData";
import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
async function addAssetModel( async function addAssetModel(
scene: THREE.Scene, scene: THREE.Scene,
@@ -63,13 +63,12 @@ async function addAssetModel(
} }
if (intersectPoint) { if (intersectPoint) {
if (intersectPoint.y < 0) { if (intersectPoint.y < 0) {
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z); intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
} }
const cachedModel = THREE.Cache.get(selectedItem.id); const cachedModel = THREE.Cache.get(selectedItem.id);
if (cachedModel) { if (cachedModel) {
handleModelLoad(cachedModel, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId); handleModelLoad(cachedModel, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || "", projectId, userId);
return; return;
} else { } else {
const cachedModelBlob = await retrieveGLTF(selectedItem.id); const cachedModelBlob = await retrieveGLTF(selectedItem.id);
@@ -79,21 +78,19 @@ async function addAssetModel(
URL.revokeObjectURL(blobUrl); URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl); THREE.Cache.remove(blobUrl);
THREE.Cache.add(selectedItem.id, gltf); THREE.Cache.add(selectedItem.id, gltf);
handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId); handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || "", projectId, userId);
}); });
} else { } else {
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, async (gltf) => {
async (gltf) => { const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob()); await storeGLTF(selectedItem.id, modelBlob);
await storeGLTF(selectedItem.id, modelBlob); THREE.Cache.add(selectedItem.id, gltf);
THREE.Cache.add(selectedItem.id, gltf); await handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || "", projectId, userId);
await handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId); });
}
);
} }
} }
} }
} catch (error) { } catch {
echo.error("Failed to add asset"); echo.error("Failed to add asset");
} finally { } finally {
setSelectedItem({}); setSelectedItem({});
@@ -118,7 +115,7 @@ async function handleModelLoad(
modelId: selectedItem.id, modelId: selectedItem.id,
modelUuid: model.uuid, modelUuid: model.uuid,
}; };
model.position.set(intersectPoint!.x, intersectPoint!.y, intersectPoint!.z); model.position.set(intersectPoint.x, intersectPoint.y, intersectPoint.z);
model.scale.set(...CONSTANTS.assetConfig.defaultScaleAfterGsap); model.scale.set(...CONSTANTS.assetConfig.defaultScaleAfterGsap);
model.traverse((child: any) => { model.traverse((child: any) => {
@@ -132,7 +129,7 @@ async function handleModelLoad(
modelUuid: model.uuid, modelUuid: model.uuid,
modelName: selectedItem.name, modelName: selectedItem.name,
assetId: selectedItem.id, assetId: selectedItem.id,
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z], position: [intersectPoint.x, intersectPoint.y, intersectPoint.z],
rotation: [0, 0, 0], rotation: [0, 0, 0],
isLocked: false, isLocked: false,
isVisible: true, isVisible: true,
@@ -141,13 +138,9 @@ async function handleModelLoad(
}; };
if (selectedItem.type) { if (selectedItem.type) {
const data = PointsCalculator( const data = PointsCalculator(selectedItem.type, gltf.scene.clone(), new THREE.Vector3(...model.rotation));
selectedItem.type,
gltf.scene.clone(),
new THREE.Vector3(...model.rotation)
);
if (!data || !data.points) return; if (!data?.points) return;
const eventData: any = { type: selectedItem.type, subType: selectedItem.subType }; const eventData: any = { type: selectedItem.type, subType: selectedItem.subType };
@@ -159,7 +152,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation, rotation: newFloorItem.rotation,
state: "idle", state: "idle",
type: "transfer", type: "transfer",
subType: selectedItem.subType || '', subType: selectedItem.subType || "",
speed: 1, speed: 1,
points: data.points.map((point: THREE.Vector3, index: number) => { points: data.points.map((point: THREE.Vector3, index: number) => {
const triggers: TriggerSchema[] = []; const triggers: TriggerSchema[] = [];
@@ -228,7 +221,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation, rotation: newFloorItem.rotation,
state: "idle", state: "idle",
type: "vehicle", type: "vehicle",
subType: selectedItem.subType || '', subType: selectedItem.subType || "",
speed: 1, speed: 1,
point: { point: {
uuid: THREE.MathUtils.generateUUID(), uuid: THREE.MathUtils.generateUUID(),
@@ -266,7 +259,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation, rotation: newFloorItem.rotation,
state: "idle", state: "idle",
type: "roboticArm", type: "roboticArm",
subType: selectedItem.subType || '', subType: selectedItem.subType || "",
speed: 1, speed: 1,
point: { point: {
uuid: THREE.MathUtils.generateUUID(), uuid: THREE.MathUtils.generateUUID(),
@@ -300,7 +293,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation, rotation: newFloorItem.rotation,
state: "idle", state: "idle",
type: "machine", type: "machine",
subType: selectedItem.subType || '', subType: selectedItem.subType || "",
point: { point: {
uuid: THREE.MathUtils.generateUUID(), uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z], position: [data.points[0].x, data.points[0].y, data.points[0].z],
@@ -332,7 +325,7 @@ async function handleModelLoad(
storageCapacity: 10, storageCapacity: 10,
storageCount: 10, storageCount: 10,
materialType: "Default material", materialType: "Default material",
subType: selectedItem.subType || '', subType: selectedItem.subType || "",
point: { point: {
uuid: THREE.MathUtils.generateUUID(), uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z], position: [data.points[0].x, data.points[0].y, data.points[0].z],
@@ -343,7 +336,7 @@ async function handleModelLoad(
actionName: "Action 1", actionName: "Action 1",
actionType: "store", actionType: "store",
triggers: [], triggers: [],
} },
], ],
}, },
}; };
@@ -361,7 +354,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation, rotation: newFloorItem.rotation,
state: "idle", state: "idle",
type: "human", type: "human",
subType: selectedItem.subType || '', subType: selectedItem.subType || "",
speed: 1, speed: 1,
point: { point: {
uuid: THREE.MathUtils.generateUUID(), uuid: THREE.MathUtils.generateUUID(),
@@ -375,23 +368,23 @@ async function handleModelLoad(
loadCount: 1, loadCount: 1,
assemblyCount: 1, assemblyCount: 1,
assemblyCondition: { assemblyCondition: {
conditionType: 'material', conditionType: "material",
materialType: "Default material" materialType: "Default material",
}, },
manufactureCount: 1, manufactureCount: 1,
loadCapacity: 1, loadCapacity: 1,
processTime: 10, processTime: 10,
triggers: [] triggers: [],
} },
] ],
} },
} };
addEvent(humanEvent); addEvent(humanEvent);
eventData.point = { eventData.point = {
uuid: humanEvent.point.uuid, uuid: humanEvent.point.uuid,
position: humanEvent.point.position, position: humanEvent.point.position,
rotation: humanEvent.point.rotation, rotation: humanEvent.point.rotation,
} };
} else if (selectedItem.type === "Crane") { } else if (selectedItem.type === "Crane") {
const craneEvent: CraneEventSchema = { const craneEvent: CraneEventSchema = {
modelUuid: newFloorItem.modelUuid, modelUuid: newFloorItem.modelUuid,
@@ -400,7 +393,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation, rotation: newFloorItem.rotation,
state: "idle", state: "idle",
type: "crane", type: "crane",
subType: selectedItem.subType || '', subType: selectedItem.subType || "",
point: { point: {
uuid: THREE.MathUtils.generateUUID(), uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z], position: [data.points[0].x, data.points[0].y, data.points[0].z],
@@ -411,17 +404,17 @@ async function handleModelLoad(
actionName: "Action 1", actionName: "Action 1",
actionType: "pickAndDrop", actionType: "pickAndDrop",
maxPickUpCount: 1, maxPickUpCount: 1,
triggers: [] triggers: [],
} },
] ],
} },
} };
addEvent(craneEvent); addEvent(craneEvent);
eventData.point = { eventData.point = {
uuid: craneEvent.point.uuid, uuid: craneEvent.point.uuid,
position: craneEvent.point.position, position: craneEvent.point.position,
rotation: craneEvent.point.rotation, rotation: craneEvent.point.rotation,
} };
} }
const completeData = { const completeData = {
@@ -430,10 +423,10 @@ async function handleModelLoad(
modelName: newFloorItem.modelName, modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId, assetId: newFloorItem.assetId,
position: newFloorItem.position, position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false, isLocked: false,
isVisible: true, isVisible: true,
socketId: socket?.id || '', socketId: socket?.id || "",
eventData: eventData, eventData: eventData,
versionId: versionId, versionId: versionId,
projectId: projectId, projectId: projectId,
@@ -448,14 +441,13 @@ async function handleModelLoad(
modelName: newFloorItem.modelName, modelName: newFloorItem.modelName,
position: newFloorItem.position, position: newFloorItem.position,
assetId: newFloorItem.assetId, assetId: newFloorItem.assetId,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
eventData: eventData, eventData: eventData,
isLocked: false, isLocked: false,
isVisible: true, isVisible: true,
versionId: versionId, versionId: versionId,
projectId: projectId, projectId: projectId,
}); });
} else { } else {
// SOCKET // SOCKET
@@ -467,7 +459,7 @@ async function handleModelLoad(
modelName: completeData.modelName, modelName: completeData.modelName,
assetId: completeData.assetId, assetId: completeData.assetId,
position: completeData.position, position: completeData.position,
rotation: [completeData.rotation.x, completeData.rotation.y, completeData.rotation.z,] as [number, number, number], rotation: [completeData.rotation.x, completeData.rotation.y, completeData.rotation.z] as [number, number, number],
isLocked: completeData.isLocked, isLocked: completeData.isLocked,
isCollidable: false, isCollidable: false,
isVisible: completeData.isVisible, isVisible: completeData.isVisible,
@@ -483,10 +475,10 @@ async function handleModelLoad(
modelName: newFloorItem.modelName, modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId, assetId: newFloorItem.assetId,
position: newFloorItem.position, position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false, isLocked: false,
isVisible: true, isVisible: true,
socketId: socket?.id || '', socketId: socket?.id || "",
versionId: versionId, versionId: versionId,
projectId: projectId, projectId: projectId,
userId: userId, userId: userId,
@@ -500,13 +492,12 @@ async function handleModelLoad(
modelName: newFloorItem.modelName, modelName: newFloorItem.modelName,
position: newFloorItem.position, position: newFloorItem.position,
assetId: newFloorItem.assetId, assetId: newFloorItem.assetId,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false, isLocked: false,
isVisible: true, isVisible: true,
versionId: versionId, versionId: versionId,
projectId: projectId, projectId: projectId,
}); });
} else { } else {
// SOCKET // SOCKET

View File

@@ -17,7 +17,6 @@ import SocketResponses from "../collaboration/socket/socketResponses.dev";
import Ground from "../scene/environment/ground"; import Ground from "../scene/environment/ground";
import MeasurementTool from "../scene/tools/measurementTool"; import MeasurementTool from "../scene/tools/measurementTool";
import NavMesh from "../simulation/vehicle/navMesh/navMesh"; import NavMesh from "../simulation/vehicle/navMesh/navMesh";
import CalculateAreaGroup from "./groups/calculateAreaGroup";
import LayoutImage from "./layout/layoutImage"; import LayoutImage from "./layout/layoutImage";
import AssetsGroup from "./asset/assetsGroup"; import AssetsGroup from "./asset/assetsGroup";
import DxfFile from "./dfx/LoadBlueprint"; import DxfFile from "./dfx/LoadBlueprint";
@@ -51,8 +50,8 @@ export default function Builder() {
if (!toggleView) { if (!toggleView) {
setHoveredLine(null); setHoveredLine(null);
setHoveredPoint(null); setHoveredPoint(null);
state.gl.domElement.style.cursor = 'default'; state.gl.domElement.style.cursor = "default";
setToolMode('cursor'); setToolMode("cursor");
} }
}, [toggleView]); }, [toggleView]);
@@ -65,14 +64,14 @@ export default function Builder() {
setShadows(data.shadowVisibility); setShadows(data.shadowVisibility);
setRenderDistance(data.renderDistance); setRenderDistance(data.renderDistance);
setLimitDistance(data.limitDistance); setLimitDistance(data.limitDistance);
}) });
}, [projectId]); }, [projectId]);
useFrame(() => { useFrame(() => {
if (csgRef.current && selectedWallAsset) { if (csgRef.current && selectedWallAsset) {
csgRef.current.update(); csgRef.current.update();
} }
}) });
return ( return (
<> <>
@@ -82,13 +81,11 @@ export default function Builder() {
<AssetsGroup plane={plane} /> <AssetsGroup plane={plane} />
<mesh name='Walls-And-WallAssets-Group'> <mesh name="Walls-And-WallAssets-Group">
<Geometry ref={csgRef} useGroups> <Geometry ref={csgRef} useGroups>
<WallGroup /> <WallGroup />
<WallAssetGroup /> <WallAssetGroup />
</Geometry> </Geometry>
</mesh> </mesh>
@@ -102,8 +99,6 @@ export default function Builder() {
<MeasurementTool /> <MeasurementTool />
<CalculateAreaGroup />
<NavMesh /> <NavMesh />
<DxfFile /> <DxfFile />

View File

@@ -1,50 +1,61 @@
import { useMemo } from 'react'; import { useMemo } from "react";
import { DoubleSide, Shape, Vector2 } from 'three'; import { DoubleSide, Shape, Vector2 } from "three";
import { Extrude } from '@react-three/drei'; import { Extrude, Html } from "@react-three/drei";
import * as Constants from '../../../../../types/world/worldConstants'; import * as Constants from "../../../../../types/world/worldConstants";
import getCenteroidPoint from "../../../functions/getCenteroid";
import getArea from "../../../functions/getArea";
function Floor2DInstance({ floor }: { readonly floor: Floor }) { function Floor2DInstance({ floor }: { readonly floor: Floor }) {
const savedTheme: string | null = localStorage.getItem("theme"); const savedTheme: string | null = localStorage.getItem("theme");
const points2D = useMemo(() => {
return floor.points.map((p) => new Vector2(parseFloat(p.position[0].toFixed(2)), parseFloat(p.position[2].toFixed(2))));
}, [floor]);
const shape = useMemo(() => { const shape = useMemo(() => {
const shape = new Shape(); const shape = new Shape();
const points = floor.points.map(p => new Vector2(p.position[0], p.position[2])); shape.moveTo(points2D[0].x, points2D[0].y);
if (points.length < 3) return null; for (let i = 1; i < points2D.length; i++) {
shape.moveTo(points[0].x, points[0].y); shape.lineTo(points2D[i].x, points2D[i].y);
for (let i = 1; i < points.length; i++) {
shape.lineTo(points[i].x, points[i].y);
} }
shape.lineTo(points2D[0].x, points2D[0].y);
return shape; return shape;
}, [floor]); }, [points2D]);
const area = useMemo(() => getArea(points2D), [points2D]);
const centroid: [number, number, number] = useMemo(() => {
const center = getCenteroidPoint(points2D);
if (!center) return [0, Constants.floorConfig.height + 0.01, 0];
return [center.x, Constants.floorConfig.height + 0.01, center.y] as [number, number, number];
}, [points2D]);
if (!shape) return null; if (!shape) return null;
const formattedArea = `${area.toFixed(2)}`;
return ( return (
<mesh <>
castShadow <mesh castShadow receiveShadow name={`Floor-2D-${floor.floorUuid}`} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]} userData={floor}>
receiveShadow <Extrude name={`Floor-${floor.floorUuid}`} args={[shape, { depth: Constants.floorConfig.height }]} userData={floor}>
name={`Floor-2D-${floor.floorUuid}`} <meshBasicMaterial
rotation={[Math.PI / 2, 0, 0]} color={savedTheme === "dark" ? Constants.lineConfig.floorColor : Constants.lineConfig.floorColor}
position={[0, 0, 0]} side={DoubleSide}
userData={floor} transparent
> opacity={0.4}
<Extrude depthWrite={false}
name={`Floor-${floor.floorUuid}`} />
args={[shape, { </Extrude>
depth: Constants.floorConfig.height, </mesh>
}]}
userData={floor} <Html key={floor.floorUuid} position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
> <div className="distance area">
<meshBasicMaterial {floor.floorName} ({formattedArea})
color={savedTheme === "dark" ? "#808080" : "#808080"} </div>
side={DoubleSide} </Html>
transparent </>
opacity={0.4}
depthWrite={false}
/>
</Extrude>
</mesh>
); );
} }
export default Floor2DInstance; export default Floor2DInstance;

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from "react";
import { Vector3 } from 'three'; import { Vector3 } from "three";
import { Html } from '@react-three/drei'; import { Html } from "@react-three/drei";
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from "../../../scene/sceneContext";
import { useToggleView, useToolMode } from '../../../../store/builder/store'; import { useToggleView, useToolMode } from "../../../../store/builder/store";
import Line from '../../line/line'; import Line from "../../line/line";
import Point from '../../point/point'; import Point from "../../point/point";
import FloorInstance from './Instance/floorInstance'; import FloorInstance from "./Instance/floorInstance";
import Floor2DInstance from './Instance/floor2DInstance'; import Floor2DInstance from "./Instance/floor2DInstance";
import useModuleStore from '../../../../store/ui/useModuleStore'; import useModuleStore from "../../../../store/ui/useModuleStore";
import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
function FloorInstances() { function FloorInstances() {
const { floorStore } = useSceneContext(); const { floorStore } = useSceneContext();
@@ -20,11 +20,11 @@ function FloorInstances() {
useEffect(() => { useEffect(() => {
// console.log('floors: ', floors); // console.log('floors: ', floors);
}, [floors]) }, [floors]);
useEffect(() => { useEffect(() => {
if (!toggleView && activeModule === 'builder') { if (!toggleView && activeModule === "builder") {
if (toolMode !== 'cursor') { if (toolMode !== "cursor") {
if (selectedFloor) setSelectedFloor(null); if (selectedFloor) setSelectedFloor(null);
} }
} else if (selectedFloor) { } else if (selectedFloor) {
@@ -36,8 +36,8 @@ function FloorInstances() {
const points: Point[] = []; const points: Point[] = [];
const seenUuids = new Set<string>(); const seenUuids = new Set<string>();
floors.forEach(floor => { floors.forEach((floor) => {
floor.points.forEach(point => { floor.points.forEach((point) => {
if (!seenUuids.has(point.pointUuid)) { if (!seenUuids.has(point.pointUuid)) {
seenUuids.add(point.pointUuid); seenUuids.add(point.pointUuid);
points.push(point); points.push(point);
@@ -65,7 +65,7 @@ function FloorInstances() {
lines.push({ lines.push({
start: current, start: current,
end: next, end: next,
key: lineKey key: lineKey,
}); });
} }
} }
@@ -76,9 +76,8 @@ function FloorInstances() {
return ( return (
<> <>
{!toggleView && floors.length > 0 && ( {!toggleView && floors.length > 0 && (
<mesh name='Floors-Group'> <mesh name="Floors-Group">
{floors.map((floor) => ( {floors.map((floor) => (
<FloorInstance key={floor.floorUuid} floor={floor} /> <FloorInstance key={floor.floorUuid} floor={floor} />
))} ))}
@@ -86,7 +85,7 @@ function FloorInstances() {
)} )}
{toggleView && floors.length > 0 && ( {toggleView && floors.length > 0 && (
<mesh name='Floors-2D-Group'> <mesh name="Floors-2D-Group">
{floors.map((floor) => ( {floors.map((floor) => (
<Floor2DInstance key={floor.floorUuid} floor={floor} /> <Floor2DInstance key={floor.floorUuid} floor={floor} />
))} ))}
@@ -95,14 +94,13 @@ function FloorInstances() {
{toggleView && ( {toggleView && (
<> <>
<group name='Floor-Points-Group'> <group name="Floor-Points-Group">
{allPoints.map((point) => ( {allPoints.map((point) => (
<Point key={point.pointUuid} point={point} /> <Point key={point.pointUuid} point={point} />
))} ))}
</group> </group>
<group name='Floor-Lines-Group'> <group name="Floor-Lines-Group">
{allLines.map(({ start, end, key }) => ( {allLines.map(({ start, end, key }) => (
<Line key={key} points={[start, end]} /> <Line key={key} points={[start, end]} />
))} ))}
@@ -114,7 +112,7 @@ function FloorInstances() {
return ( return (
<React.Fragment key={key}> <React.Fragment key={key}>
{toggleView && {toggleView && (
<Html <Html
key={`${start.pointUuid}_${end.pointUuid}`} key={`${start.pointUuid}_${end.pointUuid}`}
userData={line} userData={line}
@@ -125,24 +123,19 @@ function FloorInstances() {
prepend prepend
sprite sprite
> >
<div <div key={key} className={`distance ${key}`}>
key={key}
className={`distance ${key}`}
>
{distance.toFixed(2)} m {distance.toFixed(2)} m
</div> </div>
</Html> </Html>
} )}
</React.Fragment> </React.Fragment>
) );
})} })}
</group> </group>
</> </>
)} )}
</> </>
) );
} }
export default FloorInstances; export default FloorInstances;

View File

@@ -86,6 +86,10 @@ function FloorCreator() {
return; return;
} }
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) { if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition; newPoint.position = snappedPosition;
} }
@@ -288,7 +292,7 @@ function FloorCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]); }, [camera, raycaster, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
return ( return (
<> <>

View File

@@ -1,29 +0,0 @@
import { Vector2 } from "three";
export function computeArea(data: any, type: "zone" | "rooms" | "aisle"): any {
if (type === "zone") {
const points3D = data.map((p: any) => new Vector2(p[0], p[2]));
let area = 0;
for (let i = 0; i < points3D.length - 1; i++) {
const current = points3D[i];
const next = points3D[i + 1];
area += current.x * next.y - next.x * current.y;
}
return Math.abs(area) / 2;
}
if (type === "rooms") {
const points2D = data.coordinates.map(
(coordinate: any) =>
new Vector2(coordinate.position.x, coordinate.position.z)
);
let area = 0;
for (let i = 0; i < points2D.length - 1; i++) {
const current = points2D[i];
const next = points2D[i + 1];
area += current.x * next.y - next.x * current.y;
}
return Math.abs(area) / 2;
}
}

View File

@@ -0,0 +1,10 @@
import { Vector2 } from "three";
export default function getArea(points: Vector2[]): number {
let sum = 0;
for (let i = 0; i < points.length; i++) {
const j = (i + 1) % points.length;
sum += points[i].x * points[j].y - points[j].x * points[i].y;
}
return Math.abs(sum / 2);
}

View File

@@ -0,0 +1,48 @@
import { Vector2 } from "three";
export default function getCenteroidPoint(points: Vector2[]): Vector2 | null {
if (points.length < 3) return null;
let minZ = points[0].y;
let maxZ = points[0].y;
for (let i = 1; i < points.length; i++) {
minZ = Math.min(minZ, points[i].y);
maxZ = Math.max(maxZ, points[i].y);
}
const scanZ = minZ + (maxZ - minZ) * 0.51;
const intersections: number[] = [];
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % points.length];
if (p1.y === p2.y) continue;
const [top, bottom] = p1.y > p2.y ? [p1, p2] : [p2, p1];
if (scanZ >= bottom.y && scanZ < top.y) {
const t = (scanZ - bottom.y) / (top.y - bottom.y);
const x = bottom.x + t * (top.x - bottom.x);
intersections.push(x);
}
}
intersections.sort((a, b) => a - b);
if (intersections.length >= 2) {
const x = (intersections[0] + intersections[1]) / 2;
return new Vector2(x, scanZ);
}
let x = 0,
z = 0;
for (const p of points) {
x += p.x;
z += p.y;
}
x /= points.length;
z /= points.length;
return new Vector2(x, z);
}

View File

@@ -1,106 +0,0 @@
import { useRoomsState, useToggleView } from "../../../store/builder/store";
import { computeArea } from "../functions/computeArea";
import { Html } from "@react-three/drei";
import * as CONSTANTS from "../../../types/world/worldConstants";
import * as turf from "@turf/turf";
import * as THREE from "three";
const CalculateAreaGroup = () => {
const { roomsState } = useRoomsState();
const { toggleView } = useToggleView();
const savedTheme: string | null = localStorage.getItem("theme");
return (
<group name="roomArea" visible={toggleView}>
<group name="roomFills" visible={toggleView}>
{roomsState.length > 0 &&
roomsState.flat().map((room: any, index: number) => {
const coordinates = room.coordinates;
if (!coordinates || coordinates.length < 3) return null;
const coords2D = coordinates.map((p: any) => new THREE.Vector2(p.position.x, p.position.z));
if (!coords2D[0].equals(coords2D[coords2D.length - 1])) {
coords2D.push(coords2D[0]);
}
const shape = new THREE.Shape(coords2D);
const extrudeSettings = {
depth: 0.01,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
geometry.rotateX(Math.PI / 2);
const material = new THREE.MeshBasicMaterial({
color: savedTheme === "dark" ? "#d2baff" : "#6f42c1",
side: THREE.DoubleSide,
transparent: true,
opacity: 0.4,
depthWrite: false,
});
return (
<group key={`roomFill-${index}`}>
<mesh
geometry={geometry}
material={material}
position={[0, 0.12, 0]}
/>
</group>
);
})}
</group>
{roomsState.length > 0 &&
roomsState.flat().map((room: any, index: number) => {
if (!toggleView) return null;
const coordinates = room.coordinates;
if (!coordinates || coordinates.length < 3) return null;
let coords2D = coordinates.map((p: any) => [p.position.x, p.position.z,]);
const first = coords2D[0];
const last = coords2D[coords2D.length - 1];
if (first[0] !== last[0] || first[1] !== last[1]) {
coords2D.push(first);
}
const polygon = turf.polygon([coords2D]);
const center2D = turf.center(polygon).geometry.coordinates;
const sumY = coordinates.reduce((sum: number, p: any) => sum + p.position.y, 0);
const avgY = sumY / coordinates.length;
const area = computeArea(room, "rooms");
const formattedArea = `${area.toFixed(2)}`;
const htmlPosition: [number, number, number] = [
center2D[0],
avgY + CONSTANTS.zoneConfig.height,
center2D[1],
];
return (
<Html
key={`${index}-${room.layer || index}`}
position={htmlPosition}
wrapperClass="distance-text-wrapper"
className="distance-text"
zIndexRange={[1, 0]}
prepend
center
sprite
>
<div className={`distance area line-${room.layer || index}`}>
Room ({formattedArea})
</div>
</Html>
);
})}
</group>
);
};
export default CalculateAreaGroup;

View File

@@ -1,16 +1,16 @@
import * as THREE from 'three'; import * as THREE from "three";
import { useCallback } from 'react'; import { useCallback } from "react";
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from "../../../scene/sceneContext";
const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const CAN_POINT_SNAP = true; // Whether snapping is enabled or not const CAN_POINT_SNAP = true; // Whether snapping is enabled or not
const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not
export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => { export const usePointSnapping = (currentPoint: { uuid: string; pointType: string; position: [number, number, number] } | null) => {
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore(); const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore();
const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore(); const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore();
@@ -21,350 +21,359 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
const getAllOtherWallPoints = useCallback(() => { const getAllOtherWallPoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return walls.flatMap(wall => return walls.flatMap((wall) => wall.points.filter((point) => point.pointUuid !== currentPoint.uuid));
wall.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [walls, currentPoint]); }, [walls, currentPoint]);
const snapWallPoint = useCallback((position: [number, number, number], tempPoints?: Point) => { const snapWallPoint = useCallback(
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; (position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherWallPoints(); const otherPoints = getAllOtherWallPoints();
if (tempPoints) { const currentVec = new THREE.Vector3(...position);
otherPoints.push(tempPoints); for (const point of otherPoints) {
} const pointVec = new THREE.Vector3(...point.position);
const currentVec = new THREE.Vector3(...position); const distance = currentVec.distanceTo(pointVec);
for (const point of otherPoints) { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Wall") {
const pointVec = new THREE.Vector3(...point.position); return { position: point.position, isSnapped: true, snappedPoint: point };
const distance = currentVec.distanceTo(pointVec); }
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Wall') {
return { position: point.position, isSnapped: true, snappedPoint: point };
} }
} return { position: position, isSnapped: false, snappedPoint: null };
return { position: position, isSnapped: false, snappedPoint: null }; },
}, [currentPoint, getAllOtherWallPoints]); [currentPoint, getAllOtherWallPoints]
);
const snapWallAngle = useCallback((newPosition: [number, number, number]): { const snapWallAngle = useCallback(
position: [number, number, number], (newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
isSnapped: boolean, if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
snapSources: THREE.Vector3[]
} => { const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: [],
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { return {
position: newPosition, position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped: false, isSnapped,
snapSources: [] snapSources,
}; };
} },
[currentPoint, getConnectedAislePoints]
const newPos = new THREE.Vector3(...newPosition); );
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedAislePoints]);
// Aisle Snapping // Aisle Snapping
const getAllOtherAislePoints = useCallback(() => { const getAllOtherAislePoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return aisles.flatMap(aisle => return aisles.flatMap((aisle) => aisle.points.filter((point) => point.pointUuid !== currentPoint.uuid));
aisle.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [aisles, currentPoint]); }, [aisles, currentPoint]);
const snapAislePoint = useCallback((position: [number, number, number]) => { const snapAislePoint = useCallback(
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; (position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherAislePoints(); const otherPoints = getAllOtherAislePoints();
const currentVec = new THREE.Vector3(...position); const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) { for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position); const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec); const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Aisle') { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Aisle") {
return { position: point.position, isSnapped: true, snappedPoint: point }; return { position: point.position, isSnapped: true, snappedPoint: point };
}
} }
}
return { position: position, isSnapped: false, snappedPoint: null }; return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherAislePoints]); },
[currentPoint, getAllOtherAislePoints]
);
const snapAisleAngle = useCallback((newPosition: [number, number, number]): { const snapAisleAngle = useCallback(
position: [number, number, number], (newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
isSnapped: boolean, if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
snapSources: THREE.Vector3[]
} => { const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: [],
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { return {
position: newPosition, position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped: false, isSnapped,
snapSources: [] snapSources,
}; };
} },
[currentPoint, getConnectedAislePoints]
const newPos = new THREE.Vector3(...newPosition); );
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedAislePoints]);
// Floor Snapping // Floor Snapping
const getAllOtherFloorPoints = useCallback(() => { const getAllOtherFloorPoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return floors.flatMap(floor => return floors.flatMap((floor) => floor.points.filter((point) => point.pointUuid !== currentPoint.uuid));
floor.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [floors, currentPoint]); }, [floors, currentPoint]);
const snapFloorPoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => { const snapFloorPoint = useCallback(
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; (position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherFloorPoints(); let otherPoints = getAllOtherFloorPoints();
if (tempPoints) { if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints]; otherPoints = [...otherPoints, ...tempPoints];
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Floor') {
return { position: point.position, isSnapped: true, snappedPoint: point };
} }
} const currentVec = new THREE.Vector3(...position);
return { position: position, isSnapped: false, snappedPoint: null }; for (const point of otherPoints) {
}, [currentPoint, getAllOtherFloorPoints]); const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
const snapFloorAngle = useCallback((newPosition: [number, number, number]): { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Floor") {
position: [number, number, number], return { position: point.position, isSnapped: true, snappedPoint: point };
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedFloorPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
} }
} }
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { return { position: position, isSnapped: false, snappedPoint: null };
if (!closestZ || zDist < closestZ.dist) { },
closestZ = { pos: cPos, dist: zDist }; [currentPoint, getAllOtherFloorPoints]
);
const snapFloorAngle = useCallback(
(
newPosition: [number, number, number]
): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedFloorPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
} }
} }
}
const snappedPos = newPos.clone(); const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = []; const snapSources: THREE.Vector3[] = [];
if (closestX) { if (closestX) {
snappedPos.x = closestX.pos.x; snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone()); snapSources.push(closestX.pos.clone());
} }
if (closestZ) { if (closestZ) {
snappedPos.z = closestZ.pos.z; snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone()); snapSources.push(closestZ.pos.clone());
} }
const isSnapped = snapSources.length > 0; const isSnapped = snapSources.length > 0;
return { return {
position: [snappedPos.x, snappedPos.y, snappedPos.z], position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped, isSnapped,
snapSources snapSources,
}; };
}, [currentPoint, getConnectedFloorPoints]); },
[currentPoint, getConnectedFloorPoints]
);
// Zone Snapping // Zone Snapping
const getAllOtherZonePoints = useCallback(() => { const getAllOtherZonePoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return zones.flatMap(zone => return zones.flatMap((zone) => zone.points.filter((point) => point.pointUuid !== currentPoint.uuid));
zone.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
}, [zones, currentPoint]); }, [zones, currentPoint]);
const snapZonePoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => { const snapZonePoint = useCallback(
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; (position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherZonePoints(); let otherPoints = getAllOtherZonePoints();
if (tempPoints) { if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints]; otherPoints = [...otherPoints, ...tempPoints];
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Zone') {
return { position: point.position, isSnapped: true, snappedPoint: point };
} }
} const currentVec = new THREE.Vector3(...position);
return { position: position, isSnapped: false, snappedPoint: null }; for (const point of otherPoints) {
}, [currentPoint, getAllOtherZonePoints]); const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
const snapZoneAngle = useCallback((newPosition: [number, number, number]): { if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Zone") {
position: [number, number, number], return { position: point.position, isSnapped: true, snappedPoint: point };
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedZonePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
} }
} }
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { return { position: position, isSnapped: false, snappedPoint: null };
if (!closestZ || zDist < closestZ.dist) { },
closestZ = { pos: cPos, dist: zDist }; [currentPoint, getAllOtherZonePoints]
);
const snapZoneAngle = useCallback(
(
newPosition: [number, number, number]
): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedZonePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
} }
} }
}
const snappedPos = newPos.clone(); const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = []; const snapSources: THREE.Vector3[] = [];
if (closestX) { if (closestX) {
snappedPos.x = closestX.pos.x; snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone()); snapSources.push(closestX.pos.clone());
} }
if (closestZ) { if (closestZ) {
snappedPos.z = closestZ.pos.z; snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone()); snapSources.push(closestZ.pos.clone());
} }
const isSnapped = snapSources.length > 0; const isSnapped = snapSources.length > 0;
return { return {
position: [snappedPos.x, snappedPos.y, snappedPos.z], position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped, isSnapped,
snapSources snapSources,
}; };
}, [currentPoint, getConnectedZonePoints]); },
[currentPoint, getConnectedZonePoints]
);
return { return {
snapAislePoint, snapAislePoint,
@@ -376,4 +385,4 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
snapZonePoint, snapZonePoint,
snapZoneAngle, snapZoneAngle,
}; };
}; };

View File

@@ -1,151 +1,73 @@
import { useMemo } from 'react'; import { useMemo } from "react";
import * as turf from '@turf/turf'; import * as turf from "@turf/turf";
export function useWallClassification(walls: Walls) { export function useWallClassification(walls: Walls) {
const findRooms = () => { const findRooms = () => {
if (walls.length < 3) return []; if (walls.length < 3) return [];
// Map pointUuid to list of connected line segments const wallSet = new Map<string, Wall>();
const pointMap = new Map<string, Wall[]>(); const positionPairSet = new Set<string>();
const makeKey = (p1: Point, p2: Point) => {
return [p1.pointUuid, p2.pointUuid].sort((a, b) => a.localeCompare(b)).join("-");
};
const makePositionPairKey = (p1: Point, p2: Point) => {
const sortedPositions = [
[p1.position[0].toFixed(6), p1.position[2].toFixed(6)],
[p2.position[0].toFixed(6), p2.position[2].toFixed(6)],
].sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));
return sortedPositions.map((pos) => pos.join(",")).join("|");
};
for (const wall of walls) { for (const wall of walls) {
for (const point of wall.points) { const [p1, p2] = wall.points;
const list = pointMap.get(point.pointUuid) || [];
list.push(wall); if (p1.pointUuid === p2.pointUuid || (p1.position[0] === p2.position[0] && p1.position[1] === p2.position[1] && p1.position[2] === p2.position[2])) {
pointMap.set(point.pointUuid, list); continue;
}
const positionPairKey = makePositionPairKey(p1, p2);
if (positionPairSet.has(positionPairKey)) {
continue;
}
positionPairSet.add(positionPairKey);
const key = makeKey(p1, p2);
if (!wallSet.has(key)) {
wallSet.set(key, wall);
} }
} }
// Create graph of connected walls using pointUuid const uniqueWalls = Array.from(wallSet.values());
const visited = new Set<string>();
const mergedLineStrings = [];
const wallKey = (p1: Point, p2: Point) => `${p1.pointUuid}-${p2.pointUuid}`; const lineStrings = uniqueWalls.map((wall) => {
const coords: [number, number][] = wall.points.map((p) => [p.position[0], p.position[2]]);
return turf.lineString(coords, { wallUuid: wall.wallUuid });
});
for (const wall of walls) { const collection = turf.featureCollection(lineStrings);
const key = wallKey(wall.points[0], wall.points[1]);
if (visited.has(key) || visited.has(wallKey(wall.points[1], wall.points[0]))) continue;
let line: Point[] = [...wall.points]; const polygons = turf.polygonize(collection);
visited.add(key);
// Try to extend the line forward and backward by matching endpoints
let extended = true;
while (extended) {
extended = false;
const last = line[line.length - 1];
const nextWalls = pointMap.get(last.pointUuid) || [];
for (const next of nextWalls) {
const [n1, n2] = next.points;
const nextKey = wallKey(n1, n2);
if (visited.has(nextKey) || visited.has(wallKey(n2, n1))) continue;
if (n1.pointUuid === last.pointUuid && n2.pointUuid !== line[line.length - 2]?.pointUuid) {
line.push(n2);
visited.add(nextKey);
extended = true;
break;
} else if (n2.pointUuid === last.pointUuid && n1.pointUuid !== line[line.length - 2]?.pointUuid) {
line.push(n1);
visited.add(nextKey);
extended = true;
break;
}
}
const first = line[0];
const prevWalls = pointMap.get(first.pointUuid) || [];
for (const prev of prevWalls) {
const [p1, p2] = prev.points;
const prevKey = wallKey(p1, p2);
if (visited.has(prevKey) || visited.has(wallKey(p2, p1))) continue;
if (p1.pointUuid === first.pointUuid && p2.pointUuid !== line[1]?.pointUuid) {
line.unshift(p2);
visited.add(prevKey);
extended = true;
break;
} else if (p2.pointUuid === first.pointUuid && p1.pointUuid !== line[1]?.pointUuid) {
line.unshift(p1);
visited.add(prevKey);
extended = true;
break;
}
}
}
// Create merged LineString
const coords = line.map(p => [p.position[0], p.position[2]]);
mergedLineStrings.push(turf.lineString(coords, {
pointUuids: line.map(p => p.pointUuid)
}));
}
const validLineStrings = mergedLineStrings.map(ls => {
const coords = ls.geometry.coordinates.map(coord => coord.join(','));
if (coords.length < 2) return null;
const start = coords[0];
const end = coords[coords.length - 1];
const middle = coords.slice(1, -1);
const seen = new Set<string>([start, end]);
const filteredMiddle: string[] = [];
for (const point of middle) {
if (!seen.has(point)) {
seen.add(point);
filteredMiddle.push(point);
}
}
const newCoords = [start, ...filteredMiddle, end];
if (newCoords.length >= 4) {
const resultCoords = newCoords.map(str => str.split(',').map(Number));
return {
...ls,
geometry: {
...ls.geometry,
coordinates: resultCoords,
},
};
}
return null;
}).filter(Boolean);
if (validLineStrings.length === 0) return [];
const lineStrings = turf.featureCollection(validLineStrings as any);
const polygons = turf.polygonize(lineStrings as any);
const rooms: Point[][] = []; const rooms: Point[][] = [];
polygons.features.forEach(feature => { polygons.features.forEach((feature) => {
if (feature.geometry.type === 'Polygon') { if (feature.geometry.type === "Polygon") {
const coordinates = feature.geometry.coordinates[0]; const coords = feature.geometry.coordinates[0];
const roomPoints: Point[] = []; const roomPoints: Point[] = [];
for (const [x, z] of coordinates) { for (const [x, z] of coords) {
const matchingPoint = walls.flatMap(wall => wall.points) const matchingPoint = uniqueWalls.flatMap((w) => w.points).find((p) => p.position[0].toFixed(6) === x.toFixed(6) && p.position[2].toFixed(6) === z.toFixed(6));
.find(p =>
p.position[0].toFixed(10) === x.toFixed(10) &&
p.position[2].toFixed(10) === z.toFixed(10)
);
if (matchingPoint) { if (matchingPoint) {
roomPoints.push(matchingPoint); roomPoints.push(matchingPoint);
} }
} }
if (roomPoints.length > 0 && if (roomPoints.length > 0 && roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
roomPoints.push(roomPoints[0]); roomPoints.push(roomPoints[0]);
} }
@@ -161,12 +83,14 @@ export function useWallClassification(walls: Walls) {
const rooms = useMemo(() => findRooms(), [walls]); const rooms = useMemo(() => findRooms(), [walls]);
const findWallType = (wall: Wall) => { const findWallType = (wall: Wall) => {
const containingRooms = rooms.filter(room => { const containingRooms = rooms.filter((room) => {
for (let i = 0; i < room.length - 1; i++) { for (let i = 0; i < room.length - 1; i++) {
const p1 = room[i]; const p1 = room[i];
const p2 = room[i + 1]; const p2 = room[i + 1];
if ((wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) || if (
(wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid)) { (wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) ||
(wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid)
) {
return true; return true;
} }
} }
@@ -175,18 +99,18 @@ export function useWallClassification(walls: Walls) {
if (containingRooms.length === 0) { if (containingRooms.length === 0) {
return { return {
type: 'segment', type: "segment",
rooms: [] rooms: [],
}; };
} else if (containingRooms.length === 1) { } else if (containingRooms.length === 1) {
return { return {
type: 'room', type: "room",
rooms: containingRooms rooms: containingRooms,
}; };
} else { } else {
return { return {
type: 'rooms', type: "rooms",
rooms: containingRooms rooms: containingRooms,
}; };
} }
}; };
@@ -197,16 +121,16 @@ export function useWallClassification(walls: Walls) {
const isRoomWall = (wall: Wall): boolean => { const isRoomWall = (wall: Wall): boolean => {
const type = findWallType(wall).type; const type = findWallType(wall).type;
return type === 'room' || type === 'rooms'; return type === "room" || type === "rooms";
}; };
const isSegmentWall = (wall: Wall): boolean => { const isSegmentWall = (wall: Wall): boolean => {
return findWallType(wall).type === 'segment'; return findWallType(wall).type === "segment";
}; };
const isWallFlipped = (wall: Wall): boolean => { const isWallFlipped = (wall: Wall): boolean => {
const wallType = findWallType(wall); const wallType = findWallType(wall);
if (wallType.type === 'segment') return false; if (wallType.type === "segment") return false;
for (const room of wallType.rooms) { for (const room of wallType.rooms) {
for (let i = 0; i < room.length - 1; i++) { for (let i = 0; i < room.length - 1; i++) {
@@ -223,13 +147,12 @@ export function useWallClassification(walls: Walls) {
return false; return false;
}; };
return { return {
rooms, rooms,
getWallType, getWallType,
isRoomWall, isRoomWall,
isSegmentWall, isSegmentWall,
findRooms, findRooms,
isWallFlipped isWallFlipped,
}; };
} }

View File

@@ -1,19 +1,21 @@
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 { 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 { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from "../../../scene/sceneContext";
import { useToggleView, useToolMode } from '../../../../store/builder/store'; import { useToggleView, useToolMode } from "../../../../store/builder/store";
import { useWallClassification } from './instance/helpers/useWallClassification'; import { useWallClassification } from "./instance/helpers/useWallClassification";
import Line from '../../line/line'; import Line from "../../line/line";
import Point from '../../point/point'; import Point from "../../point/point";
import WallInstance from './instance/wallInstance'; import WallInstance from "./instance/wallInstance";
import * as Constants from '../../../../types/world/worldConstants'; import * as Constants from "../../../../types/world/worldConstants";
import texturePath from "../../../../assets/textures/floor/white.png"; import texturePath from "../../../../assets/textures/floor/white.png";
import texturePathDark from "../../../../assets/textures/floor/black.png"; import texturePathDark from "../../../../assets/textures/floor/black.png";
import useModuleStore from '../../../../store/ui/useModuleStore'; import useModuleStore from "../../../../store/ui/useModuleStore";
import getCenteroidPoint from "../../functions/getCenteroid";
import getArea from "../../functions/getArea";
function WallInstances() { function WallInstances() {
const { wallStore } = useSceneContext(); const { wallStore } = useSceneContext();
@@ -25,12 +27,12 @@ function WallInstances() {
const { rooms } = useWallClassification(walls); const { rooms } = useWallClassification(walls);
useEffect(() => { useEffect(() => {
// console.log('walls: ', walls); // console.log("walls: ", walls);
}, [walls]) }, [walls]);
useEffect(() => { useEffect(() => {
if (!toggleView && activeModule === 'builder') { if (!toggleView && activeModule === "builder") {
if (toolMode !== 'cursor') { if (toolMode !== "cursor") {
if (selectedWall) setSelectedWall(null); if (selectedWall) setSelectedWall(null);
} }
} else { } else {
@@ -42,8 +44,8 @@ function WallInstances() {
const points: Point[] = []; const points: Point[] = [];
const seenUuids = new Set<string>(); const seenUuids = new Set<string>();
walls.forEach(wall => { walls.forEach((wall) => {
wall.points.forEach(point => { wall.points.forEach((point) => {
if (!seenUuids.has(point.pointUuid)) { if (!seenUuids.has(point.pointUuid)) {
seenUuids.add(point.pointUuid); seenUuids.add(point.pointUuid);
points.push(point); points.push(point);
@@ -62,9 +64,9 @@ function WallInstances() {
<WallInstance key={wall.wallUuid} wall={wall} /> <WallInstance key={wall.wallUuid} wall={wall} />
))} ))}
<group name='Wall-Floors-Group'> <group name="Wall-Floors-Group">
{rooms.map((room, index) => ( {rooms.map((room, index) => (
<Floor key={index} room={room} /> <Floor3D key={index} room={room} />
))} ))}
</group> </group>
</> </>
@@ -72,14 +74,19 @@ function WallInstances() {
{toggleView && ( {toggleView && (
<> <>
<group name='Wall-Points-Group'> <group name="Wall-Points-Group">
{allPoints.map((point) => ( {allPoints.map((point) => (
<Point key={point.pointUuid} point={point} /> <Point key={point.pointUuid} point={point} />
))} ))}
</group> </group>
<group name='Wall-Lines-Group'> <group name="Wall-Floors-Group">
{rooms.map((room, index) => (
<Floor2D key={index} room={room} />
))}
</group>
<group name="Wall-Lines-Group">
{walls.map((wall) => ( {walls.map((wall) => (
<React.Fragment key={wall.wallUuid}> <React.Fragment key={wall.wallUuid}>
<Line points={wall.points} /> <Line points={wall.points} />
@@ -91,8 +98,8 @@ function WallInstances() {
const distance = new Vector3(...wall.points[0].position).distanceTo(new Vector3(...wall.points[1].position)); const distance = new Vector3(...wall.points[0].position).distanceTo(new Vector3(...wall.points[1].position));
return ( return (
< React.Fragment key={wall.wallUuid}> <React.Fragment key={wall.wallUuid}>
{toggleView && {toggleView && (
<Html <Html
key={`${wall.points[0].pointUuid}_${wall.points[1].pointUuid}`} key={`${wall.points[0].pointUuid}_${wall.points[1].pointUuid}`}
userData={wall} userData={wall}
@@ -103,29 +110,25 @@ function WallInstances() {
prepend prepend
sprite sprite
> >
<div <div key={wall.wallUuid} className={`distance ${wall.wallUuid}`}>
key={wall.wallUuid}
className={`distance ${wall.wallUuid}`}
>
{distance.toFixed(2)} m {distance.toFixed(2)} m
</div> </div>
</Html> </Html>
} )}
</React.Fragment> </React.Fragment>
) );
})} })}
</group> </group>
</> </>
)} )}
</> </>
) );
} }
export default WallInstances; export default WallInstances;
function Floor({ room }: { readonly room: Point[] }) { function Floor3D({ room }: { readonly room: Point[] }) {
const savedTheme: string | null = localStorage.getItem('theme'); const savedTheme: string | null = localStorage.getItem("theme");
const textureScale = Constants.floorConfig.textureScale; const textureScale = Constants.floorConfig.textureScale;
const floorTexture = useLoader(TextureLoader, savedTheme === "dark" ? texturePathDark : texturePath); const floorTexture = useLoader(TextureLoader, savedTheme === "dark" ? texturePathDark : texturePath);
floorTexture.wrapS = floorTexture.wrapT = RepeatWrapping; floorTexture.wrapS = floorTexture.wrapT = RepeatWrapping;
@@ -134,10 +137,12 @@ function Floor({ room }: { readonly room: Point[] }) {
const shape = useMemo(() => { const shape = useMemo(() => {
const shape = new Shape(); const shape = new Shape();
const points = room.map(p => new Vector2(p.position[0], p.position[2])); const points = room.map((p) => new Vector2(p.position[0], p.position[2]));
if (points.length < 3) return null; if (points.length < 3) return null;
shape.moveTo(points[0].x, points[0].y); shape.moveTo(points[0].x, points[0].y);
points.forEach((pt) => { shape.lineTo(pt.x, pt.y); }); points.forEach((pt) => {
shape.lineTo(pt.x, pt.y);
});
return shape; return shape;
}, [room]); }, [room]);
@@ -145,15 +150,54 @@ function Floor({ room }: { readonly room: Point[] }) {
return ( return (
<mesh name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}> <mesh name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
<Extrude <Extrude receiveShadow castShadow name="Wall-Floor" args={[shape, { depth: Constants.floorConfig.height, bevelEnabled: false }]} position={[0, 0, 0]}>
receiveShadow
castShadow
name="Wall-Floor"
args={[shape, { depth: Constants.floorConfig.height, bevelEnabled: false }]}
position={[0, 0, 0]}
>
<meshStandardMaterial color={Constants.floorConfig.defaultColor} map={floorTexture} side={DoubleSide} /> <meshStandardMaterial color={Constants.floorConfig.defaultColor} map={floorTexture} side={DoubleSide} />
</Extrude> </Extrude>
</mesh> </mesh>
); );
} }
function Floor2D({ room }: { readonly room: Point[] }) {
const savedTheme: string | null = localStorage.getItem("theme");
const points2D = useMemo(() => {
return room.map((p) => new Vector2(parseFloat(p.position[0].toFixed(2)), parseFloat(p.position[2].toFixed(2))));
}, [room]);
const shape = useMemo(() => {
const shape = new Shape();
shape.moveTo(points2D[0].x, points2D[0].y);
for (let i = 1; i < points2D.length; i++) {
shape.lineTo(points2D[i].x, points2D[i].y);
}
shape.lineTo(points2D[0].x, points2D[0].y);
return shape;
}, [points2D]);
const area = useMemo(() => getArea(points2D), [points2D]);
const centroid: [number, number, number] = useMemo(() => {
const center = getCenteroidPoint(points2D);
if (!center) return [0, Constants.floorConfig.height + 0.01, 0];
return [center.x, Constants.floorConfig.height + 0.01, center.y] as [number, number, number];
}, [points2D]);
if (!shape) return null;
const formattedArea = `${area.toFixed(2)}`;
return (
<>
<mesh castShadow receiveShadow name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
<Extrude receiveShadow castShadow name="Wall-Floor" args={[shape, { depth: Constants.floorConfig.height }]} position={[0, 0, 0]}>
<meshBasicMaterial color={savedTheme === "dark" ? Constants.lineConfig.wallColor : Constants.lineConfig.wallColor} side={DoubleSide} transparent opacity={0.4} depthWrite={false} />
</Extrude>
</mesh>
<Html position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
<div className="distance area">({formattedArea})</div>
</Html>
</>
);
}

View File

@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from "react";
import * as THREE from 'three'; import * as THREE from "three";
import { useFrame, useThree } from '@react-three/fiber'; import { useFrame, useThree } from "@react-three/fiber";
import { Html } from '@react-three/drei'; import { Html } from "@react-three/drei";
import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store'; import { useActiveLayer, useToolMode, useToggleView } from "../../../../store/builder/store";
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping'; import { useDirectionalSnapping } from "../../point/helpers/useDirectionalSnapping";
import { usePointSnapping } from '../../point/helpers/usePointSnapping'; import { usePointSnapping } from "../../point/helpers/usePointSnapping";
import ReferenceLine from '../../line/reference/referenceLine'; import ReferenceLine from "../../line/reference/referenceLine";
interface ReferenceWallProps { interface ReferenceWallProps {
tempPoints: Point[]; tempPoints: Point[];
@@ -26,10 +26,10 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null); const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null);
const { snapWallPoint } = usePointSnapping({ uuid: 'temp-wall', pointType: 'Wall', position: directionalSnap.position || [0, 0, 0] }); const { snapWallPoint } = usePointSnapping({ uuid: "temp-wall", pointType: "Wall", position: directionalSnap.position || [0, 0, 0] });
useFrame(() => { useFrame(() => {
if (toolMode === 'Wall' && toggleView && tempPoints.length === 1) { if (toolMode === "Wall" && toggleView && tempPoints.length === 1) {
raycaster.setFromCamera(pointer, camera); raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3(); const intersectionPoint = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, intersectionPoint); raycaster.ray.intersectPlane(plane, intersectionPoint);
@@ -37,7 +37,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (!intersectionPoint) return; if (!intersectionPoint) return;
const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z], tempPoints[0]); const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (snapped.isSnapped && snapped.snappedPoint) { if (snapped.isSnapped && snapped.snappedPoint) {
finalPosition.current = snapped.position; finalPosition.current = snapped.position;
@@ -58,23 +58,22 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
const wallPoints: [Point, Point] = [ const wallPoints: [Point, Point] = [
tempPoints[0], tempPoints[0],
{ {
pointUuid: 'temp-point', pointUuid: "temp-point",
pointType: 'Wall', pointType: "Wall",
position: finalPosition.current, position: finalPosition.current,
layer: activeLayer, layer: activeLayer,
} },
]; ];
setTempWall({ setTempWall({
wallUuid: 'temp-wall', wallUuid: "temp-wall",
points: wallPoints, points: wallPoints,
outsideMaterial: 'default', outsideMaterial: "default",
insideMaterial: 'default', insideMaterial: "default",
wallThickness: wallThickness, wallThickness: wallThickness,
wallHeight: wallHeight, wallHeight: wallHeight,
decals: [] decals: [],
}) });
} else if (tempWall !== null) { } else if (tempWall !== null) {
setTempWall(null); setTempWall(null);
} }
@@ -87,9 +86,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
if (!tempWall) return null; if (!tempWall) return null;
const renderWall = () => { const renderWall = () => {
return ( return <ReferenceLine points={tempWall.points} />;
<ReferenceLine points={tempWall.points} />
)
}; };
const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempWall.points[0].position), new THREE.Vector3(...tempWall.points[1].position)).divideScalar(2); const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempWall.points[0].position), new THREE.Vector3(...tempWall.points[1].position)).divideScalar(2);
@@ -113,8 +110,8 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
</Html> </Html>
)} )}
</> </>
) );
} };
return ( return (
<group name="Wall-Reference-Group"> <group name="Wall-Reference-Group">
@@ -124,4 +121,4 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
); );
} }
export default ReferenceWall; export default ReferenceWall;

View File

@@ -22,7 +22,7 @@ function WallCreator() {
const { activeLayer } = useActiveLayer(); const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const { wallStore, undoRedo2DStore, versionStore } = useSceneContext(); const { wallStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore(); const { addWall, getWallPointById, removeWall, getWallByPoints, getConnectedPoints } = wallStore();
const { push2D } = undoRedo2DStore(); const { push2D } = undoRedo2DStore();
const drag = useRef(false); const drag = useRef(false);
const isLeftMouseDown = useRef(false); const isLeftMouseDown = useRef(false);
@@ -379,7 +379,9 @@ function WallCreator() {
newPoint.layer = snappedPoint.layer; newPoint.layer = snappedPoint.layer;
} }
if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { const connectedPoints = getConnectedPoints(tempPoints[0]?.pointUuid || "");
if (snappedPoint && (snappedPoint.pointUuid === tempPoints[0]?.pointUuid || connectedPoints.some((point) => point.pointUuid === snappedPoint.pointUuid))) {
return; return;
} }
@@ -485,7 +487,29 @@ function WallCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addWall, getWallPointById, wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint, selectedVersion?.versionId]); }, [
gl,
camera,
scene,
raycaster,
pointer,
plane,
toggleView,
toolMode,
activeLayer,
socket,
tempPoints,
isCreating,
addWall,
getWallPointById,
wallThickness,
wallHeight,
insideMaterial,
outsideMaterial,
snappedPosition,
snappedPoint,
selectedVersion?.versionId,
]);
return ( return (
<> <>

View File

@@ -1,50 +1,55 @@
import { useMemo } from 'react'; import { useMemo } from "react";
import { DoubleSide, Shape, Vector2 } from 'three'; import { DoubleSide, Shape, Vector2 } from "three";
import { Extrude } from '@react-three/drei'; import { Extrude, Html } from "@react-three/drei";
import * as Constants from '../../../../../types/world/worldConstants'; import * as Constants from "../../../../../types/world/worldConstants";
import getCenteroid from "../../../functions/getCenteroid";
import getArea from "../../../functions/getArea";
function Zone2DInstance({ zone }: { readonly zone: Zone }) { function Zone2DInstance({ zone }: { readonly zone: Zone }) {
const savedTheme: string | null = localStorage.getItem("theme"); const savedTheme: string | null = localStorage.getItem("theme");
const points2D = useMemo(() => {
return zone.points.map((p) => new Vector2(parseFloat(p.position[0].toFixed(2)), parseFloat(p.position[2].toFixed(2))));
}, [zone]);
const shape = useMemo(() => { const shape = useMemo(() => {
const shape = new Shape(); const shape = new Shape();
const points = zone.points.map(p => new Vector2(p.position[0], p.position[2])); shape.moveTo(points2D[0].x, points2D[0].y);
if (points.length < 3) return null; for (let i = 1; i < points2D.length; i++) {
shape.moveTo(points[0].x, points[0].y); shape.lineTo(points2D[i].x, points2D[i].y);
for (let i = 1; i < points.length; i++) {
shape.lineTo(points[i].x, points[i].y);
} }
shape.lineTo(points2D[0].x, points2D[0].y);
return shape; return shape;
}, [zone]); }, [points2D]);
const area = useMemo(() => getArea(points2D), [points2D]);
const centroid: [number, number, number] = useMemo(() => {
const center = getCenteroid(points2D);
if (!center) return [0, Constants.floorConfig.height + 0.01, 0];
return [center.x, Constants.floorConfig.height + 0.01, center.y] as [number, number, number];
}, [points2D]);
if (!shape) return null; if (!shape) return null;
const formattedArea = `${area.toFixed(2)}`;
return ( return (
<mesh <>
castShadow <mesh castShadow receiveShadow name={`Zone-2D-${zone.zoneUuid}`} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]} userData={zone}>
receiveShadow <Extrude name={`Zone-${zone.zoneUuid}`} args={[shape, { depth: Constants.floorConfig.height }]} userData={zone}>
name={`Zone-2D-${zone.zoneUuid}`} <meshBasicMaterial color={savedTheme === "dark" ? Constants.lineConfig.zoneColor : Constants.lineConfig.zoneColor} side={DoubleSide} transparent opacity={0.4} depthWrite={false} />
rotation={[Math.PI / 2, 0, 0]} </Extrude>
position={[0, 0, 0]} </mesh>
userData={zone}
> <Html key={zone.zoneUuid} position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
<Extrude <div className="distance area">
name={`Zone-${zone.zoneUuid}`} {zone.zoneName} ({formattedArea})
args={[shape, { </div>
depth: Constants.floorConfig.height, </Html>
}]} </>
userData={zone}
>
<meshBasicMaterial
color={savedTheme === "dark" ? "#007BFF" : "#007BFF"}
side={DoubleSide}
transparent
opacity={0.4}
depthWrite={false}
/>
</Extrude>
</mesh>
); );
} }
export default Zone2DInstance; export default Zone2DInstance;

View File

@@ -1,12 +1,12 @@
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from "react";
import { Vector3 } from 'three'; import { Vector3 } from "three";
import { Html } from '@react-three/drei'; import { Html } from "@react-three/drei";
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from "../../../scene/sceneContext";
import { useToggleView } from '../../../../store/builder/store'; import { useToggleView } from "../../../../store/builder/store";
import Line from '../../line/line'; import Line from "../../line/line";
import Point from '../../point/point'; import Point from "../../point/point";
import ZoneInstance from './Instance/zoneInstance'; import ZoneInstance from "./Instance/zoneInstance";
import Zone2DInstance from './Instance/zone2DInstance'; import Zone2DInstance from "./Instance/zone2DInstance";
function ZoneInstances() { function ZoneInstances() {
const { zoneStore } = useSceneContext(); const { zoneStore } = useSceneContext();
@@ -15,14 +15,14 @@ function ZoneInstances() {
useEffect(() => { useEffect(() => {
// console.log('zones: ', zones); // console.log('zones: ', zones);
}, [zones]) }, [zones]);
const allPoints = useMemo(() => { const allPoints = useMemo(() => {
const points: Point[] = []; const points: Point[] = [];
const seenUuids = new Set<string>(); const seenUuids = new Set<string>();
zones.forEach(zone => { zones.forEach((zone) => {
zone.points.forEach(point => { zone.points.forEach((point) => {
if (!seenUuids.has(point.pointUuid)) { if (!seenUuids.has(point.pointUuid)) {
seenUuids.add(point.pointUuid); seenUuids.add(point.pointUuid);
points.push(point); points.push(point);
@@ -50,7 +50,7 @@ function ZoneInstances() {
lines.push({ lines.push({
start: current, start: current,
end: next, end: next,
key: lineKey key: lineKey,
}); });
} }
} }
@@ -61,9 +61,8 @@ function ZoneInstances() {
return ( return (
<> <>
{!toggleView && zones.length > 0 && ( {!toggleView && zones.length > 0 && (
<mesh name='Zones-Group'> <mesh name="Zones-Group">
{zones.map((zone) => ( {zones.map((zone) => (
<ZoneInstance key={zone.zoneUuid} zone={zone} /> <ZoneInstance key={zone.zoneUuid} zone={zone} />
))} ))}
@@ -71,7 +70,7 @@ function ZoneInstances() {
)} )}
{toggleView && zones.length > 0 && ( {toggleView && zones.length > 0 && (
<mesh name='Zones-2D-Group'> <mesh name="Zones-2D-Group">
{zones.map((zone) => ( {zones.map((zone) => (
<Zone2DInstance key={zone.zoneUuid} zone={zone} /> <Zone2DInstance key={zone.zoneUuid} zone={zone} />
))} ))}
@@ -80,14 +79,13 @@ function ZoneInstances() {
{toggleView && ( {toggleView && (
<> <>
<group name='Zone-Points-Group'> <group name="Zone-Points-Group">
{allPoints.map((point) => ( {allPoints.map((point) => (
<Point key={point.pointUuid} point={point} /> <Point key={point.pointUuid} point={point} />
))} ))}
</group> </group>
<group name='Zone-Lines-Group'> <group name="Zone-Lines-Group">
{allLines.map(({ start, end, key }) => ( {allLines.map(({ start, end, key }) => (
<Line key={key} points={[start, end]} /> <Line key={key} points={[start, end]} />
))} ))}
@@ -99,7 +97,7 @@ function ZoneInstances() {
return ( return (
<React.Fragment key={key}> <React.Fragment key={key}>
{toggleView && {toggleView && (
<Html <Html
key={`${start.pointUuid}_${end.pointUuid}`} key={`${start.pointUuid}_${end.pointUuid}`}
userData={line} userData={line}
@@ -110,23 +108,19 @@ function ZoneInstances() {
prepend prepend
sprite sprite
> >
<div <div key={key} className={`distance ${key}`}>
key={key}
className={`distance ${key}`}
>
{distance.toFixed(2)} m {distance.toFixed(2)} m
</div> </div>
</Html> </Html>
} )}
</React.Fragment> </React.Fragment>
) );
})} })}
</group> </group>
</> </>
)} )}
</> </>
) );
} }
export default ZoneInstances export default ZoneInstances;

View File

@@ -86,6 +86,10 @@ function ZoneCreator() {
return; return;
} }
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) { if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition; newPoint.position = snappedPosition;
} }
@@ -282,7 +286,7 @@ function ZoneCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); canvasElement.removeEventListener("contextmenu", onContext);
}; };
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]); }, [gl, camera, scene, raycaster, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
return ( return (
<> <>

View File

@@ -40,7 +40,7 @@ const CamMode: React.FC = () => {
const keyCombination = detectModifierKeys(event); const keyCombination = detectModifierKeys(event);
if (keyCombination === "/" && !isTransitioning && !toggleView) { if (keyCombination === "/" && !isTransitioning && !toggleView) {
setIsTransitioning && setIsTransitioning(true); setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse; state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse; state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
@@ -55,10 +55,10 @@ const CamMode: React.FC = () => {
await switchToThirdPerson(state.controls, state.camera); await switchToThirdPerson(state.controls, state.camera);
} }
setIsTransitioning && setIsTransitioning(false); setIsTransitioning(false);
} }
if (keyCombination === 'Shift') { if (keyCombination === "Shift") {
setIsShiftActive(true); setIsShiftActive(true);
} }
}; };

View File

@@ -1,9 +1,9 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import * as THREE from 'three'; import * as THREE from "three";
import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei'; import { PerspectiveCamera, OrthographicCamera, CameraControls } from "@react-three/drei";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import * as CONSTANTS from '../../../types/world/worldConstants'; import * as CONSTANTS from "../../../types/world/worldConstants";
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi"; import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
import { useToggleView } from "../../../store/builder/store"; import { useToggleView } from "../../../store/builder/store";
@@ -18,19 +18,21 @@ export default function SwitchView() {
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse; (controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
} else { } else {
if (!projectId) return; if (!projectId) return;
getCameraApi(projectId).then((data) => { getCameraApi(projectId)
if (data?.position && data?.target) { .then((data) => {
(controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z); if (data?.position && data?.target) {
(controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z); (controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z);
} else { (controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z);
} else {
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}
})
.catch(() => {
echo.error("Failed to retrieve camera position or target");
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition); (controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget); (controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
} });
}).catch(() => {
echo.error("Failed to retrieve camera position or target");
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
})
if (controls) { if (controls) {
(controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse; (controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
@@ -62,4 +64,4 @@ export default function SwitchView() {
)} )}
</> </>
); );
} }

View File

@@ -1,5 +1,5 @@
import { CameraControls } from "@react-three/drei"; import { CameraControls } from "@react-three/drei";
import { useRef, useEffect } from "react"; import { useRef, useEffect, useState } from "react";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import * as THREE from "three"; import * as THREE from "three";
import * as CONSTANTS from "../../../types/world/worldConstants"; import * as CONSTANTS from "../../../types/world/worldConstants";
@@ -7,6 +7,7 @@ import { useSocketStore, useToggleView, useResetCamera } from "../../../store/bu
import CamMode from "../camera/camMode"; import CamMode from "../camera/camMode";
import SwitchView from "../camera/switchView"; import SwitchView from "../camera/switchView";
import SyncCam from "../camera/syncCam";
import ContextControls from "./contextControls/contextControls"; import ContextControls from "./contextControls/contextControls";
import TransformControl from "./transformControls/transformControls"; import TransformControl from "./transformControls/transformControls";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
@@ -20,7 +21,6 @@ import { getUserData } from "../../../functions/getUserData";
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi"; import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
import { setCameraApi } from "../../../services/factoryBuilder/camera/setCameraApi"; import { setCameraApi } from "../../../services/factoryBuilder/camera/setCameraApi";
import updateCamPosition from "../camera/functions/updateCameraPosition"; import updateCamPosition from "../camera/functions/updateCameraPosition";
import SyncCam from "../camera/syncCam";
export default function Controls() { export default function Controls() {
const controlsRef = useRef<CameraControls>(null); const controlsRef = useRef<CameraControls>(null);
@@ -59,7 +59,12 @@ export default function Controls() {
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth); controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
if (!socket?.connected) { if (!socket?.connected) {
setCameraApi(projectId, new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition), new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget), new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation)); setCameraApi(
projectId,
new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation)
);
} else { } else {
const camData = { const camData = {
organization, organization,
@@ -143,7 +148,6 @@ export default function Controls() {
boundaryEnclosesCamera={true} boundaryEnclosesCamera={true}
dollyDragInverted dollyDragInverted
> >
<SwitchView /> <SwitchView />
<CamMode /> <CamMode />

View File

@@ -23,6 +23,7 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co
{ name: "backward", keys: ["ArrowDown", "s", "S"] }, { name: "backward", keys: ["ArrowDown", "s", "S"] },
{ name: "left", keys: ["ArrowLeft", "a", "A"] }, { name: "left", keys: ["ArrowLeft", "a", "A"] },
{ name: "right", keys: ["ArrowRight", "d", "D"] }, { name: "right", keys: ["ArrowRight", "d", "D"] },
{ name: "jump", keys: ["Space"] },
], ],
[] []
); );

View File

@@ -169,14 +169,24 @@ export function useRetrieveHandler() {
if (vehicle && !vehicle.isActive && vehicle.state === "idle" && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { if (vehicle && !vehicle.isActive && vehicle.state === "idle" && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction); const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
if (material) { if (material) {
addCurrentAction(triggeredModel.modelUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "", material.materialType, material.materialId); addCurrentAction(
triggeredModel.modelUuid,
retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "",
material.materialType,
material.materialId
);
retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`); retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`);
} }
} }
} else { } else {
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction); const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
if (material) { if (material) {
addCurrentAction(triggeredModel.modelUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "", material.materialType, material.materialId); addCurrentAction(
triggeredModel.modelUuid,
retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "",
material.materialType,
material.materialId
);
retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`); retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`);
} }
} }
@@ -282,7 +292,9 @@ export function useRetrieveHandler() {
} }
if (human && !human.isScheduled && human.state === "idle" && human.currentLoad < action.loadCapacity) { if (human && !human.isScheduled && human.state === "idle" && human.currentLoad < action.loadCapacity) {
const triggeredModel = action.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid ? getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid) : null; const triggeredModel = action.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid
? getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid)
: null;
const storageAction = getActionByUuid(selectedProduct.productUuid, actionUuid); const storageAction = getActionByUuid(selectedProduct.productUuid, actionUuid);
@@ -290,7 +302,18 @@ export function useRetrieveHandler() {
const model = getVehicleById(triggeredModel.modelUuid); const model = getVehicleById(triggeredModel.modelUuid);
if (model && !model.isActive && model.state === "idle" && model.isPicking && model.currentLoad < model.point.action.loadCapacity) { if (model && !model.isActive && model.state === "idle" && model.isPicking && model.currentLoad < model.point.action.loadCapacity) {
if (humanAsset?.animationState?.current === "idle") { if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false); if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) { } else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
const lastMaterial = getLastMaterial(storageUnit.modelUuid); const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) { if (lastMaterial) {
@@ -305,6 +328,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1); retrievalCountRef.current.set(actionUuid, currentCount + 1);
} }
} }
monitoredHumansRef.current.delete(human.modelUuid);
} }
return; return;
} }
@@ -312,7 +336,18 @@ export function useRetrieveHandler() {
const armBot = getArmBotById(triggeredModel.modelUuid); const armBot = getArmBotById(triggeredModel.modelUuid);
if (armBot && !armBot.isActive && armBot.state === "idle" && !armBot.currentAction) { if (armBot && !armBot.isActive && armBot.state === "idle" && !armBot.currentAction) {
if (humanAsset?.animationState?.current === "idle") { if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false); if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) { } else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
const lastMaterial = getLastMaterial(storageUnit.modelUuid); const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) { if (lastMaterial) {
@@ -327,6 +362,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1); retrievalCountRef.current.set(actionUuid, currentCount + 1);
} }
} }
monitoredHumansRef.current.delete(human.modelUuid);
} }
return; return;
} }
@@ -334,7 +370,18 @@ export function useRetrieveHandler() {
const model = getConveyorById(triggeredModel.modelUuid); const model = getConveyorById(triggeredModel.modelUuid);
if (model && !model.isPaused) { if (model && !model.isPaused) {
if (humanAsset?.animationState?.current === "idle") { if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false); if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) { } else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
const lastMaterial = getLastMaterial(storageUnit.modelUuid); const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) { if (lastMaterial) {
@@ -349,6 +396,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1); retrievalCountRef.current.set(actionUuid, currentCount + 1);
} }
} }
monitoredHumansRef.current.delete(human.modelUuid);
} }
return; return;
} }
@@ -385,6 +433,18 @@ export function useRetrieveHandler() {
return; return;
} }
} else if (triggeredModel?.type === "storageUnit") { } else if (triggeredModel?.type === "storageUnit") {
if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
const lastMaterial = getLastMaterial(storageUnit.modelUuid); const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) { if (lastMaterial) {
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction); const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
@@ -397,6 +457,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1); retrievalCountRef.current.set(actionUuid, currentCount + 1);
} }
monitoredHumansRef.current.delete(human.modelUuid);
} }
} }
} }

View File

@@ -61,21 +61,11 @@ export const useLoadingProgress = create<{
setLoadingProgress: (x: number) => set({ loadingProgress: x }), setLoadingProgress: (x: number) => set({ loadingProgress: x }),
})); }));
export const useOrganization = create<any>((set: any) => ({
organization: "",
setOrganization: (x: any) => set(() => ({ organization: x })),
}));
export const useToggleView = create<any>((set: any) => ({ export const useToggleView = create<any>((set: any) => ({
toggleView: false, toggleView: false,
setToggleView: (x: any) => set(() => ({ toggleView: x })), setToggleView: (x: any) => set(() => ({ toggleView: x })),
})); }));
export const useRoomsState = create<any>((set: any) => ({
roomsState: [],
setRoomsState: (x: any) => set(() => ({ roomsState: x })),
}));
export const useSelectedItem = create<any>((set: any) => ({ export const useSelectedItem = create<any>((set: any) => ({
selectedItem: { selectedItem: {
name: "", name: "",
@@ -107,11 +97,6 @@ export const useNavMesh = create<any>((set: any) => ({
setNavMesh: (x: any) => set({ navMesh: x }), setNavMesh: (x: any) => set({ navMesh: x }),
})); }));
export const useLayers = create<any>((set: any) => ({
Layers: 1,
setLayers: (x: any) => set(() => ({ Layers: x })),
}));
export const useCamPosition = create<any>((set: any) => ({ export const useCamPosition = create<any>((set: any) => ({
camPosition: { x: undefined, y: undefined, z: undefined }, camPosition: { x: undefined, y: undefined, z: undefined },
setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }), setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }),
@@ -177,11 +162,6 @@ export const useResetCamera = create<any>((set: any) => ({
setResetCamera: (x: any) => set({ resetCamera: x }), setResetCamera: (x: any) => set({ resetCamera: x }),
})); }));
export const useAddAction = create<any>((set: any) => ({
addAction: null,
setAddAction: (x: any) => set({ addAction: x }),
}));
export const useActiveTool = create<any>((set: any) => ({ export const useActiveTool = create<any>((set: any) => ({
activeTool: "cursor", activeTool: "cursor",
setActiveTool: (x: any) => set({ activeTool: x }), setActiveTool: (x: any) => set({ activeTool: x }),
@@ -315,11 +295,6 @@ export const useTileDistance = create<any>((set: any) => ({
})), })),
})); }));
export const usePlayAgv = create<any>((set, get) => ({
PlayAgv: [],
setPlayAgv: (updateFn: (prev: any[]) => any[]) => set({ PlayAgv: updateFn(get().PlayAgv) }),
}));
// Define the Asset type // Define the Asset type
type Asset = { type Asset = {
id: string; id: string;
@@ -350,19 +325,6 @@ export const useResourceManagementId = create<ResourceManagementState>((set) =>
setResourceManagementId: (id: string) => set({ resourceManagementId: id }), setResourceManagementId: (id: string) => set({ resourceManagementId: id }),
})); }));
// version visible hidden
interface VersionHistoryState {
viewVersionHistory: boolean;
setVersionHistoryVisible: (value: boolean) => void;
}
const useVersionHistoryVisibleStore = create<VersionHistoryState>((set) => ({
viewVersionHistory: false,
setVersionHistoryVisible: (value) => set({ viewVersionHistory: value }),
}));
export default useVersionHistoryVisibleStore;
interface ShortcutStore { interface ShortcutStore {
showShortcuts: boolean; showShortcuts: boolean;
setShowShortcuts: (value: boolean) => void; setShowShortcuts: (value: boolean) => void;

View File

@@ -1,14 +1,16 @@
import { create } from 'zustand'; import { create } from "zustand";
import { immer } from 'zustand/middleware/immer'; import { immer } from "zustand/middleware/immer";
interface VersionStore { interface VersionStore {
versionHistory: VersionHistory; versionHistory: VersionHistory;
selectedVersion: Version | null; selectedVersion: Version | null;
viewVersionHistory: boolean;
createNewVersion: boolean; createNewVersion: boolean;
setSelectedVersion: (version: Version) => void; setSelectedVersion: (version: Version) => void;
clearSelectedVersion: () => void; clearSelectedVersion: () => void;
setVersionHistoryVisible: (visibility: boolean) => void;
setCreateNewVersion: (createNewVersion: boolean) => void; setCreateNewVersion: (createNewVersion: boolean) => void;
addVersion: (version: Version) => void; addVersion: (version: Version) => void;
@@ -26,6 +28,7 @@ export const createVersionStore = () => {
immer((set, get) => ({ immer((set, get) => ({
versionHistory: [], versionHistory: [],
selectedVersion: null, selectedVersion: null,
viewVersionHistory: false,
createNewVersion: false, createNewVersion: false,
setSelectedVersion: (version) => { setSelectedVersion: (version) => {
@@ -40,58 +43,64 @@ export const createVersionStore = () => {
}); });
}, },
setVersionHistoryVisible: (visibility: boolean) => {
set((state) => {
state.viewVersionHistory = visibility;
});
},
setCreateNewVersion: (createNewVersion: boolean) => { setCreateNewVersion: (createNewVersion: boolean) => {
set((state) => { set((state) => {
state.createNewVersion = createNewVersion; state.createNewVersion = createNewVersion;
}) });
}, },
addVersion: (version: Version) => { addVersion: (version: Version) => {
set((state) => { set((state) => {
state.versionHistory.unshift(version); state.versionHistory.unshift(version);
}) });
}, },
setVersions: (versions: Version[]) => { setVersions: (versions: Version[]) => {
set((state) => { set((state) => {
state.versionHistory = versions; state.versionHistory = versions;
}) });
}, },
clearVersions: () => { clearVersions: () => {
set((state) => { set((state) => {
state.versionHistory = []; state.versionHistory = [];
state.selectedVersion = null; state.selectedVersion = null;
state.createNewVersion = false state.createNewVersion = false;
}) });
}, },
setVersionName: (versionId: string, versionName: string) => { setVersionName: (versionId: string, versionName: string) => {
set((state) => { set((state) => {
const version = state.versionHistory.find((v) => v.versionId === versionId); const version = get().getVersionById(versionId);
if (version) { if (version) {
version.versionName = versionName; version.versionName = versionName;
} }
}) });
}, },
updateVersion: (versionId: string, versionName: string, versionDescription: string) => { updateVersion: (versionId: string, versionName: string, versionDescription: string) => {
set((state) => { set((state) => {
const version = state.versionHistory.find((v) => v.versionId === versionId); const version = get().getVersionById(versionId);
if (version) { if (version) {
version.versionName = versionName; version.versionName = versionName;
version.versionDescription = versionDescription; version.versionDescription = versionDescription;
} }
}) });
}, },
getVersionById: (versionId: string) => { getVersionById: (versionId: string) => {
return get().versionHistory.find((v) => { return get().versionHistory.find((v) => {
return v.versionId === versionId return v.versionId === versionId;
}) });
} },
})) }))
); );
}; };
export type VersionStoreType = ReturnType<typeof createVersionStore>; export type VersionStoreType = ReturnType<typeof createVersionStore>;

View File

@@ -202,7 +202,7 @@ export const firstPersonControls: Controls = {
leftSpeed: -0.1, // Speed of left movement leftSpeed: -0.1, // Speed of left movement
rightSpeed: 0.1, // Speed of right movement rightSpeed: 0.1, // Speed of right movement
walkSpeed: 1, // Walk speed walkSpeed: 1, // Walk speed
sprintSpeed: 4 // Sprint Speed sprintSpeed: 4, // Sprint Speed
}; };
export const thirdPersonControls: ThirdPersonControls = { export const thirdPersonControls: ThirdPersonControls = {
@@ -359,7 +359,7 @@ export const roofConfig: RoofConfig = {
export const aisleConfig: AisleConfig = { export const aisleConfig: AisleConfig = {
width: 0.1, // Width of the aisles width: 0.1, // Width of the aisles
height: 0.01, // Height of the aisles height: 0.01, // Height of the aisles
defaultColor: '#E2AC09', // Default color of the aisles defaultColor: "#E2AC09", // Default color of the aisles
}; };
export const zoneConfig: ZoneConfig = { export const zoneConfig: ZoneConfig = {
@@ -384,4 +384,4 @@ export const distanceConfig: DistanceConfig = {
export const undoRedoConfig: undoRedoCount = { export const undoRedoConfig: undoRedoCount = {
undoRedoCount: 50, undoRedoCount: 50,
} };

View File

@@ -1,7 +1,18 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/ui/useModuleStore"; import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/ui/useModuleStore";
import { usePlayerStore, useToggleStore } from "../../store/ui/useUIToggleStore"; import { usePlayerStore, useToggleStore } from "../../store/ui/useUIToggleStore";
import useVersionHistoryVisibleStore, { useActiveSubTool, useActiveTool, useAddAction, useDfxUpload, useRenameModeStore, useIsComparing, useSelectedComment, useShortcutStore, useToggleView, useToolMode, useViewSceneStore } from "../../store/builder/store"; import {
useActiveSubTool,
useActiveTool,
useDfxUpload,
useRenameModeStore,
useIsComparing,
useSelectedComment,
useShortcutStore,
useToggleView,
useToolMode,
useViewSceneStore,
} from "../../store/builder/store";
import useCameraModeStore, { usePlayButtonStore } from "../../store/ui/usePlayButtonStore"; import useCameraModeStore, { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
import { detectModifierKeys } from "./detectModifierKeys"; import { detectModifierKeys } from "./detectModifierKeys";
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
@@ -22,7 +33,6 @@ const KeyPressListener: React.FC = () => {
const { setToolMode } = useToolMode(); const { setToolMode } = useToolMode();
const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { toggleView, setToggleView } = useToggleView(); const { toggleView, setToggleView } = useToggleView();
const { setAddAction } = useAddAction();
const { setActiveTool } = useActiveTool(); const { setActiveTool } = useActiveTool();
const { clearSelectedZone } = useSelectedZoneStore(); const { clearSelectedZone } = useSelectedZoneStore();
const { showShortcuts, setShowShortcuts } = useShortcutStore(); const { showShortcuts, setShowShortcuts } = useShortcutStore();
@@ -33,8 +43,7 @@ const KeyPressListener: React.FC = () => {
const { setViewSceneLabels } = useViewSceneStore(); const { setViewSceneLabels } = useViewSceneStore();
const { isRenameMode, setIsRenameMode } = useRenameModeStore(); const { isRenameMode, setIsRenameMode } = useRenameModeStore();
const { selectedFloorAsset, setSelectedWallAsset } = useBuilderStore(); const { selectedFloorAsset, setSelectedWallAsset } = useBuilderStore();
const { setCreateNewVersion } = versionStore(); const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { setSelectedComment } = useSelectedComment(); const { setSelectedComment } = useSelectedComment();
const { setDfxUploaded } = useDfxUpload(); const { setDfxUploaded } = useDfxUpload();
const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element?.getAttribute("contenteditable") === "true"; const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element?.getAttribute("contenteditable") === "true";
@@ -77,7 +86,6 @@ const KeyPressListener: React.FC = () => {
setToggleThreeD(toggleTo2D); setToggleThreeD(toggleTo2D);
if (toggleTo2D) { if (toggleTo2D) {
setSelectedWallAsset(null); setSelectedWallAsset(null);
setAddAction(null);
setToggleUI(localStorage.getItem("navBarUiLeft") !== "false", localStorage.getItem("navBarUiRight") !== "false"); setToggleUI(localStorage.getItem("navBarUiLeft") !== "false", localStorage.getItem("navBarUiRight") !== "false");
} else { } else {
setToggleUI(false, false); setToggleUI(false, false);
@@ -219,10 +227,6 @@ const KeyPressListener: React.FC = () => {
setShowShortcuts(!showShortcuts); setShowShortcuts(!showShortcuts);
} }
// if (keyCombination === "Ctrl+Shift+P") {
// pref
// }
if (keyCombination === "U") { if (keyCombination === "U") {
setViewSceneLabels((prev) => !prev); setViewSceneLabels((prev) => !prev);
} }