Merge branch 'main-demo' into dev-physics
This commit is contained in:
@@ -2,289 +2,314 @@ import React, { useState, useRef, useEffect, act } from "react";
|
||||
import img from "../../assets/image/image.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getUserData } from "../../functions/getUserData";
|
||||
import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store";
|
||||
import {
|
||||
useLoadingProgress,
|
||||
useProjectName,
|
||||
useSocketStore,
|
||||
} from "../../store/builder/store";
|
||||
import { viewProject } from "../../services/dashboard/viewProject";
|
||||
import OuterClick from "../../utils/outerClick";
|
||||
import { KebabIcon } from "../icons/ExportCommonIcons";
|
||||
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
||||
import { updateProject } from "../../services/dashboard/updateProject";
|
||||
|
||||
interface DashBoardCardProps {
|
||||
projectName: string;
|
||||
thumbnail: any;
|
||||
projectId: string;
|
||||
createdAt?: string;
|
||||
isViewed?: string;
|
||||
createdBy?: { _id: string, userName: string };
|
||||
handleDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleRestoreProject?: (projectId: string) => Promise<void>;
|
||||
handleDuplicateWorkspaceProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string,
|
||||
userId?: string
|
||||
) => Promise<void>;
|
||||
handleDuplicateRecentProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string
|
||||
) => Promise<void>;
|
||||
active?: 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>>;
|
||||
projectName: string;
|
||||
thumbnail: any;
|
||||
projectId: string;
|
||||
createdAt?: string;
|
||||
isViewed?: string;
|
||||
createdBy?: { _id: string; userName: string };
|
||||
handleDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
|
||||
handleRestoreProject?: (projectId: string) => Promise<void>;
|
||||
handleDuplicateWorkspaceProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string,
|
||||
userId?: string
|
||||
) => Promise<void>;
|
||||
handleDuplicateRecentProject?: (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string
|
||||
) => Promise<void>;
|
||||
active?: 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>>;
|
||||
}
|
||||
type RelativeTimeFormatUnit = any;
|
||||
|
||||
const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||
projectName,
|
||||
thumbnail,
|
||||
projectId,
|
||||
active,
|
||||
handleDeleteProject,
|
||||
handleRestoreProject,
|
||||
handleTrashDeleteProject,
|
||||
handleDuplicateWorkspaceProject,
|
||||
handleDuplicateRecentProject,
|
||||
createdAt,
|
||||
createdBy,
|
||||
setRecentDuplicateData,
|
||||
setProjectDuplicateData,
|
||||
setActiveFolder
|
||||
projectName,
|
||||
thumbnail,
|
||||
projectId,
|
||||
active,
|
||||
handleDeleteProject,
|
||||
handleRestoreProject,
|
||||
handleTrashDeleteProject,
|
||||
handleDuplicateWorkspaceProject,
|
||||
handleDuplicateRecentProject,
|
||||
createdAt,
|
||||
createdBy,
|
||||
setRecentDuplicateData,
|
||||
setProjectDuplicateData,
|
||||
setActiveFolder,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { setProjectName } = useProjectName();
|
||||
const { userId, organization, userName } = getUserData();
|
||||
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState(projectName);
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const { projectSocket } = useSocketStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const kebabRef = useRef<HTMLDivElement>(null);
|
||||
const navigate = useNavigate();
|
||||
const { setProjectName } = useProjectName();
|
||||
const { userId, organization, userName } = getUserData();
|
||||
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState(projectName);
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const { projectSocket } = useSocketStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const kebabRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const navigateToProject = async (e: any) => {
|
||||
console.log('active: ', active);
|
||||
if (active && active == "trash") return;
|
||||
try {
|
||||
const viewProjects = await viewProject(organization, projectId, userId)
|
||||
console.log('viewProjects: ', viewProjects);
|
||||
console.log('projectName: ', projectName);
|
||||
setLoadingProgress(1)
|
||||
setProjectName(projectName);
|
||||
navigate(`/${projectId}`);
|
||||
} catch {
|
||||
const navigateToProject = async (e: any) => {
|
||||
if (active && active == "trash") return;
|
||||
try {
|
||||
const viewProjects = await viewProject(organization, projectId, userId);
|
||||
|
||||
setLoadingProgress(1);
|
||||
setProjectName(projectName);
|
||||
navigate(`/${projectId}`);
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const handleOptionClick = async (option: string) => {
|
||||
switch (option) {
|
||||
case "delete":
|
||||
if (handleDeleteProject) {
|
||||
handleDeleteProject(projectId);
|
||||
} else if (handleTrashDeleteProject) {
|
||||
handleTrashDeleteProject(projectId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOptionClick = async (option: string) => {
|
||||
switch (option) {
|
||||
case "delete":
|
||||
if (handleDeleteProject) {
|
||||
handleDeleteProject(projectId);
|
||||
} else if (handleTrashDeleteProject) {
|
||||
handleTrashDeleteProject(projectId);
|
||||
}
|
||||
break;
|
||||
case "restore":
|
||||
if (handleRestoreProject) {
|
||||
await handleRestoreProject(projectId);
|
||||
}
|
||||
break;
|
||||
case "open in new tab":
|
||||
try {
|
||||
if (active === "shared" && createdBy) {
|
||||
console.log("ihreq");
|
||||
const newTab = await viewProject(organization, projectId, createdBy?._id);
|
||||
console.log('newTab: ', newTab);
|
||||
} else {
|
||||
const newTab = await viewProject(organization, projectId, userId);
|
||||
console.log('newTab: ', newTab);
|
||||
setProjectName(projectName);
|
||||
setIsKebabOpen(false);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
window.open(`/${projectId}`, "_blank");
|
||||
break;
|
||||
case "rename":
|
||||
setIsRenaming(true);
|
||||
break;
|
||||
case "duplicate":
|
||||
if (handleDuplicateWorkspaceProject) {
|
||||
setProjectDuplicateData &&
|
||||
setProjectDuplicateData({
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
});
|
||||
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId);
|
||||
if (active === "shared" && setActiveFolder) {
|
||||
setActiveFolder("myProjects")
|
||||
}
|
||||
} else if (handleDuplicateRecentProject) {
|
||||
setRecentDuplicateData &&
|
||||
setRecentDuplicateData({
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
userId
|
||||
});
|
||||
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
break;
|
||||
case "restore":
|
||||
if (handleRestoreProject) {
|
||||
await handleRestoreProject(projectId);
|
||||
}
|
||||
setIsKebabOpen(false);
|
||||
};
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["kebab-wrapper", "kebab-options-wrapper"],
|
||||
setMenuVisible: () => setIsKebabOpen(false),
|
||||
});
|
||||
|
||||
const handleProjectName = async (projectName: string) => {
|
||||
setRenameValue(projectName);
|
||||
if (!projectId) return;
|
||||
break;
|
||||
case "open in new tab":
|
||||
try {
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
if (!projects || !projects.Projects) return;
|
||||
let projectUuid = projects.Projects.find(
|
||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||
if (active === "shared" && createdBy) {
|
||||
const newTab = await viewProject(
|
||||
organization,
|
||||
projectId,
|
||||
createdBy?._id
|
||||
);
|
||||
const updateProjects = {
|
||||
projectId: projectUuid,
|
||||
organization,
|
||||
userId,
|
||||
projectName,
|
||||
thumbnail: undefined,
|
||||
};
|
||||
} else {
|
||||
const newTab = await viewProject(organization, projectId, userId);
|
||||
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:update", updateProjects);
|
||||
}
|
||||
} catch (error) { }
|
||||
};
|
||||
|
||||
function getRelativeTime(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,
|
||||
};
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
|
||||
for (const key in intervals) {
|
||||
const unit = key as RelativeTimeFormatUnit;
|
||||
const diff = Math.floor(diffInSeconds / intervals[unit]);
|
||||
if (diff >= 1) {
|
||||
return rtf.format(-diff, unit);
|
||||
}
|
||||
setProjectName(projectName);
|
||||
setIsKebabOpen(false);
|
||||
}
|
||||
} catch (error) {}
|
||||
window.open(`/${projectId}`, "_blank");
|
||||
break;
|
||||
case "rename":
|
||||
setIsRenaming(true);
|
||||
break;
|
||||
case "duplicate":
|
||||
if (handleDuplicateWorkspaceProject) {
|
||||
setProjectDuplicateData &&
|
||||
setProjectDuplicateData({
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
});
|
||||
await handleDuplicateWorkspaceProject(
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
userId
|
||||
);
|
||||
if (active === "shared" && setActiveFolder) {
|
||||
setActiveFolder("myProjects");
|
||||
}
|
||||
} else if (handleDuplicateRecentProject) {
|
||||
setRecentDuplicateData &&
|
||||
setRecentDuplicateData({
|
||||
projectId,
|
||||
projectName,
|
||||
thumbnail,
|
||||
userId,
|
||||
});
|
||||
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
||||
}
|
||||
return "just now";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setIsKebabOpen(false);
|
||||
};
|
||||
|
||||
const kebabOptionsMap: Record<string, string[]> = {
|
||||
default: ["rename", "delete", "duplicate", "open in new tab"],
|
||||
trash: ["restore", "delete"],
|
||||
shared: ["duplicate", "open in new tab"],
|
||||
OuterClick({
|
||||
contextClassName: ["kebab-wrapper", "kebab-options-wrapper"],
|
||||
setMenuVisible: () => setIsKebabOpen(false),
|
||||
});
|
||||
|
||||
const handleProjectName = async (projectName: string) => {
|
||||
setRenameValue(projectName);
|
||||
if (!projectId) return;
|
||||
try {
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
if (!projects || !projects.Projects) return;
|
||||
let projectUuid = projects.Projects.find(
|
||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||
);
|
||||
const updateProjects = {
|
||||
projectId: projectUuid?._id,
|
||||
organization,
|
||||
userId,
|
||||
projectName,
|
||||
thumbnail: undefined,
|
||||
};
|
||||
// const updatedProjectName = await updateProject(
|
||||
// projectUuid._id,
|
||||
// userId,
|
||||
// organization,
|
||||
// undefined,
|
||||
// projectName
|
||||
// );
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:update", updateProjects);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
function getRelativeTime(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,
|
||||
};
|
||||
|
||||
const getOptions = () => {
|
||||
if (active === "trash") return kebabOptionsMap.trash;
|
||||
if (active === "shared") return kebabOptionsMap.shared;
|
||||
if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared;
|
||||
return kebabOptionsMap.default;
|
||||
};
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
|
||||
return (
|
||||
<button
|
||||
className="dashboard-card-container"
|
||||
onClick={navigateToProject}
|
||||
title={projectName}
|
||||
onMouseLeave={() => setIsKebabOpen(false)}
|
||||
for (const key in intervals) {
|
||||
const unit = key as RelativeTimeFormatUnit;
|
||||
const diff = Math.floor(diffInSeconds / intervals[unit]);
|
||||
if (diff >= 1) {
|
||||
return rtf.format(-diff, unit);
|
||||
}
|
||||
}
|
||||
return "just now";
|
||||
}
|
||||
|
||||
const kebabOptionsMap: Record<string, string[]> = {
|
||||
default: ["rename", "delete", "duplicate", "open in new tab"],
|
||||
trash: ["restore", "delete"],
|
||||
shared: ["duplicate", "open in new tab"],
|
||||
};
|
||||
|
||||
const getOptions = () => {
|
||||
if (active === "trash") return kebabOptionsMap.trash;
|
||||
if (active === "shared") return kebabOptionsMap.shared;
|
||||
if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared;
|
||||
return kebabOptionsMap.default;
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className="dashboard-card-container"
|
||||
onClick={navigateToProject}
|
||||
title={projectName}
|
||||
onMouseLeave={() => setIsKebabOpen(false)}
|
||||
>
|
||||
<div className="dashboard-card-wrapper">
|
||||
<div className="preview-container">
|
||||
{thumbnail ? (
|
||||
<img src={thumbnail} alt="" />
|
||||
) : (
|
||||
<img src={img} alt="" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="project-details-container"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="dashboard-card-wrapper">
|
||||
<div className="preview-container">
|
||||
{thumbnail ? <img src={thumbnail} alt="" /> : <img src={img} alt="" />}
|
||||
</div>
|
||||
<div className="project-details-container" onClick={(e) => { e.stopPropagation() }}>
|
||||
<div className="project-details">
|
||||
{isRenaming ? (
|
||||
<input
|
||||
value={renameValue}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
handleProjectName(e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsRenaming(false);
|
||||
setProjectName(renameValue);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setProjectName(renameValue);
|
||||
setIsRenaming(false);
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span>{renameValue}</span>
|
||||
)}
|
||||
{createdAt && (
|
||||
<div className="project-data">
|
||||
{active && active == "trash" ? `Trashed by you` : `Edited `}{" "}
|
||||
{getRelativeTime(createdAt)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="users-list-container" ref={kebabRef}>
|
||||
<div className="user-profile">
|
||||
{(!createdBy) ? userName ? userName?.charAt(0).toUpperCase() : "A" : createdBy?.userName?.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<button
|
||||
className="kebab-wrapper"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsKebabOpen((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<KebabIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isKebabOpen && (
|
||||
<div className="kebab-options-wrapper">
|
||||
{getOptions().map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
className="option"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOptionClick(option);
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="project-details">
|
||||
{isRenaming ? (
|
||||
<input
|
||||
value={renameValue}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
handleProjectName(e.target.value);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsRenaming(false);
|
||||
setProjectName(renameValue);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setProjectName(renameValue);
|
||||
setIsRenaming(false);
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span>{renameValue}</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
{createdAt && (
|
||||
<div className="project-data">
|
||||
{active && active == "trash" ? `Trashed by you` : `Edited `}{" "}
|
||||
{getRelativeTime(createdAt)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="users-list-container" ref={kebabRef}>
|
||||
<div className="user-profile">
|
||||
{!createdBy
|
||||
? userName
|
||||
? userName?.charAt(0).toUpperCase()
|
||||
: "A"
|
||||
: createdBy?.userName?.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<button
|
||||
className="kebab-wrapper"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsKebabOpen((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<KebabIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isKebabOpen && (
|
||||
<div className="kebab-options-wrapper">
|
||||
{getOptions().map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
className="option"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOptionClick(option);
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardCard;
|
||||
export default DashboardCard;
|
||||
|
||||
@@ -8,15 +8,16 @@ import { recentlyViewed } from "../../services/dashboard/recentlyViewed";
|
||||
import { searchProject } from "../../services/dashboard/searchProjects";
|
||||
import { deleteProject } from "../../services/dashboard/deleteProject";
|
||||
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
||||
import { generateUniqueId } from "../../functions/generateUniqueId";
|
||||
|
||||
interface Project {
|
||||
_id: string;
|
||||
projectName: string;
|
||||
thumbnail: string;
|
||||
createdBy: { _id: string, userName: string };
|
||||
createdBy: { _id: string; userName: string };
|
||||
projectUuid?: string;
|
||||
createdAt: string;
|
||||
isViewed?: string
|
||||
isViewed?: string;
|
||||
}
|
||||
|
||||
interface RecentProjectsData {
|
||||
@@ -25,12 +26,12 @@ interface RecentProjectsData {
|
||||
|
||||
const DashboardHome: React.FC = () => {
|
||||
const [recentProjects, setRecentProjects] = useState<RecentProjectsData>({});
|
||||
|
||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectSocket } = useSocketStore();
|
||||
const [recentDuplicateData, setRecentDuplicateData] = useState<Object>({});
|
||||
|
||||
|
||||
const fetchRecentProjects = async () => {
|
||||
try {
|
||||
const projects = await recentlyViewed(organization, userId);
|
||||
@@ -38,9 +39,7 @@ const DashboardHome: React.FC = () => {
|
||||
if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) {
|
||||
setRecentProjects(projects);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching recent projects:", error);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const handleRecentProjectSearch = async (inputValue: string) => {
|
||||
@@ -65,7 +64,7 @@ const DashboardHome: React.FC = () => {
|
||||
// userId,
|
||||
// organization
|
||||
// );
|
||||
// console.log('deletedProject: ', deletedProject);
|
||||
//
|
||||
|
||||
//socket for delete Project
|
||||
const deleteProject = {
|
||||
@@ -91,9 +90,7 @@ const DashboardHome: React.FC = () => {
|
||||
};
|
||||
});
|
||||
setIsSearchActive(false);
|
||||
} catch (error) {
|
||||
console.error("Error deleting project:", error);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const handleDuplicateRecentProject = async (
|
||||
@@ -105,7 +102,8 @@ const DashboardHome: React.FC = () => {
|
||||
userId,
|
||||
thumbnail,
|
||||
organization,
|
||||
projectUuid: projectId,
|
||||
projectUuid: generateUniqueId(),
|
||||
refProjectID: projectId,
|
||||
projectName,
|
||||
};
|
||||
projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData);
|
||||
@@ -163,4 +161,4 @@ const DashboardHome: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHome;
|
||||
export default DashboardHome;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { deleteProject } from "../../services/dashboard/deleteProject";
|
||||
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
||||
import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject";
|
||||
import { duplicateProject } from "../../services/dashboard/duplicateProject";
|
||||
import { generateUniqueId } from "../../functions/generateUniqueId";
|
||||
|
||||
interface Project {
|
||||
_id: string;
|
||||
@@ -27,7 +28,7 @@ const DashboardProjects: React.FC = () => {
|
||||
const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>(
|
||||
{}
|
||||
);
|
||||
const [sharedwithMeProject, setSharedWithMeProjects] = useState<any>([])
|
||||
const [sharedwithMeProject, setSharedWithMeProjects] = useState<any>([]);
|
||||
const [projectDuplicateData, setProjectDuplicateData] = useState<Object>({});
|
||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||
const [activeFolder, setActiveFolder] = useState<string>("myProjects");
|
||||
@@ -41,7 +42,11 @@ const DashboardProjects: React.FC = () => {
|
||||
}
|
||||
if (!setWorkspaceProjects || !setIsSearchActive) return;
|
||||
|
||||
const searchedProject = await searchProject(organization, userId, inputValue);
|
||||
const searchedProject = await searchProject(
|
||||
organization,
|
||||
userId,
|
||||
inputValue
|
||||
);
|
||||
setIsSearchActive(true);
|
||||
setWorkspaceProjects(searchedProject.message ? {} : searchedProject);
|
||||
};
|
||||
@@ -49,14 +54,13 @@ const DashboardProjects: React.FC = () => {
|
||||
const fetchAllProjects = async () => {
|
||||
try {
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
|
||||
if (!projects || !projects.Projects) return;
|
||||
|
||||
if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) {
|
||||
setWorkspaceProjects(projects);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching projects:", error);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const handleDeleteProject = async (projectId: any) => {
|
||||
@@ -66,7 +70,7 @@ const DashboardProjects: React.FC = () => {
|
||||
// userId,
|
||||
// organization
|
||||
// );
|
||||
// console.log('deletedProject: ', deletedProject);
|
||||
//
|
||||
const deleteProjects = {
|
||||
projectId,
|
||||
organization,
|
||||
@@ -77,7 +81,6 @@ const DashboardProjects: React.FC = () => {
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:delete", deleteProjects);
|
||||
} else {
|
||||
console.error("Socket is not connected.");
|
||||
}
|
||||
setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => {
|
||||
if (!Array.isArray(prevDiscardedProjects?.Projects)) {
|
||||
@@ -92,22 +95,28 @@ const DashboardProjects: React.FC = () => {
|
||||
};
|
||||
});
|
||||
setIsSearchActive(false);
|
||||
} catch (error) {
|
||||
console.error("Error deleting project:", error);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const handleDuplicateWorkspaceProject = async (
|
||||
projectId: string,
|
||||
projectName: string,
|
||||
thumbnail: string,
|
||||
thumbnail: string
|
||||
) => {
|
||||
// const duplicatedProject = await duplicateProject(
|
||||
// projectId,
|
||||
// generateUniqueId(),
|
||||
// thumbnail,
|
||||
// projectName
|
||||
// );
|
||||
// console.log("duplicatedProject: ", duplicatedProject);
|
||||
|
||||
const duplicateProjectData = {
|
||||
userId,
|
||||
thumbnail,
|
||||
organization,
|
||||
projectUuid: projectId,
|
||||
projectUuid: generateUniqueId(),
|
||||
refProjectID: projectId,
|
||||
projectName,
|
||||
};
|
||||
projectSocket.emit("v1:project:Duplicate", duplicateProjectData);
|
||||
@@ -153,16 +162,12 @@ const DashboardProjects: React.FC = () => {
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
const sharedProject = async () => {
|
||||
try {
|
||||
const sharedWithMe = await sharedWithMeProjects();
|
||||
setSharedWithMeProjects(sharedWithMe)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
setSharedWithMeProjects(sharedWithMe);
|
||||
} catch {}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSearchActive) {
|
||||
@@ -172,10 +177,9 @@ const DashboardProjects: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (activeFolder === "shared") {
|
||||
sharedProject()
|
||||
sharedProject();
|
||||
}
|
||||
}, [activeFolder])
|
||||
|
||||
}, [activeFolder]);
|
||||
|
||||
return (
|
||||
<div className="dashboard-home-container">
|
||||
@@ -184,7 +188,10 @@ const DashboardProjects: React.FC = () => {
|
||||
handleProjectsSearch={handleProjectsSearch}
|
||||
/>
|
||||
|
||||
<div className="dashboard-container" style={{ height: "calc(100% - 87px)" }}>
|
||||
<div
|
||||
className="dashboard-container"
|
||||
style={{ height: "calc(100% - 87px)" }}
|
||||
>
|
||||
<div className="header-wrapper" style={{ display: "flex", gap: "7px" }}>
|
||||
<button
|
||||
className={`header ${activeFolder === "myProjects" && "active"}`}
|
||||
@@ -199,19 +206,22 @@ const DashboardProjects: React.FC = () => {
|
||||
Shared with me
|
||||
</button>
|
||||
</div>
|
||||
<div className="cards-container">{activeFolder == "myProjects" ? renderProjects() : renderSharedProjects()}</div>
|
||||
<div className="cards-container">
|
||||
{activeFolder == "myProjects"
|
||||
? renderProjects()
|
||||
: renderSharedProjects()}
|
||||
</div>
|
||||
|
||||
{projectDuplicateData && Object.keys(projectDuplicateData).length > 0 && (
|
||||
<ProjectSocketRes
|
||||
setIsSearchActive={setIsSearchActive}
|
||||
setWorkspaceProjects={setWorkspaceProjects}
|
||||
/>
|
||||
)}
|
||||
{projectDuplicateData &&
|
||||
Object.keys(projectDuplicateData).length > 0 && (
|
||||
<ProjectSocketRes
|
||||
setIsSearchActive={setIsSearchActive}
|
||||
setWorkspaceProjects={setWorkspaceProjects}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardProjects;
|
||||
|
||||
|
||||
|
||||
@@ -160,3 +160,153 @@ export function RenameIcon() {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function FocusIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.31999 1.56H9.89999C10.0325 1.56 10.14 1.66745 10.14 1.8V4.14M10.14 7.5V9.9C10.14 10.0325 10.0325 10.14 9.89999 10.14H7.31999M4.55999 10.14H1.91999C1.78744 10.14 1.67999 10.0325 1.67999 9.9V7.5M1.67999 4.14V1.8C1.67999 1.66745 1.78744 1.56 1.91999 1.56H4.55999" stroke="white" stroke-linecap="round" />
|
||||
<circle cx="6.00005" cy="5.87999" r="1.7" stroke="white" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function TransformIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_55_63)">
|
||||
<path d="M3 0.75C2.40326 0.75 1.83097 0.987053 1.40901 1.40901C0.987053 1.83097 0.75 2.40326 0.75 3C0.75 3.59674 0.987053 4.16903 1.40901 4.59099C1.83097 5.01295 2.40326 5.25 3 5.25C3.24134 5.24937 3.481 5.20991 3.7098 5.13314L3.28805 4.71141L4.79632 3.20316L4.94545 3.05402L5.22342 3.33199C5.24047 3.22214 5.24935 3.11117 5.25 3C5.25 2.40326 5.01295 1.83097 4.59099 1.40901C4.16903 0.987053 3.59674 0.75 3 0.75ZM4.94545 3.65062L3.88467 4.71141L5.92336 6.75L5.37333 7.30001L8.07427 7.84017L7.53403 5.13923L6.98405 5.68922L4.94545 3.65062ZM8.28647 6.75L8.61202 8.37797L6.75 8.00555V11.25H11.25V6.75H8.28645H8.28647Z" fill="#FCFDFD" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_55_63">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export function DublicateIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_190)">
|
||||
<path d="M9 1.5H2C1.72386 1.5 1.5 1.72386 1.5 2V9C1.5 9.27615 1.27614 9.5 1 9.5C0.72386 9.5 0.5 9.27615 0.5 9V2C0.5 1.17158 1.17158 0.5 2 0.5H9C9.27615 0.5 9.5 0.72386 9.5 1C9.5 1.27614 9.27615 1.5 9 1.5Z" fill="white" />
|
||||
<path d="M6.5 5.5C6.5 5.22385 6.72385 5 7 5C7.27615 5 7.5 5.22385 7.5 5.5V6.5H8.5C8.77615 6.5 9 6.72385 9 7C9 7.27615 8.77615 7.5 8.5 7.5H7.5V8.5C7.5 8.77615 7.27615 9 7 9C6.72385 9 6.5 8.77615 6.5 8.5V7.5H5.5C5.22385 7.5 5 7.27615 5 7C5 6.72385 5.22385 6.5 5.5 6.5H6.5V5.5Z" fill="white" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2.5C10.8285 2.5 11.5 3.17158 11.5 4V10C11.5 10.8285 10.8285 11.5 10 11.5H4C3.17158 11.5 2.5 10.8285 2.5 10V4C2.5 3.17158 3.17158 2.5 4 2.5H10ZM10 3.5C10.2761 3.5 10.5 3.72386 10.5 4V10C10.5 10.2761 10.2761 10.5 10 10.5H4C3.72386 10.5 3.5 10.2761 3.5 10V4C3.5 3.72386 3.72386 3.5 4 3.5H10Z" fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_190">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export function CopyIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_197)">
|
||||
<path d="M4.375 1.5H8.875C9.22018 1.5 9.5 1.77982 9.5 2.125V5.07422C9.25749 5.02497 9.00651 5 8.75 5C6.67893 5 5 6.67893 5 8.75C5 8.91951 5.01426 9.08616 5.03613 9.25H4.375C4.02982 9.25 3.75 8.97018 3.75 8.625V2.125C3.75 1.77982 4.02982 1.5 4.375 1.5Z" stroke="white" />
|
||||
<path d="M7.02181 10.8891C5.8404 9.93469 5.6564 8.20324 6.61085 7.02182C7.56529 5.84041 9.29675 5.65641 10.4782 6.61086C11.6596 7.5653 11.8436 9.29676 10.8891 10.4782C9.93468 11.6596 8.20322 11.8436 7.02181 10.8891ZM7.53035 9.63652C7.55951 9.73588 7.64818 9.80729 7.7514 9.81511L7.79716 9.81441L7.84067 9.80562C7.94019 9.77642 8.01223 9.68724 8.01987 9.58381L8.01932 9.53942L7.89568 8.38246L9.76272 9.88956L9.80012 9.91475C9.89114 9.96447 10.0045 9.95243 10.083 9.88469L10.1143 9.8522L10.1395 9.8148C10.1892 9.72378 10.1772 9.61043 10.1094 9.53189L10.0769 9.50062L8.20913 7.99291L9.36823 7.86974L9.41174 7.86095C9.52542 7.82759 9.60327 7.71674 9.59039 7.59475C9.57742 7.47272 9.47859 7.38002 9.36039 7.37128L9.3154 7.37259L7.5357 7.5631L7.50592 7.57044L7.46405 7.58808L7.42779 7.61277L7.40435 7.63401L7.37612 7.66895L7.36028 7.69633L7.35134 7.71672L7.33894 7.75692L7.33456 7.781L7.33441 7.81227L7.52217 9.59225L7.53035 9.63652Z" fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_197">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PasteIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_207)">
|
||||
<path d="M4.375 1.5H8.875C9.22018 1.5 9.5 1.77982 9.5 2.125V5.07422C9.25749 5.02497 9.00651 5 8.75 5C6.67893 5 5 6.67893 5 8.75C5 8.91951 5.01426 9.08616 5.03613 9.25H4.375C4.02982 9.25 3.75 8.97018 3.75 8.625V2.125C3.75 1.77982 4.02982 1.5 4.375 1.5Z" stroke="white" />
|
||||
<path d="M10.4408 6.58164C11.6383 7.51587 11.8516 9.24395 10.9174 10.4414C9.9832 11.6389 8.25512 11.8523 7.05765 10.918C5.86019 9.98382 5.64679 8.25574 6.58102 7.05828C7.51524 5.86081 9.24332 5.64742 10.4408 6.58164ZM9.95361 7.84272C9.92276 7.74387 9.83289 7.67398 9.72956 7.66791L9.68382 7.66939L9.64046 7.67892C9.54146 7.7098 9.47094 7.8002 9.46506 7.90374L9.46636 7.94811L9.60965 9.10281L7.71726 7.62766L7.67944 7.60311C7.58759 7.55494 7.47446 7.56891 7.39709 7.63798L7.36637 7.67099L7.34182 7.70881C7.29366 7.80066 7.30763 7.91379 7.37669 7.99117L7.4097 8.02188L9.30286 9.49763L8.14603 9.64048L8.10268 9.65001C7.98957 9.6853 7.91362 9.79746 7.92858 9.91921C7.94362 10.041 8.044 10.132 8.16233 10.1387L8.2073 10.1367L9.9835 9.91593L10.0131 9.90809L10.0547 9.88974L10.0906 9.86444L10.1136 9.8428L10.1413 9.80739L10.1566 9.77974L10.1652 9.7592L10.1769 9.71879L10.1809 9.69464L10.1805 9.66338L9.96254 7.88684L9.95361 7.84272Z" fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_207">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModifiersIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 4.5V3.23607C2 3.08082 2.03615 2.92771 2.10558 2.78886L2.5 2H5L5.5 3H10.5C10.7761 3 11 3.22386 11 3.5V4.5V9C11 9.5523 10.5523 10 10 10H9" stroke="white" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M8.61805 4.5H1.15458C0.824894 4.5 0.585449 4.81349 0.672199 5.13155L1.79898 9.2631C1.91764 9.6982 2.3128 10 2.76375 10H9.84535C10.175 10 10.4145 9.6865 10.3277 9.36845L9.10045 4.86844C9.0411 4.65091 8.84355 4.5 8.61805 4.5Z" stroke="white" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function DeleteIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_241)">
|
||||
<path d="M4.70588 5.32353V9.02941M7.17647 5.32353V9.02941M9.64706 2.85294V10.2647C9.64706 10.947 9.09402 11.5 8.41177 11.5H3.47059C2.78835 11.5 2.23529 10.947 2.23529 10.2647V2.85294M1 2.85294H10.8824M7.79412 2.85294V2.23529C7.79412 1.55306 7.24108 1 6.55882 1H5.32353C4.6413 1 4.08824 1.55306 4.08824 2.23529V2.85294" stroke="white" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_241">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function MoveIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_241)">
|
||||
<path d="M4.70588 5.32353V9.02941M7.17647 5.32353V9.02941M9.64706 2.85294V10.2647C9.64706 10.947 9.09402 11.5 8.41177 11.5H3.47059C2.78835 11.5 2.23529 10.947 2.23529 10.2647V2.85294M1 2.85294H10.8824M7.79412 2.85294V2.23529C7.79412 1.55306 7.24108 1 6.55882 1H5.32353C4.6413 1 4.08824 1.55306 4.08824 2.23529V2.85294" stroke="white" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_241">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function RotateIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.99998 1.31111H4.7251L3.88539 0.471406L4.3568 0L6.00124 1.64444L4.37502 3.27067L3.90361 2.79927L4.72511 1.97777H3.99998C2.89541 1.97777 1.99998 2.87321 1.99998 3.97777H1.33331C1.33331 2.50501 2.52722 1.31111 3.99998 1.31111ZM3.99998 5.33333C3.99998 4.59696 4.59693 4 5.33331 4H10.6667C11.4031 4 12 4.59696 12 5.33333V10.6667C12 11.4031 11.4031 12 10.6667 12H5.33331C4.59693 12 3.99998 11.4031 3.99998 10.6667V5.33333ZM5.33331 4.66667H10.6667C11.0349 4.66667 11.3333 4.96514 11.3333 5.33333V10.6667C11.3333 11.0349 11.0349 11.3333 10.6667 11.3333H5.33331C4.96513 11.3333 4.66664 11.0349 4.66664 10.6667V5.33333C4.66664 4.96514 4.96513 4.66667 5.33331 4.66667Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function GroupIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.99998 1.31111H4.7251L3.88539 0.471406L4.3568 0L6.00124 1.64444L4.37502 3.27067L3.90361 2.79927L4.72511 1.97777H3.99998C2.89541 1.97777 1.99998 2.87321 1.99998 3.97777H1.33331C1.33331 2.50501 2.52722 1.31111 3.99998 1.31111ZM3.99998 5.33333C3.99998 4.59696 4.59693 4 5.33331 4H10.6667C11.4031 4 12 4.59696 12 5.33333V10.6667C12 11.4031 11.4031 12 10.6667 12H5.33331C4.59693 12 3.99998 11.4031 3.99998 10.6667V5.33333ZM5.33331 4.66667H10.6667C11.0349 4.66667 11.3333 4.96514 11.3333 5.33333V10.6667C11.3333 11.0349 11.0349 11.3333 10.6667 11.3333H5.33331C4.96513 11.3333 4.66664 11.0349 4.66664 10.6667V5.33333C4.66664 4.96514 4.96513 4.66667 5.33331 4.66667Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrayIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.09998" y="0.5" width="1.66667" height="5.66667" rx="0.5" stroke="white" />
|
||||
<rect x="5.09998" y="3.16797" width="1.66667" height="5.66667" rx="0.5" stroke="white" />
|
||||
<rect x="9.09998" y="5.83203" width="1.66667" height="5.66667" rx="0.5" stroke="white" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function SubMenuIcon() {
|
||||
return (
|
||||
<svg width="4" height="6" viewBox="0 0 4 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 6V0L4 3L0.5 6Z" fill="white" />
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
useLoadingProgress,
|
||||
useRenameModeStore,
|
||||
useSaveVersion,
|
||||
useSelectedAssets,
|
||||
useSelectedComment,
|
||||
useSelectedFloorItem,
|
||||
useSocketStore,
|
||||
@@ -58,6 +59,7 @@ function MainScene() {
|
||||
const { setFloatingWidget } = useFloatingWidget();
|
||||
const { clearComparisonProduct } = useComparisonProduct();
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { selectedAssets,setSelectedAssets } = useSelectedAssets();
|
||||
const { assetStore, productStore } = useSceneContext();
|
||||
const { products } = productStore();
|
||||
const { setName } = assetStore();
|
||||
@@ -97,18 +99,40 @@ function MainScene() {
|
||||
|
||||
const handleObjectRename = async (newName: string) => {
|
||||
if (!projectId) return
|
||||
let response = await setAssetsApi({
|
||||
modelUuid: selectedFloorItem.userData.modelUuid,
|
||||
modelName: newName,
|
||||
projectId
|
||||
});
|
||||
selectedFloorItem.userData = {
|
||||
...selectedFloorItem.userData,
|
||||
modelName: newName
|
||||
};
|
||||
setSelectedFloorItem(selectedFloorItem);
|
||||
setIsRenameMode(false);
|
||||
setName(selectedFloorItem.userData.modelUuid, response.modelName);
|
||||
if (selectedFloorItem) {
|
||||
console.log('selectedFloorItem.userData.modelUuid: ', selectedFloorItem.userData.modelUuid);
|
||||
console.log(' newName: ', newName);
|
||||
console.log('projectId: ', projectId);
|
||||
setAssetsApi({
|
||||
modelUuid: selectedFloorItem.userData.modelUuid,
|
||||
modelName: newName,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || ''
|
||||
}).then(() => {
|
||||
selectedFloorItem.userData = {
|
||||
...selectedFloorItem.userData,
|
||||
modelName: newName
|
||||
};
|
||||
setSelectedFloorItem(selectedFloorItem);
|
||||
setIsRenameMode(false);
|
||||
setName(selectedFloorItem.userData.modelUuid, newName);
|
||||
})
|
||||
} else if (selectedAssets.length === 1) {
|
||||
setAssetsApi({
|
||||
modelUuid: selectedAssets[0].userData.modelUuid,
|
||||
modelName: newName,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || ''
|
||||
}).then(() => {
|
||||
selectedAssets[0].userData = {
|
||||
...selectedAssets[0].userData,
|
||||
modelName: newName
|
||||
};
|
||||
setSelectedAssets(selectedAssets);
|
||||
setIsRenameMode(false);
|
||||
setName(selectedAssets[0].userData.modelUuid, newName);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -135,7 +159,7 @@ function MainScene() {
|
||||
{(isPlaying) &&
|
||||
activeModule !== "simulation" && <ControlsPlayer />}
|
||||
|
||||
{isRenameMode && selectedFloorItem?.userData.modelName && <RenameTooltip name={selectedFloorItem?.userData.modelName} onSubmit={handleObjectRename} />}
|
||||
{isRenameMode && (selectedFloorItem?.userData.modelName || selectedAssets.length === 1) && <RenameTooltip name={selectedFloorItem?.userData.modelName || selectedAssets[0].userData.modelName} onSubmit={handleObjectRename} />}
|
||||
{/* remove this later */}
|
||||
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}
|
||||
</>
|
||||
@@ -188,7 +212,7 @@ function MainScene() {
|
||||
{activeModule !== "market" && !selectedUser && <Footer />}
|
||||
|
||||
<VersionSaved />
|
||||
{(commentPositionState !== null || selectedComment !== null) && <ThreadChat/>}
|
||||
{(commentPositionState !== null || selectedComment !== null) && <ThreadChat />}
|
||||
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -17,255 +17,242 @@ import SkeletonUI from "../../templates/SkeletonUI";
|
||||
// -------------------------------------
|
||||
|
||||
interface AssetProp {
|
||||
filename: string;
|
||||
thumbnail?: string;
|
||||
category: string;
|
||||
description?: string;
|
||||
tags: string;
|
||||
url?: string;
|
||||
uploadDate?: number;
|
||||
isArchieve?: boolean;
|
||||
animated?: boolean;
|
||||
price?: number;
|
||||
CreatedBy?: string;
|
||||
filename: string;
|
||||
thumbnail?: string;
|
||||
category: string;
|
||||
description?: string;
|
||||
tags: string;
|
||||
url?: string;
|
||||
uploadDate?: number;
|
||||
isArchieve?: boolean;
|
||||
animated?: boolean;
|
||||
price?: number;
|
||||
CreatedBy?: string;
|
||||
}
|
||||
interface CategoryListProp {
|
||||
assetImage?: string;
|
||||
assetName?: string;
|
||||
categoryImage: string;
|
||||
category: string;
|
||||
assetImage?: string;
|
||||
assetName?: string;
|
||||
categoryImage: string;
|
||||
category: string;
|
||||
}
|
||||
const Assets: React.FC = () => {
|
||||
const { setSelectedItem } = useSelectedItem();
|
||||
const [searchValue, setSearchValue] = useState<string>("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [categoryAssets, setCategoryAssets] = useState<AssetProp[]>([]);
|
||||
const [filtereredAssets, setFiltereredAssets] = useState<AssetProp[]>([]);
|
||||
const [categoryList, setCategoryList] = useState<CategoryListProp[]>([]);
|
||||
const [isLoading, setisLoading] = useState<boolean>(false); // Loading state for assets
|
||||
const { setSelectedItem } = useSelectedItem();
|
||||
const [searchValue, setSearchValue] = useState<string>("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [categoryAssets, setCategoryAssets] = useState<AssetProp[]>([]);
|
||||
const [filtereredAssets, setFiltereredAssets] = useState<AssetProp[]>([]);
|
||||
const [categoryList, setCategoryList] = useState<CategoryListProp[]>([]);
|
||||
const [isLoading, setisLoading] = useState<boolean>(false); // Loading state for assets
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
const searchTerm = value.toLowerCase();
|
||||
setSearchValue(value);
|
||||
if (searchTerm.trim() === "" && !selectedCategory) {
|
||||
setCategoryAssets([]);
|
||||
return;
|
||||
}
|
||||
const filteredModels = filtereredAssets?.filter((model) => {
|
||||
if (!model?.tags || !model?.filename || !model?.category) return false;
|
||||
if (searchTerm.startsWith(":") && searchTerm.length > 1) {
|
||||
const tagSearchTerm = searchTerm.slice(1);
|
||||
return model.tags.toLowerCase().includes(tagSearchTerm);
|
||||
} else if (selectedCategory) {
|
||||
return (
|
||||
model.category
|
||||
.toLowerCase()
|
||||
.includes(selectedCategory.toLowerCase()) &&
|
||||
model.filename.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
} else {
|
||||
return model.filename.toLowerCase().includes(searchTerm);
|
||||
}
|
||||
});
|
||||
const handleSearchChange = (value: string) => {
|
||||
const searchTerm = value.toLowerCase();
|
||||
setSearchValue(value);
|
||||
if (searchTerm.trim() === "" && !selectedCategory) {
|
||||
setCategoryAssets([]);
|
||||
return;
|
||||
}
|
||||
const filteredModels = filtereredAssets?.filter((model) => {
|
||||
if (!model?.tags || !model?.filename || !model?.category) return false;
|
||||
if (searchTerm.startsWith(":") && searchTerm.length > 1) {
|
||||
const tagSearchTerm = searchTerm.slice(1);
|
||||
return model.tags.toLowerCase().includes(tagSearchTerm);
|
||||
} else if (selectedCategory) {
|
||||
return (
|
||||
model.category
|
||||
.toLowerCase()
|
||||
.includes(selectedCategory.toLowerCase()) &&
|
||||
model.filename.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
} else {
|
||||
return model.filename.toLowerCase().includes(searchTerm);
|
||||
}
|
||||
});
|
||||
|
||||
setCategoryAssets(filteredModels);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const filteredAssets = async () => {
|
||||
try {
|
||||
const filt = await fetchAssets();
|
||||
setFiltereredAssets(filt);
|
||||
} catch {
|
||||
echo.error("Filter asset not found");
|
||||
}
|
||||
setCategoryAssets(filteredModels);
|
||||
};
|
||||
filteredAssets();
|
||||
}, [categoryAssets]);
|
||||
|
||||
useEffect(() => {
|
||||
setCategoryList([
|
||||
{ category: "Fenestration", categoryImage: feneration },
|
||||
{ category: "Vehicles", categoryImage: vehicle },
|
||||
{ category: "Workstation", categoryImage: workStation },
|
||||
{ category: "Machines", categoryImage: machines },
|
||||
{ category: "Workers", categoryImage: worker },
|
||||
{ category: "Storage", categoryImage: storage },
|
||||
{ category: "Safety", categoryImage: safety },
|
||||
{ category: "Office", categoryImage: office },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const fetchCategoryAssets = async (asset: any) => {
|
||||
setisLoading(true);
|
||||
setSelectedCategory(asset);
|
||||
try {
|
||||
const res = await getCategoryAsset(asset);
|
||||
setCategoryAssets(res);
|
||||
setFiltereredAssets(res);
|
||||
setisLoading(false); // End loading
|
||||
// eslint-disable-next-line
|
||||
} catch (error) {
|
||||
echo.error("failed to fetch assets");
|
||||
setisLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="assets-container-main">
|
||||
<Search onChange={handleSearchChange} />
|
||||
<div className="assets-list-section">
|
||||
<section>
|
||||
{(() => {
|
||||
if (isLoading) {
|
||||
return <SkeletonUI type="asset" />; // Show skeleton when loading
|
||||
useEffect(() => {
|
||||
const filteredAssets = async () => {
|
||||
try {
|
||||
const filt = await fetchAssets();
|
||||
setFiltereredAssets(filt);
|
||||
} catch {
|
||||
echo.error("Filter asset not found");
|
||||
}
|
||||
if (searchValue) {
|
||||
return (
|
||||
<div className="assets-result">
|
||||
<div className="assets-wrapper">
|
||||
<div className="searched-content">
|
||||
<p>Results for {searchValue}</p>
|
||||
</div>
|
||||
<div className="assets-container">
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset.filename}`}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
type:
|
||||
asset.type === "undefined"
|
||||
? undefined
|
||||
: asset.type
|
||||
});
|
||||
}}
|
||||
/>
|
||||
};
|
||||
filteredAssets();
|
||||
}, [categoryAssets]);
|
||||
|
||||
<div className="asset-name">
|
||||
{asset.filename
|
||||
.split("_")
|
||||
.map(
|
||||
(word: any) =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
)
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
setCategoryList([
|
||||
{ category: "Fenestration", categoryImage: feneration },
|
||||
{ category: "Vehicles", categoryImage: vehicle },
|
||||
{ category: "Workstation", categoryImage: workStation },
|
||||
{ category: "Machines", categoryImage: machines },
|
||||
{ category: "Workers", categoryImage: worker },
|
||||
{ category: "Storage", categoryImage: storage },
|
||||
{ category: "Safety", categoryImage: safety },
|
||||
{ category: "Office", categoryImage: office },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
if (selectedCategory) {
|
||||
return (
|
||||
<div className="assets-wrapper">
|
||||
<h2>
|
||||
{selectedCategory}
|
||||
<button
|
||||
className="back-button"
|
||||
id="asset-backButtom"
|
||||
onClick={() => {
|
||||
setSelectedCategory(null);
|
||||
setCategoryAssets([]);
|
||||
}}
|
||||
>
|
||||
← Back
|
||||
</button>
|
||||
</h2>
|
||||
<div className="assets-container">
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset}`}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
type:
|
||||
asset.type === "undefined"
|
||||
? undefined
|
||||
: asset.type,
|
||||
category: asset.category,
|
||||
subCategory: asset.subCategory
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="asset-name">
|
||||
{asset.filename
|
||||
.split("_")
|
||||
.map(
|
||||
(word: any) =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
)
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{categoryAssets.length === 0 && (
|
||||
<div className="no-asset">
|
||||
🚧 The asset shelf is empty. We're working on filling it
|
||||
up!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const fetchCategoryAssets = async (asset: any) => {
|
||||
setisLoading(true);
|
||||
setSelectedCategory(asset);
|
||||
try {
|
||||
const res = await getCategoryAsset(asset);
|
||||
setCategoryAssets(res);
|
||||
setFiltereredAssets(res);
|
||||
setisLoading(false); // End loading
|
||||
// eslint-disable-next-line
|
||||
} catch (error) {
|
||||
echo.error("failed to fetch assets");
|
||||
setisLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="assets-wrapper">
|
||||
<h2>Categories</h2>
|
||||
<div className="categories-container">
|
||||
{Array.from(
|
||||
new Set(categoryList.map((asset) => asset.category))
|
||||
).map((category, index) => {
|
||||
const categoryInfo = categoryList.find(
|
||||
(asset) => asset.category === category
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={`${index}-${category}`}
|
||||
className="category"
|
||||
id={category}
|
||||
onClick={() => fetchCategoryAssets(category)}
|
||||
>
|
||||
<img
|
||||
src={categoryInfo?.categoryImage ?? ""}
|
||||
alt={category}
|
||||
className="category-image"
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="category-name">{category}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="assets-container-main">
|
||||
<Search onChange={handleSearchChange} />
|
||||
<div className="assets-list-section">
|
||||
<section>
|
||||
{(() => {
|
||||
if (isLoading) {
|
||||
return <SkeletonUI type="asset" />; // Show skeleton when loading
|
||||
}
|
||||
if (searchValue) {
|
||||
return (
|
||||
<div className="assets-result">
|
||||
<div className="assets-wrapper">
|
||||
<div className="searched-content">
|
||||
<p>Results for {searchValue}</p>
|
||||
</div>
|
||||
<div className="assets-container">
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset.filename}`}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
type: asset.type === "undefined" ? undefined : asset.type
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="asset-name">
|
||||
{asset.filename
|
||||
.split("_")
|
||||
.map(
|
||||
(word: any) =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
)
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedCategory) {
|
||||
return (
|
||||
<div className="assets-wrapper">
|
||||
<h2>
|
||||
{selectedCategory}
|
||||
<button
|
||||
className="back-button"
|
||||
id="asset-backButtom"
|
||||
onClick={() => {
|
||||
setSelectedCategory(null);
|
||||
setCategoryAssets([]);
|
||||
}}
|
||||
>
|
||||
← Back
|
||||
</button>
|
||||
</h2>
|
||||
<div className="assets-container">
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset}`}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
type: asset.type === "undefined" ? undefined : asset.type,
|
||||
category: asset.category,
|
||||
subType: asset.subType
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="asset-name">
|
||||
{asset.filename.split("_").map((word: any) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{categoryAssets.length === 0 && (
|
||||
<div className="no-asset">
|
||||
🚧 The asset shelf is empty. We're working on filling it up!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="assets-wrapper">
|
||||
<h2>Categories</h2>
|
||||
<div className="categories-container">
|
||||
{Array.from(
|
||||
new Set(categoryList.map((asset) => asset.category))
|
||||
).map((category, index) => {
|
||||
const categoryInfo = categoryList.find(
|
||||
(asset) => asset.category === category
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={`${index}-${category}`}
|
||||
className="category"
|
||||
id={category}
|
||||
onClick={() => fetchCategoryAssets(category)}
|
||||
>
|
||||
<img
|
||||
src={categoryInfo?.categoryImage ?? ""}
|
||||
alt={category}
|
||||
className="category-image"
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="category-name">{category}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Assets;
|
||||
|
||||
@@ -1,23 +1,62 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Search from "../../ui/inputs/Search";
|
||||
import DropDownList from "../../ui/list/DropDownList";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
import { isPointInsidePolygon } from "../../../functions/isPointInsidePolygon";
|
||||
|
||||
interface ZoneData {
|
||||
id: string;
|
||||
name: string;
|
||||
assets: { id: string; name: string; position?: []; rotation?: {} }[];
|
||||
}
|
||||
|
||||
const Outline: React.FC = () => {
|
||||
const [searchValue, setSearchValue] = useState<string>("");
|
||||
const [zoneDataList, setZoneDataList] = useState<ZoneData[]>([]);
|
||||
const [buildingsList, setBuildingsList] = useState<{ id: string; name: string }[]>([]);
|
||||
const [isLayersOpen, setIsLayersOpen] = useState(true);
|
||||
const [isBuildingsOpen, setIsBuildingsOpen] = useState(false);
|
||||
const [isZonesOpen, setIsZonesOpen] = useState(false);
|
||||
const { assetStore, zoneStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { zones } = zoneStore();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updatedZoneList: ZoneData[] = zones?.map((zone: any) => {
|
||||
const polygon2D = zone.points.map((p: any) => [p.position[0], p.position[2]]);
|
||||
const assetsInZone = assets.filter((item: any) => {
|
||||
const [x, , z] = item.position;
|
||||
return isPointInsidePolygon([x, z], polygon2D as [number, number][]);
|
||||
})
|
||||
.map((item: any) => ({
|
||||
id: item.modelUuid,
|
||||
name: item.modelName,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
}));
|
||||
|
||||
|
||||
return {
|
||||
id: zone.zoneUuid,
|
||||
name: zone.zoneName,
|
||||
assets: assetsInZone,
|
||||
};
|
||||
});
|
||||
|
||||
setZoneDataList(updatedZoneList);
|
||||
}, [zones, assets]);
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchValue(value);
|
||||
// console.log(value); // Log the search value if needed
|
||||
};
|
||||
|
||||
const dropdownItems = [
|
||||
{ id: "1", name: "Ground Floor", active: true },
|
||||
// { id: "2", name: "Floor 1" },
|
||||
]; // Example dropdown items
|
||||
const dropdownItems = [{ id: "1", name: "Ground Floor" }];
|
||||
|
||||
return (
|
||||
<div className="outline-container">
|
||||
<Search onChange={handleSearchChange} />
|
||||
|
||||
{searchValue ? (
|
||||
<div className="searched-content">
|
||||
<p>Results for "{searchValue}"</p>
|
||||
@@ -28,7 +67,8 @@ const Outline: React.FC = () => {
|
||||
<DropDownList
|
||||
value="Layers"
|
||||
items={dropdownItems}
|
||||
defaultOpen={true}
|
||||
isOpen={isLayersOpen}
|
||||
onToggle={() => setIsLayersOpen((prev) => !prev)}
|
||||
showKebabMenu={false}
|
||||
showFocusIcon={true}
|
||||
remove
|
||||
@@ -36,10 +76,18 @@ const Outline: React.FC = () => {
|
||||
</section>
|
||||
<section className="outline-section overflow">
|
||||
<DropDownList
|
||||
value="Scene"
|
||||
items={dropdownItems}
|
||||
defaultOpen={true}
|
||||
listType="outline"
|
||||
value="Buildings"
|
||||
items={buildingsList}
|
||||
isOpen={isBuildingsOpen}
|
||||
onToggle={() => setIsBuildingsOpen((prev) => !prev)}
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
/>
|
||||
<DropDownList
|
||||
value="Zones"
|
||||
items={zoneDataList}
|
||||
isOpen={isZonesOpen}
|
||||
onToggle={() => setIsZonesOpen((prev) => !prev)}
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
/>
|
||||
|
||||
@@ -42,11 +42,11 @@ const ZoneProperties: React.FC = () => {
|
||||
|
||||
|
||||
let response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || "");
|
||||
// console.log('response: ', response);
|
||||
//
|
||||
if (response.message === "zone updated") {
|
||||
setEdit(false);
|
||||
} else {
|
||||
// console.log(response);
|
||||
//
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to set zone view");
|
||||
@@ -75,7 +75,7 @@ const ZoneProperties: React.FC = () => {
|
||||
// )
|
||||
// );
|
||||
} else {
|
||||
// console.log(response?.message);
|
||||
//
|
||||
}
|
||||
}
|
||||
function handleVectorChange(
|
||||
@@ -85,7 +85,7 @@ const ZoneProperties: React.FC = () => {
|
||||
setSelectedZone((prev) => ({ ...prev, [key]: newValue }));
|
||||
}
|
||||
const checkZoneNameDuplicate = (name: string) => {
|
||||
console.log('zones: ', zones);
|
||||
|
||||
return zones.some(
|
||||
(zone: any) =>
|
||||
zone.zoneName?.trim().toLowerCase() === name?.trim().toLowerCase() &&
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useProductContext } from "../../../../../modules/simulation/products/pr
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useVersionContext } from "../../../../../modules/builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../../../modules/scene/sceneContext";
|
||||
import CraneMechanics from "./mechanics/craneMechanics";
|
||||
|
||||
const EventProperties: React.FC = () => {
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
@@ -63,6 +64,8 @@ const EventProperties: React.FC = () => {
|
||||
return "storageUnit";
|
||||
case "human":
|
||||
return "human";
|
||||
case "crane":
|
||||
return "crane";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -83,6 +86,7 @@ const EventProperties: React.FC = () => {
|
||||
{assetType === "machine" && <MachineMechanics />}
|
||||
{assetType === "storageUnit" && <StorageMechanics />}
|
||||
{assetType === "human" && <HumanMechanics />}
|
||||
{assetType === "crane" && <CraneMechanics />}
|
||||
</>
|
||||
)}
|
||||
{!currentEventData && selectedEventSphere && (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const DefaultAction:React.FC = () => {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default DefaultAction;
|
||||
import React from "react";
|
||||
|
||||
const DefaultAction:React.FC = () => {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default DefaultAction;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
|
||||
const DespawnAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DespawnAction;
|
||||
import React from "react";
|
||||
|
||||
const DespawnAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DespawnAction;
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
interface PickAndPlaceActionProps {
|
||||
clearPoints: () => void;
|
||||
}
|
||||
|
||||
const PickAndPlaceAction: React.FC<PickAndPlaceActionProps> = ({
|
||||
clearPoints,
|
||||
}) => {
|
||||
return (
|
||||
<div className="selected-actions-list">
|
||||
<div className="value-field-container">
|
||||
<div className="label">Reset</div>
|
||||
<button
|
||||
id="pick-and-place-action-clear-button"
|
||||
type="button"
|
||||
className="regularDropdown-container"
|
||||
onClick={() => {
|
||||
clearPoints();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickAndPlaceAction;
|
||||
import React from "react";
|
||||
|
||||
interface PickAndPlaceActionProps {
|
||||
clearPoints: () => void;
|
||||
}
|
||||
|
||||
const PickAndPlaceAction: React.FC<PickAndPlaceActionProps> = ({
|
||||
clearPoints,
|
||||
}) => {
|
||||
return (
|
||||
<div className="selected-actions-list">
|
||||
<div className="value-field-container">
|
||||
<div className="label">Reset</div>
|
||||
<button
|
||||
id="pick-and-place-action-clear-button"
|
||||
type="button"
|
||||
className="regularDropdown-container"
|
||||
onClick={() => {
|
||||
clearPoints();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickAndPlaceAction;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
interface WorkerActionProps {
|
||||
loadCount: {
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
defaultValue: string,
|
||||
disabled: false,
|
||||
onChange: (value: number) => void;
|
||||
};
|
||||
}
|
||||
|
||||
const WorkerAction: React.FC<WorkerActionProps> = ({
|
||||
loadCount
|
||||
}) => {
|
||||
return (
|
||||
<div className="pillarJib-action-container">
|
||||
{loadCount && (
|
||||
<InputWithDropDown
|
||||
label="Max Load Count"
|
||||
value={loadCount.value.toString()}
|
||||
min={loadCount.min}
|
||||
max={loadCount.max}
|
||||
disabled={loadCount.disabled}
|
||||
defaultValue={loadCount.defaultValue}
|
||||
step={loadCount.step}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={(value) => loadCount.onChange(parseInt(value))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkerAction;
|
||||
@@ -1,48 +1,48 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import SwapAction from "./SwapAction";
|
||||
|
||||
interface ProcessActionProps {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
swapOptions: string[];
|
||||
swapDefaultOption: string;
|
||||
onSwapSelect: (value: string) => void;
|
||||
}
|
||||
|
||||
const ProcessAction: React.FC<ProcessActionProps> = ({
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
defaultValue,
|
||||
onChange,
|
||||
swapOptions,
|
||||
swapDefaultOption,
|
||||
onSwapSelect,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Process Time"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<SwapAction
|
||||
options={swapOptions}
|
||||
defaultOption={swapDefaultOption}
|
||||
onSelect={onSwapSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProcessAction;
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import SwapAction from "./SwapAction";
|
||||
|
||||
interface ProcessActionProps {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
swapOptions: string[];
|
||||
swapDefaultOption: string;
|
||||
onSwapSelect: (value: string) => void;
|
||||
}
|
||||
|
||||
const ProcessAction: React.FC<ProcessActionProps> = ({
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
defaultValue,
|
||||
onChange,
|
||||
swapOptions,
|
||||
swapDefaultOption,
|
||||
onSwapSelect,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Process Time"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<SwapAction
|
||||
options={swapOptions}
|
||||
defaultOption={swapDefaultOption}
|
||||
onSelect={onSwapSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProcessAction;
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
|
||||
interface SpawnActionProps {
|
||||
onChangeInterval: (value: string) => void;
|
||||
onChangeCount: (value: string) => void;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
intervalValue: string;
|
||||
countValue: string;
|
||||
intervalMin: number;
|
||||
intervalMax: number;
|
||||
intervalDefaultValue: string;
|
||||
countMin: number;
|
||||
countMax: number;
|
||||
countDefaultValue: string;
|
||||
}
|
||||
|
||||
const SpawnAction: React.FC<SpawnActionProps> = ({
|
||||
onChangeInterval,
|
||||
onChangeCount,
|
||||
defaultOption,
|
||||
options,
|
||||
onSelect,
|
||||
intervalValue,
|
||||
countValue,
|
||||
intervalMin,
|
||||
intervalMax,
|
||||
intervalDefaultValue,
|
||||
countMin,
|
||||
countMax,
|
||||
countDefaultValue,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Spawn interval"
|
||||
value={intervalValue}
|
||||
min={intervalMin}
|
||||
step={1}
|
||||
defaultValue={intervalDefaultValue}
|
||||
max={intervalMax}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChangeInterval}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Spawn count"
|
||||
value={countValue}
|
||||
min={countMin}
|
||||
step={1}
|
||||
defaultValue={countDefaultValue}
|
||||
max={countMax}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChangeCount}
|
||||
/>
|
||||
{/* <PreviewSelectionWithUpload /> */}
|
||||
<LabledDropdown
|
||||
label="Presets"
|
||||
defaultOption={defaultOption}
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpawnAction;
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
|
||||
interface SpawnActionProps {
|
||||
onChangeInterval: (value: string) => void;
|
||||
onChangeCount: (value: string) => void;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
intervalValue: string;
|
||||
countValue: string;
|
||||
intervalMin: number;
|
||||
intervalMax: number;
|
||||
intervalDefaultValue: string;
|
||||
countMin: number;
|
||||
countMax: number;
|
||||
countDefaultValue: string;
|
||||
}
|
||||
|
||||
const SpawnAction: React.FC<SpawnActionProps> = ({
|
||||
onChangeInterval,
|
||||
onChangeCount,
|
||||
defaultOption,
|
||||
options,
|
||||
onSelect,
|
||||
intervalValue,
|
||||
countValue,
|
||||
intervalMin,
|
||||
intervalMax,
|
||||
intervalDefaultValue,
|
||||
countMin,
|
||||
countMax,
|
||||
countDefaultValue,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Spawn interval"
|
||||
value={intervalValue}
|
||||
min={intervalMin}
|
||||
step={1}
|
||||
defaultValue={intervalDefaultValue}
|
||||
max={intervalMax}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChangeInterval}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Spawn count"
|
||||
value={countValue}
|
||||
min={countMin}
|
||||
step={1}
|
||||
defaultValue={countDefaultValue}
|
||||
max={countMax}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChangeCount}
|
||||
/>
|
||||
{/* <PreviewSelectionWithUpload /> */}
|
||||
<LabledDropdown
|
||||
label="Presets"
|
||||
defaultOption={defaultOption}
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpawnAction;
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
|
||||
interface StorageActionProps {
|
||||
type: "store" | "spawn" | "default";
|
||||
value: string;
|
||||
min: number;
|
||||
max?: number;
|
||||
defaultValue: string;
|
||||
currentMaterialType: string;
|
||||
handleCapacityChange: (value: string) => void;
|
||||
handleMaterialTypeChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const StorageAction: React.FC<StorageActionProps> = ({ type, value, min, max, defaultValue, currentMaterialType, handleCapacityChange, handleMaterialTypeChange }) => {
|
||||
return (
|
||||
<>
|
||||
{type === 'store' &&
|
||||
<InputWithDropDown
|
||||
label="Storage Capacity"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={handleCapacityChange}
|
||||
/>
|
||||
}
|
||||
{type === 'spawn' &&
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Spawn Capacity"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={handleCapacityChange}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label={"Material Type"}
|
||||
defaultOption={currentMaterialType}
|
||||
options={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
onSelect={handleMaterialTypeChange}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageAction;
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
|
||||
interface StorageActionProps {
|
||||
type: "store" | "spawn" | "default";
|
||||
value: string;
|
||||
min: number;
|
||||
max?: number;
|
||||
defaultValue: string;
|
||||
currentMaterialType: string;
|
||||
handleCapacityChange: (value: string) => void;
|
||||
handleMaterialTypeChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const StorageAction: React.FC<StorageActionProps> = ({ type, value, min, max, defaultValue, currentMaterialType, handleCapacityChange, handleMaterialTypeChange }) => {
|
||||
return (
|
||||
<>
|
||||
{type === 'store' &&
|
||||
<InputWithDropDown
|
||||
label="Storage Capacity"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={handleCapacityChange}
|
||||
/>
|
||||
}
|
||||
{type === 'spawn' &&
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Spawn Capacity"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={handleCapacityChange}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label={"Material Type"}
|
||||
defaultOption={currentMaterialType}
|
||||
options={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
onSelect={handleMaterialTypeChange}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageAction;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
|
||||
interface SwapActionProps {
|
||||
onSelect: (option: string) => void;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
const SwapAction: React.FC<SwapActionProps> = ({
|
||||
onSelect,
|
||||
defaultOption,
|
||||
options,
|
||||
}) => {
|
||||
return (
|
||||
<PreviewSelectionWithUpload
|
||||
label="Presets"
|
||||
defaultOption={defaultOption}
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwapAction;
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
|
||||
interface SwapActionProps {
|
||||
onSelect: (option: string) => void;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
const SwapAction: React.FC<SwapActionProps> = ({
|
||||
onSelect,
|
||||
defaultOption,
|
||||
options,
|
||||
}) => {
|
||||
return (
|
||||
<PreviewSelectionWithUpload
|
||||
label="Presets"
|
||||
defaultOption={defaultOption}
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwapAction;
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
interface TravelActionProps {
|
||||
loadCapacity: {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
unloadDuration: {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
clearPoints: () => void;
|
||||
}
|
||||
|
||||
const TravelAction: React.FC<TravelActionProps> = ({
|
||||
loadCapacity,
|
||||
unloadDuration,
|
||||
clearPoints,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Load Capacity"
|
||||
value={loadCapacity.value}
|
||||
min={loadCapacity.min}
|
||||
max={loadCapacity.max}
|
||||
defaultValue={loadCapacity.defaultValue}
|
||||
step={1}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={loadCapacity.onChange}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Unload Duration"
|
||||
value={unloadDuration.value}
|
||||
min={unloadDuration.min}
|
||||
max={unloadDuration.max}
|
||||
defaultValue={unloadDuration.defaultValue}
|
||||
step={0.1}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={unloadDuration.onChange}
|
||||
/>
|
||||
<div className="selected-actions-list">
|
||||
<div className="value-field-container">
|
||||
<div className="label">Reset</div>
|
||||
<button
|
||||
id="rest-button"
|
||||
type="button"
|
||||
className="regularDropdown-container"
|
||||
onClick={() => {
|
||||
clearPoints();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TravelAction;
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
interface TravelActionProps {
|
||||
loadCapacity: {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
unloadDuration: {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
clearPoints: () => void;
|
||||
}
|
||||
|
||||
const TravelAction: React.FC<TravelActionProps> = ({
|
||||
loadCapacity,
|
||||
unloadDuration,
|
||||
clearPoints,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Load Capacity"
|
||||
value={loadCapacity.value}
|
||||
min={loadCapacity.min}
|
||||
max={loadCapacity.max}
|
||||
defaultValue={loadCapacity.defaultValue}
|
||||
step={1}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={loadCapacity.onChange}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Unload Duration"
|
||||
value={unloadDuration.value}
|
||||
min={unloadDuration.min}
|
||||
max={unloadDuration.max}
|
||||
defaultValue={unloadDuration.defaultValue}
|
||||
step={0.1}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={unloadDuration.onChange}
|
||||
/>
|
||||
<div className="selected-actions-list">
|
||||
<div className="value-field-container">
|
||||
<div className="label">Reset</div>
|
||||
<button
|
||||
id="rest-button"
|
||||
type="button"
|
||||
className="regularDropdown-container"
|
||||
onClick={() => {
|
||||
clearPoints();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TravelAction;
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { MathUtils } from "three";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import Trigger from "../trigger/Trigger";
|
||||
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useVersionContext } from "../../../../../../modules/builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
|
||||
|
||||
function CraneMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"pickAndDrop">("pickAndDrop");
|
||||
const [selectedPointData, setSelectedPointData] = useState<CranePointSchema | undefined>();
|
||||
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { productStore } = useSceneContext();
|
||||
const { getPointByUuid, updateAction, addAction, removeAction } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid,
|
||||
selectedEventData.selectedPoint
|
||||
) as CranePointSchema | undefined;
|
||||
|
||||
if (point?.actions) {
|
||||
setSelectedPointData(point);
|
||||
|
||||
if (point.actions.length > 0) {
|
||||
const firstAction = point.actions[0];
|
||||
setActiveOption(firstAction.actionType);
|
||||
setSelectedAction(firstAction.actionUuid, firstAction.actionName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}, [selectedEventData, selectedProduct]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName,
|
||||
productUuid,
|
||||
projectId,
|
||||
eventDatas: eventData,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedAction.actionId || !selectedPointData) return;
|
||||
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedAction.actionId,
|
||||
{ actionName: newName }
|
||||
);
|
||||
|
||||
const updatedActions = selectedPointData.actions.map(action =>
|
||||
action.actionUuid === selectedAction.actionId
|
||||
? { ...action, actionName: newName }
|
||||
: action
|
||||
);
|
||||
|
||||
setSelectedPointData({
|
||||
...selectedPointData,
|
||||
actions: updatedActions,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddAction = () => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
|
||||
const newAction = {
|
||||
actionUuid: MathUtils.generateUUID(),
|
||||
actionName: `Action ${selectedPointData.actions.length + 1}`,
|
||||
actionType: "pickAndDrop" as const,
|
||||
maxPickUpCount: 1,
|
||||
triggers: [] as TriggerSchema[],
|
||||
};
|
||||
|
||||
const event = addAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid,
|
||||
selectedEventData.selectedPoint,
|
||||
newAction
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
setSelectedPointData({
|
||||
...selectedPointData,
|
||||
actions: [...selectedPointData.actions, newAction],
|
||||
});
|
||||
setSelectedAction(newAction.actionUuid, newAction.actionName);
|
||||
};
|
||||
|
||||
const handleDeleteAction = (actionUuid: string) => {
|
||||
if (!selectedPointData) return;
|
||||
|
||||
const event = removeAction(
|
||||
selectedProduct.productUuid,
|
||||
actionUuid
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
const index = selectedPointData.actions.findIndex(a => a.actionUuid === actionUuid);
|
||||
const newActions = selectedPointData.actions.filter(a => a.actionUuid !== actionUuid);
|
||||
|
||||
setSelectedPointData({
|
||||
...selectedPointData,
|
||||
actions: newActions,
|
||||
});
|
||||
|
||||
if (selectedAction.actionId === actionUuid) {
|
||||
const nextAction = newActions[index] || newActions[index - 1];
|
||||
if (nextAction) {
|
||||
setSelectedAction(nextAction.actionUuid, nextAction.actionName);
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const availableActions = {
|
||||
defaultOption: "pickAndDrop",
|
||||
options: ["pickAndDrop"],
|
||||
};
|
||||
|
||||
const currentAction = selectedPointData?.actions.find(a => a.actionUuid === selectedAction.actionId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
<ActionsList
|
||||
selectedPointData={selectedPointData}
|
||||
multipleAction
|
||||
handleAddAction={handleAddAction}
|
||||
handleDeleteAction={handleDeleteAction}
|
||||
/>
|
||||
|
||||
{selectedAction.actionId && currentAction && (
|
||||
<div className="selected-actions-details">
|
||||
<div className="selected-actions-header">
|
||||
<RenameInput
|
||||
value={selectedAction.actionName || ""}
|
||||
canEdit={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="selected-actions-list">
|
||||
<LabledDropdown
|
||||
defaultOption={activeOption}
|
||||
options={availableActions.options}
|
||||
onSelect={() => { }}
|
||||
disabled={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger
|
||||
selectedPointData={selectedPointData as any}
|
||||
type={"RoboticArm"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CraneMechanics
|
||||
@@ -17,7 +17,7 @@ import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function HumanMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker");
|
||||
const [activeOption, setActiveOption] = useState<"worker" | "assembly" | "operator">("worker");
|
||||
const [speed, setSpeed] = useState("0.5");
|
||||
const [loadCount, setLoadCount] = useState(0);
|
||||
const [assemblyCount, setAssemblyCount] = useState(0);
|
||||
@@ -78,7 +78,7 @@ function HumanMechanics() {
|
||||
|
||||
const newCurrentAction = getActionByUuid(selectedProduct.productUuid, actionUuid);
|
||||
|
||||
if (newCurrentAction && (newCurrentAction.actionType === 'assembly' || newCurrentAction?.actionType === 'worker')) {
|
||||
if (newCurrentAction && (newCurrentAction.actionType === 'assembly' || newCurrentAction?.actionType === 'worker' || newCurrentAction?.actionType === "operator")) {
|
||||
if (!selectedAction.actionId) {
|
||||
setSelectedAction(newCurrentAction.actionUuid, newCurrentAction.actionName);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ function HumanMechanics() {
|
||||
const handleSelectActionType = (actionType: string) => {
|
||||
if (!selectedAction.actionId || !currentAction || !selectedPointData) return;
|
||||
|
||||
const updatedAction = { ...currentAction, actionType: actionType as "worker" | "assembly" };
|
||||
const updatedAction = { ...currentAction, actionType: actionType as "worker" | "assembly" | "operator" };
|
||||
const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action);
|
||||
const updatedPoint = { ...selectedPointData, actions: updatedActions };
|
||||
|
||||
@@ -397,12 +397,12 @@ function HumanMechanics() {
|
||||
<LabledDropdown
|
||||
label="Action Type"
|
||||
defaultOption={activeOption}
|
||||
options={["worker", "assembly"]}
|
||||
options={["worker", "assembly", "operator"]}
|
||||
onSelect={handleSelectActionType}
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
{currentAction.actionType === 'worker' &&
|
||||
{(currentAction.actionType === 'worker' || currentAction.actionType === "operator") &&
|
||||
<WorkerAction
|
||||
loadCapacity={{
|
||||
value: loadCapacity,
|
||||
|
||||
@@ -10,100 +10,101 @@ import { updateProject } from "../../services/dashboard/updateProject";
|
||||
import { getUserData } from "../../functions/getUserData";
|
||||
|
||||
const FileMenu: React.FC = () => {
|
||||
const [openMenu, setOpenMenu] = useState(false);
|
||||
const containerRef = useRef<HTMLButtonElement>(null);
|
||||
let clickTimeout: NodeJS.Timeout | null = null;
|
||||
const { projectName, setProjectName } = useProjectName();
|
||||
const { dashBoardSocket } = useSocketStore();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization, email } = getUserData();
|
||||
const [openMenu, setOpenMenu] = useState(false);
|
||||
const containerRef = useRef<HTMLButtonElement>(null);
|
||||
let clickTimeout: NodeJS.Timeout | null = null;
|
||||
const { projectName, setProjectName } = useProjectName();
|
||||
const { dashBoardSocket } = useSocketStore();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization, email } = getUserData();
|
||||
|
||||
const handleClick = () => {
|
||||
if (clickTimeout) return;
|
||||
setOpenMenu((prev) => !prev);
|
||||
clickTimeout = setTimeout(() => {
|
||||
clickTimeout = null;
|
||||
}, 800);
|
||||
const handleClick = () => {
|
||||
if (clickTimeout) return;
|
||||
setOpenMenu((prev) => !prev);
|
||||
clickTimeout = setTimeout(() => {
|
||||
clickTimeout = null;
|
||||
}, 800);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpenMenu(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpenMenu(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
const handleProjectRename = async (projectName: string) => {
|
||||
setProjectName(projectName);
|
||||
if (!projectId) return;
|
||||
|
||||
const handleProjectRename = async (projectName: string) => {
|
||||
setProjectName(projectName);
|
||||
if (!projectId) return
|
||||
// localStorage.setItem("projectName", newName);
|
||||
|
||||
// localStorage.setItem("projectName", newName);
|
||||
try {
|
||||
if (!email || !userId) return;
|
||||
|
||||
try {
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
if (!projects || !projects.Projects) return;
|
||||
// console.log('projects: ', projects);
|
||||
let projectUuid = projects.Projects.find(
|
||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||
);
|
||||
|
||||
if (!email || !userId) return;
|
||||
const updateProjects = {
|
||||
projectId: projectUuid?._id,
|
||||
organization,
|
||||
userId,
|
||||
projectName,
|
||||
thumbnail: undefined,
|
||||
};
|
||||
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
if (!projects || !projects.Projects) return;
|
||||
// console.log('projects: ', projects);
|
||||
let projectUuid = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId)
|
||||
// if (dashBoardSocket) {
|
||||
// const handleResponse = (data: any) => {
|
||||
// console.log('Project update response:', data);
|
||||
// dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up
|
||||
// };
|
||||
// dashBoardSocket.on("v1-project:response:update", handleResponse);
|
||||
// dashBoardSocket.emit("v1:project:update", updateProjects);
|
||||
// }
|
||||
|
||||
const updateProjects = {
|
||||
projectId: projectUuid,
|
||||
organization,
|
||||
userId,
|
||||
projectName,
|
||||
thumbnail: undefined
|
||||
}
|
||||
//API for projects rename
|
||||
|
||||
// if (dashBoardSocket) {
|
||||
// const handleResponse = (data: any) => {
|
||||
// console.log('Project update response:', data);
|
||||
// dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up
|
||||
// };
|
||||
// dashBoardSocket.on("v1-project:response:update", handleResponse);
|
||||
// dashBoardSocket.emit("v1:project:update", updateProjects);
|
||||
// }
|
||||
|
||||
//API for projects rename
|
||||
const updatedProjectName = await updateProject(
|
||||
projectId,
|
||||
userId,
|
||||
organization,
|
||||
undefined,
|
||||
projectName
|
||||
);
|
||||
//
|
||||
} catch (error) {
|
||||
console.error("Error updating project name:", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<button
|
||||
id="project-dropdown-button"
|
||||
className="project-dropdowm-container"
|
||||
ref={containerRef}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="project-name">
|
||||
<div className="icon">
|
||||
<ProjectIcon />
|
||||
</div>
|
||||
<RenameInput value={projectName} onRename={handleProjectRename} />
|
||||
</div>
|
||||
<div className="more-options-button">
|
||||
<ArrowIcon />
|
||||
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
const updatedProjectName = await updateProject(
|
||||
projectId,
|
||||
userId,
|
||||
organization,
|
||||
undefined,
|
||||
projectName
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error updating project name:", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<button
|
||||
id="project-dropdown-button"
|
||||
className="project-dropdowm-container"
|
||||
ref={containerRef}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="project-name">
|
||||
<div className="icon">
|
||||
<ProjectIcon />
|
||||
</div>
|
||||
<RenameInput value={projectName} onRename={handleProjectRename} />
|
||||
</div>
|
||||
<div className="more-options-button">
|
||||
<ArrowIcon />
|
||||
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileMenu;
|
||||
|
||||
@@ -1,30 +1,18 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import List from "./List";
|
||||
import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons";
|
||||
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
|
||||
interface DropDownListProps {
|
||||
value?: string; // Value to display in the DropDownList
|
||||
items?: { id: string; name: string }[]; // Items to display in the dropdown list
|
||||
showFocusIcon?: boolean; // Determines if the FocusIcon should be displayed
|
||||
showAddIcon?: boolean; // Determines if the AddIcon should be displayed
|
||||
showKebabMenu?: boolean; // Determines if the KebabMenuList should be displayed
|
||||
kebabMenuItems?: { id: string; name: string }[]; // Items for the KebabMenuList
|
||||
defaultOpen?: boolean; // Determines if the dropdown list should be open by default
|
||||
listType?: string; // Type of list to display
|
||||
value?: string;
|
||||
items?: { id: string; name: string }[];
|
||||
showFocusIcon?: boolean;
|
||||
showAddIcon?: boolean;
|
||||
showKebabMenu?: boolean;
|
||||
kebabMenuItems?: { id: string; name: string }[];
|
||||
remove?: boolean;
|
||||
}
|
||||
|
||||
interface Zone {
|
||||
zoneUuid: string;
|
||||
zoneName: string;
|
||||
points: [number, number, number][]; // polygon vertices
|
||||
}
|
||||
interface ZoneData {
|
||||
id: string;
|
||||
name: string;
|
||||
assets: { id: string; name: string; position?: []; rotation?: {} }[];
|
||||
isOpen: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
const DropDownList: React.FC<DropDownListProps> = ({
|
||||
@@ -38,76 +26,13 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
{ id: "Paths", name: "Paths" },
|
||||
{ id: "Zones", name: "Zones" },
|
||||
],
|
||||
defaultOpen = false,
|
||||
listType = "default",
|
||||
remove,
|
||||
isOpen,
|
||||
onToggle,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(defaultOpen);
|
||||
|
||||
const handleToggle = () => {
|
||||
setIsOpen((prev) => !prev); // Toggle the state
|
||||
};
|
||||
|
||||
const [zoneDataList, setZoneDataList] = useState<ZoneData[]>([]);
|
||||
const { assetStore, zoneStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { zones } = zoneStore()
|
||||
|
||||
|
||||
const isPointInsidePolygon = (
|
||||
point: [number, number],
|
||||
polygon: [number, number][]
|
||||
) => {
|
||||
let inside = false;
|
||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
const xi = polygon[i][0],
|
||||
zi = polygon[i][1];
|
||||
const xj = polygon[j][0],
|
||||
zj = polygon[j][1];
|
||||
|
||||
const intersect =
|
||||
// eslint-disable-next-line no-mixed-operators
|
||||
zi > point[1] !== zj > point[1] &&
|
||||
point[0] < ((xj - xi) * (point[1] - zi)) / (zj - zi + 0.000001) + xi;
|
||||
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const updatedZoneList: ZoneData[] = zones?.map((zone: any) => {
|
||||
const polygon2D = zone.points.map((p: [number, number, number]) => [
|
||||
p[0],
|
||||
p[2],
|
||||
]);
|
||||
|
||||
const assetsInZone = assets
|
||||
.filter((item: any) => {
|
||||
const [x, , z] = item.position;
|
||||
return isPointInsidePolygon([x, z], polygon2D as [number, number][]);
|
||||
})
|
||||
.map((item: any) => ({
|
||||
id: item.modelUuid,
|
||||
name: item.modelName,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
}));
|
||||
|
||||
return {
|
||||
id: zone.zoneUuid,
|
||||
name: zone.zoneName,
|
||||
assets: assetsInZone,
|
||||
};
|
||||
});
|
||||
|
||||
setZoneDataList(updatedZoneList);
|
||||
}, [zones, assets]);
|
||||
|
||||
return (
|
||||
<div className="dropdown-list-container">
|
||||
{/* eslint-disable-next-line */}
|
||||
<div className="head" onClick={handleToggle}>
|
||||
<div className="head" onClick={onToggle}>
|
||||
<div className="value">{value}</div>
|
||||
<div className="options">
|
||||
{showFocusIcon && (
|
||||
@@ -130,31 +55,15 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
title="collapse-btn"
|
||||
className="collapse-icon option"
|
||||
style={{ transform: isOpen ? "rotate(0deg)" : "rotate(-90deg)" }}
|
||||
// onClick={handleToggle}
|
||||
>
|
||||
<ArrowIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="lists-container">
|
||||
{listType === "default" && <List items={items} remove={remove} />}
|
||||
{listType === "outline" && (
|
||||
<>
|
||||
<DropDownList
|
||||
value="Buildings"
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
// items={zoneDataList}
|
||||
/>
|
||||
<DropDownList
|
||||
value="Zones"
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
items={zoneDataList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<List items={items} remove={remove} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -136,7 +136,6 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
}
|
||||
|
||||
async function handleZoneAssetName(newName: string) {
|
||||
|
||||
if (zoneAssetId?.id) {
|
||||
let response = await setAssetsApi({
|
||||
modelUuid: zoneAssetId.id,
|
||||
|
||||
187
app/src/components/ui/menu/contextMenu.tsx
Normal file
187
app/src/components/ui/menu/contextMenu.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import React from "react";
|
||||
import { ArrayIcon, CopyIcon, DeleteIcon, DublicateIcon, FlipXAxisIcon, FlipZAxisIcon, FocusIcon, GroupIcon, ModifiersIcon, MoveIcon, PasteIcon, RenameIcon, RotateIcon, SubMenuIcon, TransformIcon } from "../../icons/ContextMenuIcons";
|
||||
|
||||
type ContextMenuProps = {
|
||||
visibility: {
|
||||
rename: boolean;
|
||||
focus: boolean;
|
||||
flipX: boolean;
|
||||
flipZ: boolean;
|
||||
move: boolean;
|
||||
rotate: boolean;
|
||||
duplicate: boolean;
|
||||
copy: boolean;
|
||||
paste: boolean;
|
||||
modifier: boolean;
|
||||
group: boolean;
|
||||
array: boolean;
|
||||
delete: boolean;
|
||||
};
|
||||
onRename: () => void;
|
||||
onFocus: () => void;
|
||||
onFlipX: () => void;
|
||||
onFlipZ: () => void;
|
||||
onMove: () => void;
|
||||
onRotate: () => void;
|
||||
onDuplicate: () => void;
|
||||
onCopy: () => void;
|
||||
onPaste: () => void;
|
||||
onGroup: () => void;
|
||||
onArray: () => void;
|
||||
onDelete: () => void;
|
||||
};
|
||||
|
||||
const ContextMenu: React.FC<ContextMenuProps> = ({
|
||||
visibility,
|
||||
onRename,
|
||||
onFocus,
|
||||
onFlipX,
|
||||
onFlipZ,
|
||||
onMove,
|
||||
onRotate,
|
||||
onDuplicate,
|
||||
onCopy,
|
||||
onPaste,
|
||||
onGroup,
|
||||
onArray,
|
||||
onDelete,
|
||||
}) => {
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="context-menu">
|
||||
{visibility.rename && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onRename}>
|
||||
<div className="icon"><RenameIcon /></div>
|
||||
<span>Rename</span>
|
||||
</button>
|
||||
<span className="shortcut">F2</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.focus && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onFocus}>
|
||||
<div className="icon"><FocusIcon /></div>
|
||||
<span>Focus</span>
|
||||
</button>
|
||||
<span className="shortcut">F</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.flipX && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onFlipX}>
|
||||
<div className="icon"><FlipXAxisIcon /></div>
|
||||
<span>Flip to X axis</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{visibility.flipZ && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onFlipZ}>
|
||||
<div className="icon"><FlipZAxisIcon /></div>
|
||||
<span>Flip to Z axis</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{(visibility.move || visibility.rotate) && (
|
||||
<div className="menuItem">
|
||||
<button className="button">
|
||||
<div className="icon"><TransformIcon /></div>
|
||||
<span>Transform</span>
|
||||
</button>
|
||||
<div className="more"><SubMenuIcon /></div>
|
||||
<div className="submenu">
|
||||
{visibility.move && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onMove}>
|
||||
<div className="icon"><MoveIcon /></div>
|
||||
<span>Move</span>
|
||||
</button>
|
||||
<span className="shortcut">G</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.rotate && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onRotate}>
|
||||
<div className="icon"><RotateIcon /></div>
|
||||
<span>Rotate</span>
|
||||
</button>
|
||||
<span className="shortcut">R</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{visibility.duplicate && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onDuplicate}>
|
||||
<div className="icon"><DublicateIcon /></div>
|
||||
<span>Duplicate</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + D</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.copy && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onCopy}>
|
||||
<div className="icon"><CopyIcon /></div>
|
||||
<span>Copy Objects</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + C</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.paste && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onPaste}>
|
||||
<div className="icon"><PasteIcon /></div>
|
||||
<span>Paste Objects</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + V</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.modifier && (
|
||||
<div className="menuItem">
|
||||
<div className="icon"><ModifiersIcon /></div>
|
||||
<button className="button">Modifiers</button>
|
||||
</div>
|
||||
)}
|
||||
{(visibility.group || visibility.array) && (
|
||||
<div className="menuItem">
|
||||
<button className="button">Group / Array</button>
|
||||
<div className="submenu">
|
||||
{visibility.group && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onGroup}>
|
||||
<GroupIcon />
|
||||
<span>Group</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + G</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.array && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onArray}>
|
||||
<div className="icon"><ArrayIcon /></div>
|
||||
<span>Array</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{visibility.delete && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onDelete}>
|
||||
<div className="icon"><DeleteIcon /></div>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
<span className="shortcut">X</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
||||
20
app/src/functions/isPointInsidePolygon.ts
Normal file
20
app/src/functions/isPointInsidePolygon.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export const isPointInsidePolygon = (
|
||||
point: [number, number],
|
||||
polygon: [number, number][]
|
||||
) => {
|
||||
let inside = false;
|
||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
const xi = polygon[i][0],
|
||||
zi = polygon[i][1];
|
||||
const xj = polygon[j][0],
|
||||
zj = polygon[j][1];
|
||||
|
||||
const intersect =
|
||||
// eslint-disable-next-line no-mixed-operators
|
||||
zi > point[1] !== zj > point[1] &&
|
||||
point[0] < ((xj - xi) * (point[1] - zi)) / (zj - zi + 0.000001) + xi;
|
||||
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
};
|
||||
@@ -119,6 +119,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "vehicle",
|
||||
subType: item.eventData.subType as VehicleEventSchema['subType'] || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
|
||||
@@ -151,6 +152,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "transfer",
|
||||
subType: item.eventData.subType || '',
|
||||
speed: 1,
|
||||
points: item.eventData.points?.map((point: any, index: number) => ({
|
||||
uuid: point.uuid || THREE.MathUtils.generateUUID(),
|
||||
@@ -177,6 +179,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "machine",
|
||||
subType: item.eventData.subType || '',
|
||||
point: {
|
||||
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
|
||||
position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
|
||||
@@ -200,6 +203,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "roboticArm",
|
||||
subType: item.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
|
||||
@@ -228,6 +232,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "storageUnit",
|
||||
subType: item.eventData.subType || '',
|
||||
point: {
|
||||
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
|
||||
position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
|
||||
@@ -250,6 +255,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "human",
|
||||
subType: item.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
|
||||
@@ -270,6 +276,31 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
}
|
||||
}
|
||||
addEvent(humanEvent);
|
||||
} else if (item.eventData.type === 'Crane') {
|
||||
const craneEvent: CraneEventSchema = {
|
||||
modelUuid: item.modelUuid,
|
||||
modelName: item.modelName,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
state: "idle",
|
||||
type: "crane",
|
||||
subType: item.eventData.subType as CraneEventSchema['subType'] || 'pillarJib',
|
||||
point: {
|
||||
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(),
|
||||
position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0],
|
||||
rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
|
||||
actions: [
|
||||
{
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Action 1",
|
||||
actionType: "pickAndDrop",
|
||||
maxPickUpCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
addEvent(craneEvent);
|
||||
}
|
||||
} else {
|
||||
assets.push({
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as THREE from "three";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils";
|
||||
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
|
||||
import { Socket } from "socket.io-client";
|
||||
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||
import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator";
|
||||
|
||||
import { getUserData } from "../../../../functions/getUserData";
|
||||
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
|
||||
|
||||
async function addAssetModel(
|
||||
scene: THREE.Scene,
|
||||
@@ -165,7 +165,7 @@ async function handleModelLoad(
|
||||
|
||||
if (!data || !data.points) return;
|
||||
|
||||
const eventData: any = { type: selectedItem.type };
|
||||
const eventData: any = { type: selectedItem.type, subType: selectedItem.subType };
|
||||
|
||||
if (selectedItem.type === "Conveyor") {
|
||||
const ConveyorEvent: ConveyorEventSchema = {
|
||||
@@ -175,6 +175,7 @@ async function handleModelLoad(
|
||||
rotation: newFloorItem.rotation,
|
||||
state: "idle",
|
||||
type: "transfer",
|
||||
subType: selectedItem.subType || '',
|
||||
speed: 1,
|
||||
points: data.points.map((point: THREE.Vector3, index: number) => {
|
||||
const triggers: TriggerSchema[] = [];
|
||||
@@ -243,6 +244,7 @@ async function handleModelLoad(
|
||||
rotation: newFloorItem.rotation,
|
||||
state: "idle",
|
||||
type: "vehicle",
|
||||
subType: selectedItem.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -280,6 +282,7 @@ async function handleModelLoad(
|
||||
rotation: newFloorItem.rotation,
|
||||
state: "idle",
|
||||
type: "roboticArm",
|
||||
subType: selectedItem.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -313,6 +316,7 @@ async function handleModelLoad(
|
||||
rotation: newFloorItem.rotation,
|
||||
state: "idle",
|
||||
type: "machine",
|
||||
subType: selectedItem.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
@@ -341,6 +345,7 @@ async function handleModelLoad(
|
||||
rotation: newFloorItem.rotation,
|
||||
state: "idle",
|
||||
type: "storageUnit",
|
||||
subType: selectedItem.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
@@ -368,6 +373,7 @@ async function handleModelLoad(
|
||||
rotation: newFloorItem.rotation,
|
||||
state: "idle",
|
||||
type: "human",
|
||||
subType: selectedItem.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -393,6 +399,36 @@ async function handleModelLoad(
|
||||
position: humanEvent.point.position,
|
||||
rotation: humanEvent.point.rotation,
|
||||
}
|
||||
} else if (selectedItem.type === "Crane") {
|
||||
const craneEvent: CraneEventSchema = {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
position: newFloorItem.position,
|
||||
rotation: newFloorItem.rotation,
|
||||
state: "idle",
|
||||
type: "crane",
|
||||
subType: selectedItem.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
rotation: [0, 0, 0],
|
||||
actions: [
|
||||
{
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Action 1",
|
||||
actionType: "pickAndDrop",
|
||||
maxPickUpCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
addEvent(craneEvent);
|
||||
eventData.point = {
|
||||
uuid: craneEvent.point.uuid,
|
||||
position: craneEvent.point.position,
|
||||
rotation: craneEvent.point.rotation,
|
||||
}
|
||||
}
|
||||
|
||||
const completeData = {
|
||||
@@ -401,11 +437,7 @@ async function handleModelLoad(
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
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,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
@@ -422,11 +454,7 @@ async function handleModelLoad(
|
||||
modelName: completeData.modelName,
|
||||
assetId: completeData.assetId,
|
||||
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,
|
||||
isCollidable: false,
|
||||
isVisible: completeData.isVisible,
|
||||
@@ -442,11 +470,7 @@ async function handleModelLoad(
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
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,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
@@ -462,11 +486,7 @@ async function handleModelLoad(
|
||||
modelName: data.modelName,
|
||||
assetId: data.assetId,
|
||||
position: data.position,
|
||||
rotation: [data.rotation.x, data.rotation.y, data.rotation.z] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
],
|
||||
rotation: [data.rotation.x, data.rotation.y, data.rotation.z] as [number, number, number],
|
||||
isLocked: data.isLocked,
|
||||
isCollidable: false,
|
||||
isVisible: data.isVisible,
|
||||
|
||||
@@ -3,8 +3,15 @@ import { useMemo } from "react";
|
||||
import { Cylinder } from "@react-three/drei";
|
||||
|
||||
export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => {
|
||||
const { edgeCylinders, center, size } = useMemo(() => {
|
||||
if (!boundingBox) return { edgeCylinders: [], center: new Vector3(), size: new Vector3() };
|
||||
const { edgeCylinders, cornerSpheres, center, size } = useMemo(() => {
|
||||
if (!boundingBox) {
|
||||
return {
|
||||
edgeCylinders: [],
|
||||
cornerSpheres: [],
|
||||
center: new Vector3(),
|
||||
size: new Vector3(),
|
||||
};
|
||||
}
|
||||
|
||||
const min = boundingBox.min;
|
||||
const max = boundingBox.max;
|
||||
@@ -50,7 +57,13 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam
|
||||
};
|
||||
});
|
||||
|
||||
return { edgeCylinders, center, size };
|
||||
const cornerSpheres = corners.map((corner, i) => ({
|
||||
key: `corner-sphere-${i}`,
|
||||
position: corner.clone(),
|
||||
radius: radius * 1.5,
|
||||
}));
|
||||
|
||||
return { edgeCylinders, cornerSpheres, center, size };
|
||||
}, [boundingBox, lineWidth]);
|
||||
|
||||
if (!boundingBox) return null;
|
||||
@@ -58,11 +71,18 @@ export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { nam
|
||||
return (
|
||||
<group name={name}>
|
||||
{edgeCylinders.map(({ key, position, rotation, length, radius }) => (
|
||||
<Cylinder key={key} args={[radius, radius, length, 6]} position={position} quaternion={rotation} >
|
||||
<Cylinder key={key} args={[radius, radius, length, 6]} position={position} quaternion={rotation}>
|
||||
<meshBasicMaterial color={color} depthWrite={false} />
|
||||
</Cylinder>
|
||||
))}
|
||||
|
||||
{cornerSpheres.map(({ key, position, radius }) => (
|
||||
<mesh key={key} position={position}>
|
||||
<sphereGeometry args={[radius, 12, 12]} />
|
||||
<meshBasicMaterial color={color} depthWrite={false} />
|
||||
</mesh>
|
||||
))}
|
||||
|
||||
<mesh visible={false} position={center}>
|
||||
<boxGeometry args={[size.x, size.y, size.z]} />
|
||||
</mesh>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import * as THREE from 'three';
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
import { ThreeEvent } from '@react-three/fiber';
|
||||
import { useCallback } from 'react';
|
||||
import { ProductStoreType } from '../../../../../../store/simulation/useProductStore';
|
||||
import { EventStoreType } from '../../../../../../store/simulation/useEventsStore';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
import { useActiveTool, useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useActiveTool, useDeletableFloorItem, useSelectedFloorItem, useToggleView } from '../../../../../../store/builder/store';
|
||||
import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore';
|
||||
import { useSocketStore } from '../../../../../../store/builder/store';
|
||||
import { useSceneContext } from '../../../../../scene/sceneContext';
|
||||
@@ -14,82 +11,104 @@ import { useProductContext } from '../../../../../simulation/products/productCon
|
||||
import { useVersionContext } from '../../../../version/versionContext';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getUserData } from '../../../../../../functions/getUserData';
|
||||
import { useLeftData, useTopData } from '../../../../../../store/visualization/useZone3DWidgetStore';
|
||||
import { useSelectedAsset } from '../../../../../../store/simulation/useSimulationStore';
|
||||
import { upsertProductOrEventApi } from '../../../../../../services/simulation/products/UpsertProductOrEventApi';
|
||||
|
||||
// import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi';
|
||||
|
||||
export function useModelEventHandlers({
|
||||
controls,
|
||||
boundingBox,
|
||||
groupRef,
|
||||
toggleView,
|
||||
deletableFloorItem,
|
||||
setDeletableFloorItem,
|
||||
setSelectedFloorItem,
|
||||
gl,
|
||||
setTop,
|
||||
setLeft,
|
||||
getIsEventInProduct,
|
||||
getEventByModelUuid,
|
||||
setSelectedAsset,
|
||||
clearSelectedAsset,
|
||||
removeAsset,
|
||||
updateBackend,
|
||||
leftDrag,
|
||||
rightDrag
|
||||
}: {
|
||||
controls: CameraControls | any,
|
||||
boundingBox: THREE.Box3 | null,
|
||||
groupRef: React.RefObject<THREE.Group>,
|
||||
toggleView: boolean,
|
||||
deletableFloorItem: THREE.Object3D | null,
|
||||
setDeletableFloorItem: (item: THREE.Object3D | null) => void,
|
||||
setSelectedFloorItem: (item: THREE.Object3D | null) => void,
|
||||
gl: THREE.WebGLRenderer,
|
||||
setTop: (top: number) => void,
|
||||
setLeft: (left: number) => void,
|
||||
getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean,
|
||||
getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined,
|
||||
setSelectedAsset: (EventData: EventsSchema) => void,
|
||||
clearSelectedAsset: () => void,
|
||||
removeAsset: (modelUuid: string) => void,
|
||||
updateBackend: (productName: string, productUuid: string, projectId: string, event: EventsSchema) => void,
|
||||
leftDrag: React.MutableRefObject<boolean>,
|
||||
rightDrag: React.MutableRefObject<boolean>
|
||||
}) {
|
||||
|
||||
const { controls, gl, camera } = useThree();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { toggleView } = useToggleView();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const { eventStore, productStore } = useSceneContext();
|
||||
const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { removeAsset } = assetStore();
|
||||
const { removeEvent, getEventByModelUuid } = eventStore();
|
||||
const { getIsEventInProduct, addPoint, deleteEvent } = productStore();
|
||||
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization } = getUserData();
|
||||
const leftDrag = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
const rightDrag = useRef(false);
|
||||
const isRightMouseDown = useRef(false);
|
||||
const { setTop } = useTopData();
|
||||
const { setLeft } = useLeftData();
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleDblClick = (asset: Asset) => {
|
||||
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
||||
const size = boundingBox.getSize(new THREE.Vector3());
|
||||
const center = boundingBox.getCenter(new THREE.Vector3());
|
||||
|
||||
const front = new THREE.Vector3(0, 0, 1);
|
||||
groupRef.current.localToWorld(front);
|
||||
front.sub(groupRef.current.position).normalize();
|
||||
const frontView = false;
|
||||
|
||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||
const newPosition = center.clone().addScaledVector(front, distance);
|
||||
if (frontView) {
|
||||
const size = boundingBox.getSize(new THREE.Vector3());
|
||||
const center = boundingBox.getCenter(new THREE.Vector3());
|
||||
|
||||
(controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true);
|
||||
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
||||
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
||||
cover: true,
|
||||
paddingTop: 5,
|
||||
paddingLeft: 5,
|
||||
paddingBottom: 5,
|
||||
paddingRight: 5,
|
||||
});
|
||||
const front = new THREE.Vector3(0, 0, 1);
|
||||
groupRef.current.localToWorld(front);
|
||||
front.sub(groupRef.current.position).normalize();
|
||||
|
||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||
const newPosition = center.clone().addScaledVector(front, distance);
|
||||
|
||||
(controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true);
|
||||
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
||||
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
||||
cover: true,
|
||||
paddingTop: 5,
|
||||
paddingLeft: 5,
|
||||
paddingBottom: 5,
|
||||
paddingRight: 5,
|
||||
});
|
||||
} else {
|
||||
|
||||
const collisionPos = new THREE.Vector3();
|
||||
groupRef.current.getWorldPosition(collisionPos);
|
||||
|
||||
const currentPos = new THREE.Vector3().copy(camera.position);
|
||||
|
||||
const target = new THREE.Vector3();
|
||||
if (!controls) return;
|
||||
(controls as CameraControls).getTarget(target);
|
||||
const direction = new THREE.Vector3().subVectors(target, currentPos).normalize();
|
||||
|
||||
const offsetDistance = 5;
|
||||
const newCameraPos = new THREE.Vector3().copy(collisionPos).sub(direction.multiplyScalar(offsetDistance));
|
||||
|
||||
camera.position.copy(newCameraPos);
|
||||
(controls as CameraControls).setLookAt(newCameraPos.x, newCameraPos.y, newCameraPos.z, collisionPos.x, 0, collisionPos.z, true);
|
||||
}
|
||||
|
||||
setSelectedFloorItem(groupRef.current);
|
||||
}
|
||||
@@ -117,8 +136,8 @@ export function useModelEventHandlers({
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
eventStore.getState().removeEvent(asset.modelUuid);
|
||||
const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid);
|
||||
removeEvent(asset.modelUuid);
|
||||
const updatedEvents = deleteEvent(asset.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
@@ -133,6 +152,21 @@ export function useModelEventHandlers({
|
||||
|
||||
removeAsset(asset.modelUuid);
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Delete",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
assetData: asset,
|
||||
timeStap: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
echo.success("Model Removed!");
|
||||
}
|
||||
|
||||
@@ -157,7 +191,7 @@ export function useModelEventHandlers({
|
||||
}
|
||||
}
|
||||
|
||||
const event = productStore.getState().addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint);
|
||||
const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
@@ -169,7 +203,6 @@ export function useModelEventHandlers({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -228,6 +261,50 @@ export function useModelEventHandlers({
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
const onPointerDown = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown.current = true;
|
||||
leftDrag.current = false;
|
||||
}
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown.current = true;
|
||||
rightDrag.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerMove = () => {
|
||||
if (isLeftMouseDown.current) {
|
||||
leftDrag.current = true;
|
||||
}
|
||||
if (isRightMouseDown.current) {
|
||||
rightDrag.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown.current = false;
|
||||
}
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
canvasElement.addEventListener('pointerdown', onPointerDown);
|
||||
canvasElement.addEventListener('pointermove', onPointerMove);
|
||||
canvasElement.addEventListener('pointerup', onPointerUp);
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener('pointerdown', onPointerDown);
|
||||
canvasElement.removeEventListener('pointermove', onPointerMove);
|
||||
canvasElement.removeEventListener('pointerup', onPointerUp);
|
||||
}
|
||||
|
||||
}, [gl])
|
||||
|
||||
return {
|
||||
handleDblClick,
|
||||
handleClick,
|
||||
|
||||
@@ -1,154 +1,57 @@
|
||||
import * as THREE from 'three';
|
||||
import { SkeletonUtils } from 'three-stdlib';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
||||
import { RapierRigidBody } from '@react-three/rapier';
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
|
||||
import { useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useToggleView, useToolMode } from '../../../../../store/builder/store';
|
||||
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
|
||||
import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore';
|
||||
import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore';
|
||||
import { useProductContext } from '../../../../simulation/products/productContext';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getUserData } from '../../../../../functions/getUserData';
|
||||
import useModuleStore from '../../../../../store/useModuleStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useVersionContext } from '../../../version/versionContext';
|
||||
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
|
||||
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
||||
import { SkeletonUtils } from 'three-stdlib';
|
||||
|
||||
import { getAssetFieldApi } from '../../../../../services/factoryBuilder/asset/floorAsset/getAssetField';
|
||||
import { ModelAnimator } from './animator/modelAnimator';
|
||||
|
||||
import ConveyorCollider from '../../../../scene/physics/conveyor/conveyorCollider';
|
||||
import RibbonCollider from '../../../../scene/physics/conveyor/ribbonCollider';
|
||||
import { getAssetConveyorPoints } from '../../../../../services/simulation/conveyorPoints/getAssetConveyorPoints';
|
||||
import { useModelEventHandlers } from './eventHandlers/useEventHandlers';
|
||||
|
||||
function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendered: boolean, loader: GLTFLoader }) {
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
const { controls, gl } = useThree();
|
||||
const savedTheme: string = localStorage.getItem("theme") || "light";
|
||||
const { activeTool } = useActiveTool();
|
||||
const { toolMode } = useToolMode();
|
||||
const { toggleView } = useToggleView();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { removeAsset, resetAnimation } = assetStore();
|
||||
const { setTop } = useTopData();
|
||||
const { setLeft } = useLeftData();
|
||||
const { getIsEventInProduct, addPoint } = productStore();
|
||||
const { getEventByModelUuid } = eventStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const { socket } = useSocketStore();
|
||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { assetStore } = useSceneContext();
|
||||
const { resetAnimation } = assetStore();
|
||||
const { setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const leftDrag = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
const rightDrag = useRef(false);
|
||||
const isRightMouseDown = useRef(false);
|
||||
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
|
||||
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const rigidBodyRef = useRef<RapierRigidBody>(null);
|
||||
const [isSelected, setIsSelected] = useState(false);
|
||||
const [ikData, setIkData] = useState<any>();
|
||||
const [ribbonData, setRibbonData] = useState<ConveyorPoints>();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const [fieldData, setFieldData] = useState<any>();
|
||||
const { selectedAssets } = useSelectedAssets();
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') {
|
||||
getAssetIksApi(asset.assetId).then((data) => {
|
||||
if (data.iks) {
|
||||
const iks: IK[] = data.iks;
|
||||
setIkData(iks);
|
||||
if (!fieldData && asset.eventData) {
|
||||
getAssetFieldApi(asset.assetId).then((data) => {
|
||||
if (data.type === 'ArmBot') {
|
||||
if (data.data) {
|
||||
const fieldData: IK[] = data.data;
|
||||
setFieldData(fieldData);
|
||||
}
|
||||
} else if (data.type === 'Conveyor') {
|
||||
if (data.data) {
|
||||
const fieldData = data.data;
|
||||
console.log('fieldData: ', fieldData);
|
||||
setFieldData(fieldData);
|
||||
}
|
||||
} else if (data.type === 'Crane') {
|
||||
if (data.data) {
|
||||
const fieldData = data.data;
|
||||
setFieldData(fieldData);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [asset.modelUuid, ikData])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ribbonData && boundingBox && asset.eventData && asset.eventData.type === 'Conveyor') {
|
||||
getAssetConveyorPoints(asset.assetId).then((data) => {
|
||||
|
||||
|
||||
if (data && data.conveyorPoints && data.conveyorPoints.type !== '') {
|
||||
setRibbonData(data.conveyorPoints)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}, [asset.modelUuid, asset.eventData, ribbonData, boundingBox])
|
||||
// useEffect(() => {
|
||||
// if (!ribbonData && boundingBox && asset.eventData && asset.eventData.type === 'Conveyor') {
|
||||
//
|
||||
// if (asset.assetId === "97e037828ce57fa7bd1cc615") {
|
||||
// setRibbonData({
|
||||
// type: 'normal',
|
||||
// points: [
|
||||
// [
|
||||
// [-2.4697049405553173e-9, 0.8729155659675598, -2.6850852955950217],
|
||||
// [-2.4697049405553173e-9, 0.8729155659675598, 2.6950024154767225]
|
||||
// ],
|
||||
// [
|
||||
// [-2.4697049405553173e-9, 1, -2.6850852955950217],
|
||||
// [-2.4697049405553173e-9, 1, 2.6950024154767225]
|
||||
// ]
|
||||
// ]
|
||||
// })
|
||||
// }
|
||||
// if (asset.assetId === "7ce992f669caaf2d384b9e76") {
|
||||
// setRibbonData({
|
||||
// type: 'curved',
|
||||
// points: [
|
||||
// [
|
||||
// [-0.08963948491646367, 1.2324171172287208, 0.0013611617557632294],
|
||||
// [2.745753362991343, 1.2324171172287208, -0.20188181291400256],
|
||||
// [3.0696383388490056, 1.2324171172287208, -3.044220906761294],
|
||||
// ],
|
||||
// [
|
||||
// [-0.08963948491646367, 2, 0.0013611617557632294],
|
||||
// [2.745753362991343, 2, -0.20188181291400256],
|
||||
// [3.0696383388490056, 2, -3.044220906761294],
|
||||
// ]
|
||||
// ],
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }, [asset.modelUuid, asset.eventData, ribbonData, boundingBox])
|
||||
|
||||
useEffect(() => {
|
||||
if (gltfScene) {
|
||||
gltfScene.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [gltfScene]);
|
||||
}, [asset.modelUuid, fieldData])
|
||||
|
||||
useEffect(() => {
|
||||
setDeletableFloorItem(null);
|
||||
@@ -163,285 +66,6 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
||||
}
|
||||
}, [isRendered, selectedFloorItem])
|
||||
|
||||
useEffect(() => {
|
||||
const loadModel = async () => {
|
||||
try {
|
||||
|
||||
// Check Cache
|
||||
const assetId = asset.assetId;
|
||||
const cachedModel = THREE.Cache.get(assetId);
|
||||
if (cachedModel) {
|
||||
const clone: any = SkeletonUtils.clone(cachedModel.scene);
|
||||
clone.animations = cachedModel.animations || [];
|
||||
setGltfScene(clone);
|
||||
calculateBoundingBox(clone);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(assetId);
|
||||
if (indexedDBModel) {
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf.scene.clone());
|
||||
calculateBoundingBox(gltf.scene);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
|
||||
const handleBackendLoad = async (gltf: GLTF) => {
|
||||
try {
|
||||
const response = await fetch(modelUrl);
|
||||
const modelBlob = await response.blob();
|
||||
await storeGLTF(assetId, modelBlob);
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf.scene.clone());
|
||||
calculateBoundingBox(gltf.scene);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
};
|
||||
loader.load(modelUrl,
|
||||
handleBackendLoad,
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const calculateBoundingBox = (scene: THREE.Object3D) => {
|
||||
const box = new THREE.Box3().setFromObject(scene);
|
||||
setBoundingBox(box);
|
||||
};
|
||||
|
||||
loadModel();
|
||||
|
||||
}, []);
|
||||
|
||||
const handleDblClick = (asset: Asset) => {
|
||||
if (asset) {
|
||||
if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
||||
const size = boundingBox.getSize(new THREE.Vector3());
|
||||
const center = boundingBox.getCenter(new THREE.Vector3());
|
||||
|
||||
const front = new THREE.Vector3(0, 0, 1);
|
||||
groupRef.current.localToWorld(front);
|
||||
front.sub(groupRef.current.position).normalize();
|
||||
|
||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||
const newPosition = center.clone().addScaledVector(front, distance);
|
||||
|
||||
(controls as CameraControls).setPosition(
|
||||
newPosition.x,
|
||||
newPosition.y,
|
||||
newPosition.z,
|
||||
true
|
||||
);
|
||||
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
||||
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
||||
cover: true,
|
||||
paddingTop: 5,
|
||||
paddingLeft: 5,
|
||||
paddingBottom: 5,
|
||||
paddingRight: 5,
|
||||
});
|
||||
setSelectedFloorItem(groupRef.current);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = (evt: ThreeEvent<MouseEvent>, asset: Asset) => {
|
||||
if (leftDrag.current || toggleView) return;
|
||||
if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
|
||||
|
||||
//REST
|
||||
|
||||
// const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: asset.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
eventStore.getState().removeEvent(asset.modelUuid);
|
||||
const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(asset.modelUuid);
|
||||
|
||||
echo.success("Model Removed!");
|
||||
}
|
||||
|
||||
} else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') {
|
||||
if (asset.eventData && asset.eventData.type === 'Conveyor') {
|
||||
const intersectedPoint = evt.point;
|
||||
const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone());
|
||||
if (localPosition) {
|
||||
const conveyorPoint: ConveyorPointSchema = {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [localPosition?.x, localPosition?.y, localPosition?.z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: `Action 1`,
|
||||
actionType: 'default',
|
||||
material: 'Default Material',
|
||||
delay: 0,
|
||||
spawnInterval: 5,
|
||||
spawnCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
}
|
||||
|
||||
const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handlePointerOver = useCallback((asset: Asset) => {
|
||||
if (activeTool === "delete" && activeModule === 'builder') {
|
||||
if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
|
||||
return;
|
||||
} else {
|
||||
setDeletableFloorItem(groupRef.current);
|
||||
}
|
||||
}
|
||||
}, [activeTool, activeModule, deletableFloorItem]);
|
||||
|
||||
const handlePointerOut = useCallback((evt: ThreeEvent<MouseEvent>, asset: Asset) => {
|
||||
if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
}, [activeTool, deletableFloorItem]);
|
||||
|
||||
const handleContextMenu = (asset: Asset, evt: ThreeEvent<MouseEvent>) => {
|
||||
if (rightDrag.current || toggleView) return;
|
||||
if (activeTool === "cursor" && subModule === 'simulations') {
|
||||
if (asset.modelUuid) {
|
||||
const canvasElement = gl.domElement;
|
||||
const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid);
|
||||
if (isInProduct) {
|
||||
const event = getEventByModelUuid(asset.modelUuid);
|
||||
if (event) {
|
||||
setSelectedAsset(event);
|
||||
const canvasRect = canvasElement.getBoundingClientRect();
|
||||
const relativeX = evt.clientX - canvasRect.left;
|
||||
const relativeY = evt.clientY - canvasRect.top;
|
||||
setTop(relativeY);
|
||||
setLeft(relativeX);
|
||||
} else {
|
||||
clearSelectedAsset();
|
||||
}
|
||||
} else {
|
||||
const event = getEventByModelUuid(asset.modelUuid);
|
||||
if (event) {
|
||||
setSelectedAsset(event)
|
||||
const canvasRect = canvasElement.getBoundingClientRect();
|
||||
const relativeX = evt.clientX - canvasRect.left;
|
||||
const relativeY = evt.clientY - canvasRect.top;
|
||||
setTop(relativeY);
|
||||
setLeft(relativeX);
|
||||
} else {
|
||||
clearSelectedAsset()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearSelectedAsset()
|
||||
}
|
||||
} else {
|
||||
clearSelectedAsset()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
const onPointerDown = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown.current = true;
|
||||
leftDrag.current = false;
|
||||
}
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown.current = true;
|
||||
rightDrag.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerMove = () => {
|
||||
if (isLeftMouseDown.current) {
|
||||
leftDrag.current = true;
|
||||
}
|
||||
if (isRightMouseDown.current) {
|
||||
rightDrag.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown.current = false;
|
||||
}
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
canvasElement.addEventListener('pointerdown', onPointerDown);
|
||||
canvasElement.addEventListener('pointermove', onPointerMove);
|
||||
canvasElement.addEventListener('pointerup', onPointerUp);
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener('pointerdown', onPointerDown);
|
||||
canvasElement.removeEventListener('pointermove', onPointerMove);
|
||||
canvasElement.removeEventListener('pointerup', onPointerUp);
|
||||
}
|
||||
|
||||
}, [gl])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAssets.length > 0) {
|
||||
if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) {
|
||||
@@ -454,6 +78,89 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
||||
}
|
||||
}, [selectedAssets])
|
||||
|
||||
useEffect(() => {
|
||||
if (gltfScene) {
|
||||
gltfScene.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [gltfScene]);
|
||||
|
||||
useEffect(() => {
|
||||
// Calculate Bounding Box
|
||||
const calculateBoundingBox = (scene: THREE.Object3D) => {
|
||||
const box = new THREE.Box3().setFromObject(scene);
|
||||
setBoundingBox(box);
|
||||
};
|
||||
|
||||
// Check Cache
|
||||
const assetId = asset.assetId;
|
||||
const cachedModel = THREE.Cache.get(assetId);
|
||||
if (cachedModel) {
|
||||
const clone: any = SkeletonUtils.clone(cachedModel.scene);
|
||||
clone.animations = cachedModel.animations || [];
|
||||
setGltfScene(clone);
|
||||
calculateBoundingBox(clone);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
retrieveGLTF(assetId).then((indexedDBModel) => {
|
||||
if (indexedDBModel) {
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(
|
||||
blobUrl,
|
||||
(gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf.scene.clone());
|
||||
calculateBoundingBox(gltf.scene);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
|
||||
loader.load(
|
||||
modelUrl,
|
||||
(gltf: GLTF) => {
|
||||
fetch(modelUrl)
|
||||
.then((response) => response.blob())
|
||||
.then((modelBlob) => storeGLTF(assetId, modelBlob))
|
||||
.then(() => {
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf.scene.clone());
|
||||
calculateBoundingBox(gltf.scene);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
`[Backend] Error storing/loading ${asset.modelName}:`,
|
||||
error
|
||||
);
|
||||
});
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
||||
}
|
||||
);
|
||||
}).catch((err) => {
|
||||
console.error("Failed to load model:", asset.assetId, err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef });
|
||||
|
||||
return (
|
||||
<group
|
||||
key={asset.modelUuid}
|
||||
@@ -463,7 +170,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
||||
position={asset.position}
|
||||
rotation={asset.rotation}
|
||||
visible={asset.isVisible}
|
||||
userData={{ ...asset, iks: ikData, rigidBodyRef: rigidBodyRef.current, boundingBox: boundingBox }}
|
||||
userData={{ ...asset, fieldData: fieldData }}
|
||||
castShadow
|
||||
receiveShadow
|
||||
onDoubleClick={(e) => {
|
||||
@@ -506,7 +213,11 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
||||
|
||||
</>
|
||||
) : (
|
||||
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
|
||||
<>
|
||||
{!isSelected &&
|
||||
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
|
||||
}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* <ConveyorCollider
|
||||
@@ -514,20 +225,19 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
||||
asset={asset}
|
||||
/> */}
|
||||
|
||||
{ribbonData &&
|
||||
{/* {ribbonData &&
|
||||
<RibbonCollider
|
||||
key={asset.modelUuid}
|
||||
boundingBox={boundingBox}
|
||||
ribbonData={ribbonData}
|
||||
asset={asset}
|
||||
/>
|
||||
}
|
||||
} */}
|
||||
{isSelected &&
|
||||
<AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</group>
|
||||
)} </group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
import { Vector3 } from "three";
|
||||
import { Group, Vector3 } from "three";
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
import { useLimitDistance, useRenderDistance, useSelectedFloorItem } from '../../../../store/builder/store';
|
||||
import { useLimitDistance, useRenderDistance, useSelectedFloorItem, useToggleView } from '../../../../store/builder/store';
|
||||
import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore';
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
|
||||
@@ -13,6 +13,7 @@ const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/w
|
||||
|
||||
function Models({ loader }: { loader: GLTFLoader }) {
|
||||
const { controls, camera } = useThree();
|
||||
const assetGroupRef = useRef<Group>(null);
|
||||
const { assetStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
@@ -45,6 +46,7 @@ function Models({ loader }: { loader: GLTFLoader }) {
|
||||
return (
|
||||
<group
|
||||
name='Asset Group'
|
||||
ref={assetGroupRef}
|
||||
onPointerMissed={(e) => {
|
||||
e.stopPropagation();
|
||||
if (selectedFloorItem) {
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function Builder() {
|
||||
|
||||
<AssetsGroup plane={plane} />
|
||||
|
||||
{/* <mesh name='Walls-And-WallAssets-Group'>
|
||||
<mesh name='Walls-And-WallAssets-Group'>
|
||||
<Geometry ref={csgRef} useGroups>
|
||||
|
||||
<WallGroup />
|
||||
@@ -103,7 +103,7 @@ export default function Builder() {
|
||||
<WallAssetGroup />
|
||||
|
||||
</Geometry>
|
||||
</mesh> */}
|
||||
</mesh>
|
||||
|
||||
<AislesGroup />
|
||||
|
||||
@@ -113,9 +113,9 @@ export default function Builder() {
|
||||
|
||||
<MeasurementTool />
|
||||
|
||||
{/* <CalculateAreaGroup /> */}
|
||||
<CalculateAreaGroup />
|
||||
|
||||
{/* <NavMesh /> */}
|
||||
<NavMesh />
|
||||
|
||||
<DxfFile />
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||
|
||||
const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
|
||||
wallUuid: intersect.object.userData.wallUuid,
|
||||
position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
|
||||
position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z],
|
||||
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
|
||||
});
|
||||
|
||||
@@ -190,7 +190,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||
|
||||
updateWallAsset(wallAsset.modelUuid, {
|
||||
wallUuid: intersect.object.userData.wallUuid,
|
||||
position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
|
||||
position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z],
|
||||
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ function WallAssetCreator() {
|
||||
modelName: selectedItem.name,
|
||||
modelUuid: MathUtils.generateUUID(),
|
||||
wallUuid: wall.wallUuid,
|
||||
wallAssetType: selectedItem.subCategory,
|
||||
wallAssetType: selectedItem.subType,
|
||||
assetId: selectedItem.id,
|
||||
position: [closestPoint.x, selectedItem.subCategory === "fixed-move" ? 0 : intersect.point.y, closestPoint.z],
|
||||
position: [closestPoint.x, selectedItem.subType === "fixedMove" ? 0 : intersect.point.y, closestPoint.z],
|
||||
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import { CameraControls, Html, ScreenSpace } from '@react-three/drei';
|
||||
import { useContextActionStore, useRenameModeStore, useSelectedAssets } from '../../../../store/builder/store';
|
||||
import ContextMenu from '../../../../components/ui/menu/contextMenu';
|
||||
|
||||
function ContextControls() {
|
||||
const { gl, controls } = useThree();
|
||||
const [canRender, setCanRender] = useState(false);
|
||||
const [visibility, setVisibility] = useState({ rename: true, focus: true, flipX: true, flipZ: true, move: true, rotate: true, duplicate: true, copy: true, paste: true, modifier: false, group: false, array: false, delete: true, });
|
||||
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
|
||||
const { selectedAssets } = useSelectedAssets();
|
||||
const { setContextAction } = useContextActionStore();
|
||||
const { setIsRenameMode } = useRenameModeStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAssets.length === 1) {
|
||||
setVisibility({
|
||||
rename: true,
|
||||
focus: true,
|
||||
flipX: true,
|
||||
flipZ: true,
|
||||
move: true,
|
||||
rotate: true,
|
||||
duplicate: true,
|
||||
copy: true,
|
||||
paste: true,
|
||||
modifier: false,
|
||||
group: false,
|
||||
array: false,
|
||||
delete: true,
|
||||
});
|
||||
} else if (selectedAssets.length > 1) {
|
||||
setVisibility({
|
||||
rename: false,
|
||||
focus: true,
|
||||
flipX: true,
|
||||
flipZ: true,
|
||||
move: true,
|
||||
rotate: true,
|
||||
duplicate: true,
|
||||
copy: true,
|
||||
paste: true,
|
||||
modifier: false,
|
||||
group: true,
|
||||
array: false,
|
||||
delete: true,
|
||||
});
|
||||
} else {
|
||||
setVisibility({
|
||||
rename: false,
|
||||
focus: false,
|
||||
flipX: false,
|
||||
flipZ: false,
|
||||
move: false,
|
||||
rotate: false,
|
||||
duplicate: false,
|
||||
copy: false,
|
||||
paste: false,
|
||||
modifier: false,
|
||||
group: false,
|
||||
array: false,
|
||||
delete: false,
|
||||
});
|
||||
}
|
||||
}, [selectedAssets]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
const handleContextClick = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if (selectedAssets.length > 0) {
|
||||
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 });
|
||||
setCanRender(true);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = false;
|
||||
}
|
||||
} else {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedAssets.length > 0) {
|
||||
canvasElement.addEventListener('contextmenu', handleContextClick)
|
||||
} else {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setMenuPosition({ x: 0, y: 0 });
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener('contextmenu', handleContextClick);
|
||||
};
|
||||
}, [gl, selectedAssets]);
|
||||
|
||||
const handleAssetRename = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("renameAsset");
|
||||
setIsRenameMode(true);
|
||||
}
|
||||
const handleAssetFocus = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("focusAsset");
|
||||
}
|
||||
const handleAssetMove = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("moveAsset")
|
||||
}
|
||||
const handleAssetRotate = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("rotateAsset")
|
||||
}
|
||||
const handleAssetCopy = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("copyAsset")
|
||||
}
|
||||
const handleAssetPaste = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("pasteAsset")
|
||||
}
|
||||
const handleAssetDelete = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("deleteAsset")
|
||||
}
|
||||
const handleAssetDuplicate = () => {
|
||||
setCanRender(false);
|
||||
if (controls) {
|
||||
(controls as CameraControls).enabled = true;
|
||||
}
|
||||
setContextAction("duplicateAsset")
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{canRender && (
|
||||
<ScreenSpace depth={1} >
|
||||
<Html
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: menuPosition.y,
|
||||
left: menuPosition.x,
|
||||
zIndex: 1000
|
||||
}}
|
||||
>
|
||||
<ContextMenu
|
||||
visibility={visibility}
|
||||
onRename={() => handleAssetRename()}
|
||||
onFocus={() => handleAssetFocus()}
|
||||
onFlipX={() => console.log("Flip to X")}
|
||||
onFlipZ={() => console.log("Flip to Z")}
|
||||
onMove={() => handleAssetMove()}
|
||||
onRotate={() => handleAssetRotate()}
|
||||
onDuplicate={() => handleAssetDuplicate()}
|
||||
onCopy={() => handleAssetCopy()}
|
||||
onPaste={() => handleAssetPaste()}
|
||||
onGroup={() => console.log("Group")}
|
||||
onArray={() => console.log("Array")}
|
||||
onDelete={() => handleAssetDelete()}
|
||||
/>
|
||||
</Html>
|
||||
</ScreenSpace>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContextControls;
|
||||
@@ -14,8 +14,10 @@ import TransformControl from "./transformControls/transformControls";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
|
||||
import ContextControls from "./contextControls/contextControls";
|
||||
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
||||
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
|
||||
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
|
||||
|
||||
export default function Controls() {
|
||||
const controlsRef = useRef<CameraControls>(null);
|
||||
@@ -144,8 +146,12 @@ export default function Controls() {
|
||||
|
||||
<UndoRedo2DControls />
|
||||
|
||||
<UndoRedo3DControls />
|
||||
|
||||
<TransformControl />
|
||||
|
||||
<ContextControls />
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import * as THREE from "three";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { SkeletonUtils } from "three-stdlib";
|
||||
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
|
||||
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
|
||||
import * as Types from "../../../../../types/world/worldTypes";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import { useParams } from "react-router-dom";
|
||||
@@ -28,10 +28,11 @@ const CopyPasteControls3D = ({
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
const { socket } = useSocketStore();
|
||||
const { assetStore, eventStore } = useSceneContext();
|
||||
const { assetStore, eventStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { addEvent } = eventStore();
|
||||
const { projectId } = useParams();
|
||||
const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore();
|
||||
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { userId, organization } = getUserData();
|
||||
@@ -39,10 +40,8 @@ const CopyPasteControls3D = ({
|
||||
const [isPasting, setIsPasting] = useState(false);
|
||||
const [relativePositions, setRelativePositions] = useState<THREE.Vector3[]>([]);
|
||||
const [centerOffset, setCenterOffset] = useState<THREE.Vector3 | null>(null);
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
|
||||
left: false,
|
||||
right: false,
|
||||
});
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
|
||||
const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => {
|
||||
if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] };
|
||||
@@ -60,6 +59,16 @@ const CopyPasteControls3D = ({
|
||||
return { center, relatives };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (contextAction === "copyAsset") {
|
||||
setContextAction(null);
|
||||
copySelection()
|
||||
} else if (contextAction === "pasteAsset") {
|
||||
setContextAction(null);
|
||||
pasteCopiedObjects()
|
||||
}
|
||||
}, [contextAction])
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
const canvasElement = gl.domElement;
|
||||
@@ -206,6 +215,9 @@ const CopyPasteControls3D = ({
|
||||
const addPastedObjects = () => {
|
||||
if (pastedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToCopy: AssetData[] = [];
|
||||
|
||||
pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
|
||||
if (pastedAsset) {
|
||||
const assetUuid = pastedAsset.userData.modelUuid;
|
||||
@@ -232,6 +244,7 @@ const CopyPasteControls3D = ({
|
||||
|
||||
const eventData: any = {
|
||||
type: pastedAsset.userData.eventData.type,
|
||||
subType: pastedAsset.userData.eventData.subType,
|
||||
};
|
||||
|
||||
if (pastedAsset.userData.eventData.type === "Conveyor") {
|
||||
@@ -242,6 +255,7 @@ const CopyPasteControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: 'transfer',
|
||||
subType: pastedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
points: updatedEventData.points.map((point: any, index: number) => ({
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -274,6 +288,7 @@ const CopyPasteControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "vehicle",
|
||||
subType: pastedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -312,6 +327,7 @@ const CopyPasteControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "roboticArm",
|
||||
subType: pastedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -346,6 +362,7 @@ const CopyPasteControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "machine",
|
||||
subType: pastedAsset.userData.eventData.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
|
||||
@@ -374,6 +391,7 @@ const CopyPasteControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "storageUnit",
|
||||
subType: pastedAsset.userData.eventData.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
|
||||
@@ -401,6 +419,7 @@ const CopyPasteControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "human",
|
||||
subType: pastedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -426,6 +445,36 @@ const CopyPasteControls3D = ({
|
||||
position: humanEvent.point.position,
|
||||
rotation: humanEvent.point.rotation
|
||||
};
|
||||
} else if (pastedAsset.userData.eventData.type === "Crane") {
|
||||
const craneEvent: CraneEventSchema = {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
position: newFloorItem.position,
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "crane",
|
||||
subType: pastedAsset.userData.eventData.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
|
||||
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
|
||||
actions: [
|
||||
{
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Action 1",
|
||||
actionType: "pickAndDrop",
|
||||
maxPickUpCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
addEvent(craneEvent);
|
||||
eventData.point = {
|
||||
uuid: craneEvent.point.uuid,
|
||||
position: craneEvent.point.position,
|
||||
rotation: craneEvent.point.rotation
|
||||
};
|
||||
}
|
||||
|
||||
newFloorItem.eventData = eventData;
|
||||
@@ -500,9 +549,45 @@ const CopyPasteControls3D = ({
|
||||
|
||||
updateAsset(asset.modelUuid, asset);
|
||||
}
|
||||
|
||||
assetsToCopy.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
position: [position.x, position.y, position.z],
|
||||
rotation: [pastedAsset.rotation.x, pastedAsset.rotation.y, pastedAsset.rotation.z],
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
isCollidable: false,
|
||||
opacity: 1,
|
||||
eventData: newFloorItem.eventData || undefined
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToCopy.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Copied",
|
||||
asset: assetsToCopy[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Copied",
|
||||
assets: assetsToCopy
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
|
||||
echo.success("Object added!");
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
@@ -2,13 +2,14 @@ import * as THREE from "three";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { SkeletonUtils } from "three-stdlib";
|
||||
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
|
||||
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
|
||||
import * as Types from "../../../../../types/world/worldTypes";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../../../functions/getUserData";
|
||||
import { useSceneContext } from "../../../sceneContext";
|
||||
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||
import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap";
|
||||
|
||||
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
|
||||
@@ -26,7 +27,8 @@ const DuplicationControls3D = ({
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
const { socket } = useSocketStore();
|
||||
const { assetStore, eventStore } = useSceneContext();
|
||||
const { assetStore, eventStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { addEvent } = eventStore();
|
||||
const { projectId } = useParams();
|
||||
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
||||
@@ -34,19 +36,29 @@ const DuplicationControls3D = ({
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
|
||||
const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null);
|
||||
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
||||
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
|
||||
const [isDuplicating, setIsDuplicating] = useState(false);
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
|
||||
left: false,
|
||||
right: false,
|
||||
});
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
const fineMoveBaseRef = useRef<THREE.Vector3 | null>(null);
|
||||
const lastPointerPositionRef = useRef<THREE.Vector3 | null>(null);
|
||||
const wasShiftHeldRef = useRef(false);
|
||||
|
||||
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
|
||||
const pointPosition = new THREE.Vector3().copy(point.position);
|
||||
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (contextAction === "duplicateAsset") {
|
||||
setContextAction(null);
|
||||
duplicateSelection()
|
||||
}
|
||||
}, [contextAction])
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
const canvasElement = gl.domElement;
|
||||
@@ -79,11 +91,33 @@ const DuplicationControls3D = ({
|
||||
removeAsset(obj.userData.modelUuid);
|
||||
});
|
||||
}
|
||||
setKeyEvent("");
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (isDuplicating && duplicatedObjects.length > 0) {
|
||||
if (event.key.toLowerCase() === 'x') {
|
||||
setAxisConstraint(prev => prev === 'x' ? null : 'x');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.key.toLowerCase() === 'z') {
|
||||
setAxisConstraint(prev => prev === 'z' ? null : 'z');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCombination !== keyEvent) {
|
||||
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
|
||||
setKeyEvent(keyCombination);
|
||||
} else {
|
||||
setKeyEvent("");
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||
duplicateSelection();
|
||||
}
|
||||
@@ -96,11 +130,34 @@ const DuplicationControls3D = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (keyCombination === "") {
|
||||
setKeyEvent("");
|
||||
} else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
|
||||
setKeyEvent(keyCombination);
|
||||
}
|
||||
if (duplicatedObjects[0] && keyEvent !== "" && keyCombination === '') {
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (hit) {
|
||||
const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid);
|
||||
if (model) {
|
||||
const newOffset = calculateDragOffset(model, intersectionPoint);
|
||||
setDragOffset(newOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!toggleView) {
|
||||
canvasElement.addEventListener("pointerdown", onPointerDown);
|
||||
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||
canvasElement.addEventListener("keydown", onKeyDown);
|
||||
canvasElement.addEventListener("keyup", onKeyUp);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -108,8 +165,9 @@ const DuplicationControls3D = ({
|
||||
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||
canvasElement.addEventListener("keyup", onKeyUp);
|
||||
};
|
||||
}, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]);
|
||||
}, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects, keyEvent]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!isDuplicating || duplicatedObjects.length === 0) return;
|
||||
@@ -129,7 +187,9 @@ const DuplicationControls3D = ({
|
||||
}
|
||||
|
||||
if (dragOffset) {
|
||||
const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
|
||||
const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
|
||||
const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid);
|
||||
const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef });
|
||||
|
||||
duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => {
|
||||
if (duplicatedObject.userData.modelUuid) {
|
||||
@@ -143,7 +203,7 @@ const DuplicationControls3D = ({
|
||||
);
|
||||
const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid);
|
||||
|
||||
const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset);
|
||||
const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset);
|
||||
const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
|
||||
|
||||
if (model) {
|
||||
@@ -156,6 +216,21 @@ const DuplicationControls3D = ({
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (duplicatedObjects.length > 0) {
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (hit) {
|
||||
const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid);
|
||||
if (model) {
|
||||
const newOffset = calculateDragOffset(model, intersectionPoint);
|
||||
setDragOffset(newOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [axisConstraint, camera, duplicatedObjects])
|
||||
|
||||
const duplicateSelection = useCallback(() => {
|
||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||
const positions: Record<string, THREE.Vector3> = {};
|
||||
@@ -207,6 +282,9 @@ const DuplicationControls3D = ({
|
||||
const addDuplicatedAssets = () => {
|
||||
if (duplicatedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToDuplicate: AssetData[] = [];
|
||||
|
||||
duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => {
|
||||
if (duplicatedAsset) {
|
||||
const assetUuid = duplicatedAsset.userData.modelUuid;
|
||||
@@ -233,6 +311,7 @@ const DuplicationControls3D = ({
|
||||
|
||||
const eventData: any = {
|
||||
type: duplicatedAsset.userData.eventData.type,
|
||||
subType: duplicatedAsset.userData.eventData.subType,
|
||||
};
|
||||
|
||||
if (duplicatedAsset.userData.eventData.type === "Conveyor") {
|
||||
@@ -243,6 +322,7 @@ const DuplicationControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: 'transfer',
|
||||
subType: duplicatedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
points: updatedEventData.points.map((point: any, index: number) => ({
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -275,6 +355,7 @@ const DuplicationControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "vehicle",
|
||||
subType: duplicatedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -313,6 +394,7 @@ const DuplicationControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "roboticArm",
|
||||
subType: duplicatedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -347,6 +429,7 @@ const DuplicationControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "machine",
|
||||
subType: duplicatedAsset.userData.eventData.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
|
||||
@@ -375,6 +458,7 @@ const DuplicationControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "storageUnit",
|
||||
subType: duplicatedAsset.userData.eventData.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
|
||||
@@ -402,6 +486,7 @@ const DuplicationControls3D = ({
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "human",
|
||||
subType: duplicatedAsset.userData.eventData.subType || '',
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
@@ -427,6 +512,36 @@ const DuplicationControls3D = ({
|
||||
position: humanEvent.point.position,
|
||||
rotation: humanEvent.point.rotation
|
||||
};
|
||||
} else if (duplicatedAsset.userData.eventData.type === "Crane") {
|
||||
const craneEvent: CraneEventSchema = {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
position: newFloorItem.position,
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "crane",
|
||||
subType: duplicatedAsset.userData.eventData.subType || '',
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
|
||||
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
|
||||
actions: [
|
||||
{
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Action 1",
|
||||
actionType: "pickAndDrop",
|
||||
maxPickUpCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
addEvent(craneEvent);
|
||||
eventData.point = {
|
||||
uuid: craneEvent.point.uuid,
|
||||
position: craneEvent.point.position,
|
||||
rotation: craneEvent.point.rotation
|
||||
};
|
||||
}
|
||||
|
||||
newFloorItem.eventData = eventData;
|
||||
@@ -501,9 +616,45 @@ const DuplicationControls3D = ({
|
||||
|
||||
updateAsset(asset.modelUuid, asset);
|
||||
}
|
||||
|
||||
assetsToDuplicate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
position: [position.x, position.y, position.z],
|
||||
rotation: [duplicatedAsset.rotation.x, duplicatedAsset.rotation.y, duplicatedAsset.rotation.z],
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
isCollidable: false,
|
||||
opacity: 1,
|
||||
eventData: newFloorItem.eventData || undefined
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToDuplicate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Copied",
|
||||
asset: assetsToDuplicate[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Copied",
|
||||
assets: assetsToDuplicate
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
|
||||
echo.success("Object duplicated!");
|
||||
clearSelection();
|
||||
};
|
||||
@@ -516,6 +667,7 @@ const DuplicationControls3D = ({
|
||||
setSelectedAssets([]);
|
||||
setIsDuplicating(false);
|
||||
setDragOffset(null);
|
||||
setAxisConstraint(null);
|
||||
};
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
export function handleAssetPositionSnap({
|
||||
rawBasePosition,
|
||||
intersectionPoint,
|
||||
model,
|
||||
axisConstraint,
|
||||
keyEvent,
|
||||
fineMoveBaseRef,
|
||||
lastPointerPositionRef,
|
||||
wasShiftHeldRef
|
||||
}: {
|
||||
rawBasePosition: THREE.Vector3;
|
||||
intersectionPoint: THREE.Vector3;
|
||||
model: THREE.Object3D | undefined;
|
||||
axisConstraint: "x" | "z" | null;
|
||||
keyEvent: string;
|
||||
fineMoveBaseRef: React.MutableRefObject<THREE.Vector3 | null>;
|
||||
lastPointerPositionRef: React.MutableRefObject<THREE.Vector3 | null>;
|
||||
wasShiftHeldRef: React.MutableRefObject<boolean>;
|
||||
}): THREE.Vector3 {
|
||||
const CTRL_DISTANCE = 0.5;
|
||||
const SHIFT_DISTANCE = 0.05;
|
||||
const CTRL_SHIFT_DISTANCE = 0.05;
|
||||
|
||||
const isShiftHeld = keyEvent.includes("Shift");
|
||||
|
||||
// Handle Shift toggle state
|
||||
if (isShiftHeld !== wasShiftHeldRef.current && model) {
|
||||
if (isShiftHeld) {
|
||||
fineMoveBaseRef.current = model.position.clone();
|
||||
lastPointerPositionRef.current = intersectionPoint.clone();
|
||||
} else {
|
||||
fineMoveBaseRef.current = null;
|
||||
}
|
||||
wasShiftHeldRef.current = isShiftHeld;
|
||||
}
|
||||
|
||||
// Start from raw
|
||||
let baseNewPosition = rawBasePosition.clone();
|
||||
|
||||
// Apply snapping / fine move
|
||||
if (keyEvent === "Ctrl") {
|
||||
baseNewPosition.set(
|
||||
Math.round(baseNewPosition.x / CTRL_DISTANCE) * CTRL_DISTANCE,
|
||||
baseNewPosition.y,
|
||||
Math.round(baseNewPosition.z / CTRL_DISTANCE) * CTRL_DISTANCE
|
||||
);
|
||||
} else if (keyEvent === "Ctrl+Shift") {
|
||||
if (isShiftHeld && fineMoveBaseRef.current && lastPointerPositionRef.current) {
|
||||
const pointerDelta = new THREE.Vector3().subVectors(intersectionPoint, lastPointerPositionRef.current);
|
||||
baseNewPosition = fineMoveBaseRef.current.clone().add(pointerDelta.multiplyScalar(SHIFT_DISTANCE));
|
||||
}
|
||||
baseNewPosition.set(
|
||||
Math.round(baseNewPosition.x / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE,
|
||||
baseNewPosition.y,
|
||||
Math.round(baseNewPosition.z / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE
|
||||
);
|
||||
} else if (isShiftHeld && fineMoveBaseRef.current && lastPointerPositionRef.current) {
|
||||
const pointerDelta = new THREE.Vector3().subVectors(intersectionPoint, lastPointerPositionRef.current);
|
||||
baseNewPosition = fineMoveBaseRef.current.clone().add(pointerDelta.multiplyScalar(SHIFT_DISTANCE));
|
||||
}
|
||||
|
||||
// Apply axis constraint last
|
||||
if (axisConstraint && model) {
|
||||
const currentBasePosition = model.position.clone();
|
||||
if (axisConstraint === 'x') {
|
||||
baseNewPosition.set(baseNewPosition.x, currentBasePosition.y, currentBasePosition.z);
|
||||
} else if (axisConstraint === 'z') {
|
||||
baseNewPosition.set(currentBasePosition.x, currentBasePosition.y, baseNewPosition.z);
|
||||
}
|
||||
}
|
||||
|
||||
return baseNewPosition;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
export function handleAssetRotationSnap({
|
||||
object,
|
||||
pointerDeltaX,
|
||||
keyEvent,
|
||||
snapBaseRef,
|
||||
prevRotationRef,
|
||||
wasShiftHeldRef
|
||||
}: {
|
||||
object: THREE.Object3D;
|
||||
pointerDeltaX: number;
|
||||
keyEvent: string;
|
||||
snapBaseRef: React.MutableRefObject<number | null>;
|
||||
prevRotationRef: React.MutableRefObject<number | null>;
|
||||
wasShiftHeldRef: React.MutableRefObject<boolean>;
|
||||
}): number {
|
||||
const SHIFT_SPEED = 0.5; // Fine rotation speed
|
||||
const NORMAL_SPEED = 5; // Normal rotation speed
|
||||
const CTRL_SNAP_DEG = 15; // 15 degrees
|
||||
const CTRL_SHIFT_SNAP_DEG = 5; // 5 degrees
|
||||
|
||||
const isShiftHeld = keyEvent.includes("Shift");
|
||||
const isCtrlHeld = keyEvent.includes("Ctrl");
|
||||
|
||||
const speedFactor = isShiftHeld ? SHIFT_SPEED : NORMAL_SPEED;
|
||||
let deltaAngle = pointerDeltaX * speedFactor;
|
||||
|
||||
// Track if modifier changed
|
||||
const modifierChanged = isShiftHeld !== wasShiftHeldRef.current;
|
||||
if (modifierChanged) {
|
||||
wasShiftHeldRef.current = isShiftHeld;
|
||||
if (isCtrlHeld) snapBaseRef.current = null;
|
||||
}
|
||||
|
||||
if (isCtrlHeld) {
|
||||
const snapDeg = isShiftHeld ? CTRL_SHIFT_SNAP_DEG : CTRL_SNAP_DEG;
|
||||
const snapRad = THREE.MathUtils.degToRad(snapDeg);
|
||||
|
||||
// Get current Y rotation from object's quaternion
|
||||
const euler = new THREE.Euler().setFromQuaternion(object.quaternion, 'YXZ');
|
||||
let currentAngle = euler.y;
|
||||
|
||||
// Initialize snap base on first frame
|
||||
if (snapBaseRef.current === null) {
|
||||
snapBaseRef.current = currentAngle;
|
||||
prevRotationRef.current = currentAngle;
|
||||
}
|
||||
|
||||
// Accumulate the total rotation from the base
|
||||
const totalRotation = snapBaseRef.current + deltaAngle;
|
||||
|
||||
// Snap to nearest increment
|
||||
const snappedAngle = Math.round(totalRotation / snapRad) * snapRad;
|
||||
|
||||
// Calculate the delta needed to reach the snapped angle from current rotation
|
||||
let targetDelta = snappedAngle - currentAngle;
|
||||
|
||||
// Handle wrapping around 360 degrees
|
||||
if (Math.abs(targetDelta) > Math.PI) {
|
||||
targetDelta = targetDelta - Math.sign(targetDelta) * 2 * Math.PI;
|
||||
}
|
||||
|
||||
// Update snap base for next frame
|
||||
snapBaseRef.current = totalRotation;
|
||||
|
||||
return targetDelta;
|
||||
} else {
|
||||
// Reset snapping when Ctrl is not held
|
||||
snapBaseRef.current = null;
|
||||
prevRotationRef.current = null;
|
||||
return deltaAngle;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as THREE from "three";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store";
|
||||
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store";
|
||||
import * as Types from "../../../../../types/world/worldTypes";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { snapControls } from "../../../../../utils/handleSnap";
|
||||
import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap";
|
||||
import DistanceFindingControls from "./distanceFindingControls";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||
@@ -34,19 +34,25 @@ function MoveControls3D({
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { socket } = useSocketStore();
|
||||
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { updateAsset, getAssetById } = assetStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
|
||||
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
|
||||
const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null);
|
||||
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
||||
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
|
||||
const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
|
||||
const [isMoving, setIsMoving] = useState(false);
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
const fineMoveBaseRef = useRef<THREE.Vector3 | null>(null);
|
||||
const lastPointerPositionRef = useRef<THREE.Vector3 | null>(null);
|
||||
const wasShiftHeldRef = useRef(false);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
@@ -63,6 +69,13 @@ function MoveControls3D({
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (contextAction === "moveAsset") {
|
||||
setContextAction(null);
|
||||
moveAssets()
|
||||
}
|
||||
}, [contextAction])
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
|
||||
@@ -76,10 +89,21 @@ function MoveControls3D({
|
||||
};
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent) => {
|
||||
const isModifierKey = (!event.shiftKey && !event.ctrlKey);
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (isModifierKey && keyEvent !== "") {
|
||||
if (keyCombination === "") {
|
||||
setKeyEvent("");
|
||||
} else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
|
||||
setKeyEvent(keyCombination);
|
||||
}
|
||||
if (movedObjects[0]) {
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (hit) {
|
||||
const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint);
|
||||
setDragOffset(newOffset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -104,7 +128,6 @@ function MoveControls3D({
|
||||
clearSelection();
|
||||
setMovedObjects([]);
|
||||
}
|
||||
setKeyEvent("");
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -113,6 +136,19 @@ function MoveControls3D({
|
||||
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0)
|
||||
return;
|
||||
|
||||
if (isMoving && movedObjects.length > 0) {
|
||||
if (event.key.toLowerCase() === 'x') {
|
||||
setAxisConstraint(prev => prev === 'x' ? null : 'x');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.key.toLowerCase() === 'z') {
|
||||
setAxisConstraint(prev => prev === 'z' ? null : 'z');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCombination !== keyEvent) {
|
||||
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
|
||||
setKeyEvent(keyCombination);
|
||||
@@ -183,9 +219,22 @@ function MoveControls3D({
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 0)
|
||||
setAxisConstraint(null);
|
||||
}, 100)
|
||||
}, [movedObjects, initialStates, updateAsset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (movedObjects.length > 0) {
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
if (hit) {
|
||||
const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint);
|
||||
setDragOffset(newOffset);
|
||||
}
|
||||
}
|
||||
}, [axisConstraint, camera, movedObjects])
|
||||
|
||||
useFrame(() => {
|
||||
if (!isMoving || movedObjects.length === 0) return;
|
||||
|
||||
@@ -204,20 +253,8 @@ function MoveControls3D({
|
||||
|
||||
if (dragOffset) {
|
||||
const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
|
||||
|
||||
let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1;
|
||||
|
||||
const initialBasePosition = initialPositions[movedObjects[0].uuid];
|
||||
const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition);
|
||||
|
||||
let adjustedDifference = positionDifference.multiplyScalar(moveDistance);
|
||||
|
||||
const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference);
|
||||
|
||||
if (keyEvent.includes("Ctrl")) {
|
||||
baseNewPosition.x = snapControls(baseNewPosition.x, keyEvent);
|
||||
baseNewPosition.z = snapControls(baseNewPosition.z, keyEvent);
|
||||
}
|
||||
const model = movedObjects[0];
|
||||
const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef });
|
||||
|
||||
movedObjects.forEach((movedAsset: THREE.Object3D) => {
|
||||
if (movedAsset.userData.modelUuid) {
|
||||
@@ -284,6 +321,9 @@ function MoveControls3D({
|
||||
const placeMovedAssets = () => {
|
||||
if (movedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
|
||||
movedObjects.forEach(async (movedAsset: THREE.Object3D) => {
|
||||
if (movedAsset) {
|
||||
const assetUuid = movedAsset.userData.modelUuid;
|
||||
@@ -291,6 +331,32 @@ function MoveControls3D({
|
||||
const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
|
||||
if (!asset || !model) return;
|
||||
const position = new THREE.Vector3().copy(model.position);
|
||||
const initialState = initialStates[movedAsset.uuid];
|
||||
|
||||
if (initialState) {
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
...asset,
|
||||
position: [
|
||||
initialState.position.x,
|
||||
initialState.position.y,
|
||||
initialState.position.z
|
||||
],
|
||||
rotation: [
|
||||
initialState.rotation?.x || 0,
|
||||
initialState.rotation?.y || 0,
|
||||
initialState.rotation?.z || 0
|
||||
]
|
||||
},
|
||||
newData: {
|
||||
...asset,
|
||||
position: [position.x, position.y, position.z],
|
||||
rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z]
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
const newFloorItem: Types.FloorItemType = {
|
||||
modelUuid: movedAsset.userData.modelUuid,
|
||||
@@ -339,7 +405,7 @@ function MoveControls3D({
|
||||
}
|
||||
|
||||
updateAsset(movedAsset.userData.modelUuid, {
|
||||
position: asset.position,
|
||||
position: [position.x, position.y, position.z],
|
||||
rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z],
|
||||
});
|
||||
|
||||
@@ -368,9 +434,31 @@ function MoveControls3D({
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToUpdate.length > 0) {
|
||||
if (assetsToUpdate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: assetsToUpdate[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Update",
|
||||
assets: assetsToUpdate
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
}
|
||||
|
||||
echo.success("Object moved!");
|
||||
setIsMoving(false);
|
||||
clearSelection();
|
||||
setAxisConstraint(null);
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as THREE from "three";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
|
||||
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
|
||||
import * as Types from "../../../../../types/world/worldTypes";
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useParams } from "react-router-dom";
|
||||
@@ -9,6 +9,8 @@ import { useProductContext } from "../../../../simulation/products/productContex
|
||||
import { getUserData } from "../../../../../functions/getUserData";
|
||||
import { useSceneContext } from "../../../sceneContext";
|
||||
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import { handleAssetRotationSnap } from "./functions/handleAssetRotationSnap";
|
||||
|
||||
// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
|
||||
|
||||
@@ -23,7 +25,6 @@ function RotateControls3D({
|
||||
setDuplicatedObjects
|
||||
}: any) {
|
||||
const { camera, gl, scene, pointer, raycaster } = useThree();
|
||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
|
||||
const { toggleView } = useToggleView();
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
@@ -32,20 +33,23 @@ function RotateControls3D({
|
||||
const { socket } = useSocketStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { updateAsset } = assetStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
|
||||
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
|
||||
const [initialRotations, setInitialRotations] = useState<Record<string, THREE.Euler>>({});
|
||||
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
|
||||
const [isRotating, setIsRotating] = useState(false);
|
||||
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
|
||||
const rotationCenter = useRef<THREE.Vector3 | null>(null);
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
|
||||
left: false,
|
||||
right: false,
|
||||
});
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
const snapBaseRef = useRef<number | null>(null);
|
||||
const prevRotationRef = useRef<number | null>(null);
|
||||
const wasShiftHeldRef = useRef(false);
|
||||
|
||||
const updateBackend = useCallback((
|
||||
productName: string,
|
||||
@@ -62,6 +66,13 @@ function RotateControls3D({
|
||||
});
|
||||
}, [selectedVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contextAction === "rotateAsset") {
|
||||
setContextAction(null);
|
||||
rotateAssets()
|
||||
}
|
||||
}, [contextAction])
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
|
||||
@@ -97,6 +108,8 @@ function RotateControls3D({
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return;
|
||||
|
||||
if (event.key.toLowerCase() === "r") {
|
||||
@@ -104,6 +117,15 @@ function RotateControls3D({
|
||||
rotateAssets();
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCombination !== keyEvent) {
|
||||
if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
|
||||
setKeyEvent(keyCombination);
|
||||
} else {
|
||||
setKeyEvent("");
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key.toLowerCase() === "escape") {
|
||||
event.preventDefault();
|
||||
resetToInitialRotations();
|
||||
@@ -112,11 +134,24 @@ function RotateControls3D({
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (keyCombination === "") {
|
||||
setKeyEvent("");
|
||||
} else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
|
||||
setKeyEvent(keyCombination);
|
||||
}
|
||||
const currentPointer = new THREE.Vector2(pointer.x, pointer.y);
|
||||
prevPointerPosition.current = currentPointer.clone();
|
||||
};
|
||||
|
||||
if (!toggleView) {
|
||||
canvasElement.addEventListener("pointerdown", onPointerDown);
|
||||
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||
canvasElement.addEventListener("keydown", onKeyDown);
|
||||
canvasElement?.addEventListener("keyup", onKeyUp);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -124,68 +159,72 @@ function RotateControls3D({
|
||||
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||
canvasElement?.removeEventListener("keyup", onKeyUp);
|
||||
};
|
||||
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]);
|
||||
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]);
|
||||
|
||||
const resetToInitialRotations = useCallback(() => {
|
||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||
const uuid = obj.uuid;
|
||||
if (obj.userData.modelUuid) {
|
||||
const initialRotation = initialRotations[uuid];
|
||||
const initialPosition = initialPositions[uuid];
|
||||
setTimeout(() => {
|
||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||
const uuid = obj.uuid;
|
||||
if (obj.userData.modelUuid) {
|
||||
const initialRotation = initialRotations[uuid];
|
||||
const initialPosition = initialPositions[uuid];
|
||||
|
||||
if (initialRotation && initialPosition) {
|
||||
const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,];
|
||||
const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,];
|
||||
if (initialRotation && initialPosition) {
|
||||
const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,];
|
||||
const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,];
|
||||
|
||||
updateAsset(obj.userData.modelUuid, {
|
||||
rotation: rotationArray,
|
||||
position: positionArray,
|
||||
});
|
||||
updateAsset(obj.userData.modelUuid, {
|
||||
rotation: rotationArray,
|
||||
position: positionArray,
|
||||
});
|
||||
|
||||
obj.rotation.copy(initialRotation);
|
||||
obj.position.copy(initialPosition);
|
||||
obj.rotation.copy(initialRotation);
|
||||
obj.position.copy(initialPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 100)
|
||||
}, [rotatedObjects, initialRotations, initialPositions, updateAsset]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!isRotating || rotatedObjects.length === 0) return;
|
||||
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
const currentPointer = new THREE.Vector2(pointer.x, pointer.y);
|
||||
|
||||
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
|
||||
if (point) {
|
||||
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
|
||||
}
|
||||
prevPointerPosition.current = currentPointer.clone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (point && prevPointerPosition.current && rotationCenter.current) {
|
||||
if (prevPointerPosition.current && rotationCenter.current) {
|
||||
const center = rotationCenter.current;
|
||||
|
||||
const currentAngle = Math.atan2(point.z - center.z, point.x - center.x);
|
||||
const prevAngle = Math.atan2(
|
||||
prevPointerPosition.current.y - center.z,
|
||||
prevPointerPosition.current.x - center.x
|
||||
);
|
||||
const angleDelta = prevAngle - currentAngle;
|
||||
|
||||
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
|
||||
const deltaX = currentPointer.x - prevPointerPosition.current.x;
|
||||
|
||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||
if (obj.userData.modelUuid) {
|
||||
const angleDelta = handleAssetRotationSnap({
|
||||
object: obj,
|
||||
pointerDeltaX: deltaX,
|
||||
keyEvent,
|
||||
snapBaseRef,
|
||||
prevRotationRef,
|
||||
wasShiftHeldRef
|
||||
});
|
||||
|
||||
const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
|
||||
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
|
||||
relativePosition.applyMatrix4(rotationMatrix);
|
||||
obj.position.copy(center).add(relativePosition);
|
||||
obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta);
|
||||
|
||||
const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta);
|
||||
obj.quaternion.multiply(rotationQuat);
|
||||
obj.rotation.setFromQuaternion(obj.quaternion);
|
||||
}
|
||||
});
|
||||
|
||||
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
|
||||
prevPointerPosition.current = currentPointer.clone();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -207,27 +246,54 @@ function RotateControls3D({
|
||||
setInitialRotations(rotations);
|
||||
setInitialPositions(positions);
|
||||
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
|
||||
if (point) {
|
||||
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
|
||||
}
|
||||
prevPointerPosition.current = new THREE.Vector2(pointer.x, pointer.y);
|
||||
|
||||
setRotatedObjects(selectedAssets);
|
||||
setIsRotating(true);
|
||||
}, [selectedAssets, camera, pointer, raycaster, plane]);
|
||||
}, [selectedAssets, camera, pointer, raycaster]);
|
||||
|
||||
const placeRotatedAssets = useCallback(() => {
|
||||
if (rotatedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
|
||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||
if (obj && obj.userData.modelUuid) {
|
||||
const asset = assetStore.getState().getAssetById(obj.userData.modelUuid);
|
||||
if (!asset) return;
|
||||
|
||||
const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
|
||||
|
||||
const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z];
|
||||
|
||||
if (initialRotations[obj.uuid] && initialPositions[obj.uuid]) {
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
...asset,
|
||||
position: [
|
||||
initialPositions[obj.uuid].x,
|
||||
initialPositions[obj.uuid].y,
|
||||
initialPositions[obj.uuid].z
|
||||
],
|
||||
rotation: [
|
||||
initialRotations[obj.uuid].x,
|
||||
initialRotations[obj.uuid].y,
|
||||
initialRotations[obj.uuid].z
|
||||
]
|
||||
},
|
||||
newData: {
|
||||
...asset,
|
||||
position: positionArray,
|
||||
rotation: rotationArray
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
const newFloorItem: Types.FloorItemType = {
|
||||
modelUuid: obj.userData.modelUuid,
|
||||
modelName: obj.userData.modelName,
|
||||
@@ -305,6 +371,27 @@ function RotateControls3D({
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToUpdate.length > 0) {
|
||||
if (assetsToUpdate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: assetsToUpdate[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Update",
|
||||
assets: assetsToUpdate
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
}
|
||||
|
||||
setIsRotating(false);
|
||||
clearSelection();
|
||||
}, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getUserData } from "../../../../../functions/getUserData";
|
||||
import { useSceneContext } from "../../../sceneContext";
|
||||
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||
import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
|
||||
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import DuplicationControls3D from "./duplicationControls3D";
|
||||
import CopyPasteControls3D from "./copyPasteControls3D";
|
||||
@@ -31,8 +31,10 @@ const SelectionControls3D: React.FC = () => {
|
||||
const boundingBoxRef = useRef<THREE.Mesh>();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { removeAsset } = assetStore();
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { removeAsset, getAssetById } = assetStore();
|
||||
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
|
||||
const { toolMode } = useToolMode();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
@@ -65,6 +67,13 @@ const SelectionControls3D: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (contextAction === "deleteAsset") {
|
||||
setContextAction(null);
|
||||
deleteSelection()
|
||||
}
|
||||
}, [contextAction])
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
|
||||
@@ -107,7 +116,7 @@ const SelectionControls3D: React.FC = () => {
|
||||
if (event.button === 2 && !event.ctrlKey && !event.shiftKey) {
|
||||
isRightClick.current = false;
|
||||
if (!rightClickMoved.current) {
|
||||
clearSelection();
|
||||
// clearSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -192,7 +201,7 @@ const SelectionControls3D: React.FC = () => {
|
||||
const onContextMenu = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if (!rightClickMoved.current) {
|
||||
clearSelection();
|
||||
// clearSelection();
|
||||
}
|
||||
rightClickMoved.current = false;
|
||||
};
|
||||
@@ -266,12 +275,18 @@ const SelectionControls3D: React.FC = () => {
|
||||
const deleteSelection = () => {
|
||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToDelete: AssetData[] = [];
|
||||
|
||||
const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid);
|
||||
|
||||
selectedAssets.forEach((selectedMesh: THREE.Object3D) => {
|
||||
const asset = getAssetById(selectedMesh.userData.modelUuid);
|
||||
if (!asset) return;
|
||||
|
||||
//REST
|
||||
|
||||
// const response = await deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName);
|
||||
// const response = deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
@@ -321,6 +336,31 @@ const SelectionControls3D: React.FC = () => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
assetsToDelete.push({
|
||||
type: "Asset",
|
||||
assetData: asset,
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
if (assetsToDelete.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Delete",
|
||||
asset: assetsToDelete[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Delete",
|
||||
assets: assetsToDelete
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
|
||||
selectedUUIDs.forEach((uuid: string) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as THREE from "three";
|
||||
import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
// import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
@@ -15,6 +15,7 @@ import { useVersionContext } from "../../../builder/version/versionContext";
|
||||
|
||||
export default function TransformControl() {
|
||||
const state = useThree();
|
||||
const ref = useRef(null);
|
||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { setObjectPosition } = useObjectPosition();
|
||||
@@ -23,7 +24,8 @@ export default function TransformControl() {
|
||||
const { socket } = useSocketStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { updateAsset, getAssetById } = assetStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
@@ -136,13 +138,37 @@ export default function TransformControl() {
|
||||
projectId
|
||||
};
|
||||
|
||||
// console.log('data: ', data);
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
assetData: asset,
|
||||
newData: {
|
||||
...asset,
|
||||
position: [selectedFloorItem.position.x, 0, selectedFloorItem.position.z],
|
||||
rotation: [selectedFloorItem.rotation.x, selectedFloorItem.rotation.y, selectedFloorItem.rotation.z],
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const isTextInput = (element: Element | null): boolean =>
|
||||
element instanceof HTMLInputElement ||
|
||||
element instanceof HTMLTextAreaElement ||
|
||||
element?.getAttribute("contenteditable") === "true";
|
||||
if (isTextInput(document.activeElement)) return;
|
||||
const keyCombination = detectModifierKeys(e);
|
||||
if (!selectedFloorItem) return;
|
||||
if (keyCombination === "G") {
|
||||
@@ -186,8 +212,9 @@ export default function TransformControl() {
|
||||
<>
|
||||
{(selectedFloorItem && transformMode) &&
|
||||
<TransformControls
|
||||
ref={ref}
|
||||
showX={transformMode === "translate"}
|
||||
showY={transformMode === "rotate"}
|
||||
showY={transformMode === "rotate" || transformMode === "translate"}
|
||||
showZ={transformMode === "translate"}
|
||||
object={selectedFloorItem}
|
||||
mode={transformMode}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store";
|
||||
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
||||
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
||||
|
||||
function useRedoHandler() {
|
||||
function use2DRedoHandler() {
|
||||
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
||||
const { redo2D, peekRedo2D } = undoRedo2DStore();
|
||||
const { addWall, removeWall, updateWall } = wallStore();
|
||||
@@ -352,4 +352,4 @@ function useRedoHandler() {
|
||||
return { handleRedo };
|
||||
}
|
||||
|
||||
export default useRedoHandler;
|
||||
export default use2DRedoHandler;
|
||||
@@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store";
|
||||
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
||||
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
||||
|
||||
function useUndoHandler() {
|
||||
function use2DUndoHandler() {
|
||||
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
||||
const { undo2D, peekUndo2D } = undoRedo2DStore();
|
||||
const { addWall, removeWall, updateWall } = wallStore();
|
||||
@@ -67,38 +67,37 @@ function useUndoHandler() {
|
||||
}
|
||||
|
||||
undo2D();
|
||||
|
||||
};
|
||||
|
||||
const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
|
||||
switch (point.type) {
|
||||
case 'Wall': createWallFromBackend(point.lineData); break;
|
||||
case 'Floor': createFloorFromBackend(point.lineData); break;
|
||||
case 'Zone': createZoneFromBackend(point.lineData); break;
|
||||
case 'Aisle': createAisleFromBackend(point.lineData); break;
|
||||
case 'Wall': createWallToBackend(point.lineData); break;
|
||||
case 'Floor': createFloorToBackend(point.lineData); break;
|
||||
case 'Zone': createZoneToBackend(point.lineData); break;
|
||||
case 'Aisle': createAisleToBackend(point.lineData); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
|
||||
switch (point.type) {
|
||||
case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break;
|
||||
case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break;
|
||||
case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break;
|
||||
case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break;
|
||||
case 'Wall': removeWallToBackend(point.lineData.wallUuid); break;
|
||||
case 'Floor': removeFloorToBackend(point.lineData.floorUuid); break;
|
||||
case 'Zone': removeZoneToBackend(point.lineData.zoneUuid); break;
|
||||
case 'Aisle': removeAisleToBackend(point.lineData.aisleUuid); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
|
||||
switch (point.type) {
|
||||
case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break;
|
||||
case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break;
|
||||
case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break;
|
||||
case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break;
|
||||
case 'Wall': updateWallToBackend(point.lineData.wallUuid, point.lineData); break;
|
||||
case 'Floor': updateFloorToBackend(point.lineData.floorUuid, point.lineData); break;
|
||||
case 'Zone': updateZoneToBackend(point.lineData.zoneUuid, point.lineData); break;
|
||||
case 'Aisle': updateAisleToBackend(point.lineData.aisleUuid, point.lineData); break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const createWallFromBackend = (wallData: Wall) => {
|
||||
const createWallToBackend = (wallData: Wall) => {
|
||||
addWall(wallData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -119,7 +118,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeWallFromBackend = (wallUuid: string) => {
|
||||
const removeWallToBackend = (wallUuid: string) => {
|
||||
removeWall(wallUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -140,7 +139,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => {
|
||||
const updateWallToBackend = (wallUuid: string, updatedData: Wall) => {
|
||||
updateWall(wallUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -161,7 +160,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const createFloorFromBackend = (floorData: Floor) => {
|
||||
const createFloorToBackend = (floorData: Floor) => {
|
||||
addFloor(floorData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -182,7 +181,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeFloorFromBackend = (floorUuid: string) => {
|
||||
const removeFloorToBackend = (floorUuid: string) => {
|
||||
removeFloor(floorUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -203,7 +202,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => {
|
||||
const updateFloorToBackend = (floorUuid: string, updatedData: Floor) => {
|
||||
updateFloor(floorUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -224,7 +223,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const createZoneFromBackend = (zoneData: Zone) => {
|
||||
const createZoneToBackend = (zoneData: Zone) => {
|
||||
addZone(zoneData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -245,7 +244,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeZoneFromBackend = (zoneUuid: string) => {
|
||||
const removeZoneToBackend = (zoneUuid: string) => {
|
||||
removeZone(zoneUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -266,7 +265,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => {
|
||||
const updateZoneToBackend = (zoneUuid: string, updatedData: Zone) => {
|
||||
updateZone(zoneUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -287,7 +286,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const createAisleFromBackend = (aisleData: Aisle) => {
|
||||
const createAisleToBackend = (aisleData: Aisle) => {
|
||||
addAisle(aisleData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -308,7 +307,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeAisleFromBackend = (aisleUuid: string) => {
|
||||
const removeAisleToBackend = (aisleUuid: string) => {
|
||||
removeAisle(aisleUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -329,7 +328,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => {
|
||||
const updateAisleToBackend = (aisleUuid: string, updatedData: Aisle) => {
|
||||
updateAisle(aisleUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -353,4 +352,4 @@ function useUndoHandler() {
|
||||
return { handleUndo };
|
||||
}
|
||||
|
||||
export default useUndoHandler;
|
||||
export default use2DUndoHandler;
|
||||
@@ -0,0 +1,318 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../../../functions/getUserData";
|
||||
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../sceneContext";
|
||||
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
|
||||
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi";
|
||||
|
||||
function use3DRedoHandler() {
|
||||
const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext();
|
||||
const { deleteEvent } = productStore();
|
||||
const { addEvent, removeEvent } = eventStore();
|
||||
const { updateAsset, removeAsset, addAsset } = assetStore();
|
||||
const { redo3D, peekRedo3D } = undoRedo3DStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { socket } = useSocketStore();
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleRedo = () => {
|
||||
const redoData = peekRedo3D();
|
||||
if (!redoData) return;
|
||||
|
||||
if (redoData.type === 'Scene') {
|
||||
const { actions } = redoData;
|
||||
|
||||
actions.forEach(action => {
|
||||
const { actionType } = action;
|
||||
|
||||
if ('asset' in action) {
|
||||
const asset = action.asset;
|
||||
|
||||
if (actionType === 'Asset-Add') {
|
||||
handleAdd(asset);
|
||||
} else if (actionType === 'Asset-Delete') {
|
||||
handleDelete(asset);
|
||||
} else if (actionType === 'Asset-Update') {
|
||||
handleUpdate(asset);
|
||||
} else if (actionType === 'Asset-Copied') {
|
||||
handleCopy(asset);
|
||||
} else if (actionType === 'Asset-Duplicated') {
|
||||
handleDuplicate(asset);
|
||||
}
|
||||
|
||||
} else if ('assets' in action) {
|
||||
const assets = action.assets;
|
||||
|
||||
if (actionType === 'Assets-Add') {
|
||||
assets.forEach(handleAdd);
|
||||
} else if (actionType === 'Assets-Delete') {
|
||||
assets.forEach(handleDelete);
|
||||
} else if (actionType === 'Assets-Update') {
|
||||
assets.forEach(handleUpdate);
|
||||
} else if (actionType === 'Assets-Copied') {
|
||||
assets.forEach(handleCopy);
|
||||
} else if (actionType === 'Assets-Duplicated') {
|
||||
assets.forEach(handleDuplicate);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (redoData.type === 'UI') {
|
||||
// Handle UI actions if needed
|
||||
}
|
||||
|
||||
redo3D();
|
||||
};
|
||||
|
||||
|
||||
const handleAdd = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': addAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': addWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': deleteAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = (asset: AssetData) => {
|
||||
if (!asset.newData) return;
|
||||
switch (asset.type) {
|
||||
case 'Asset': updateAssetToBackend(asset.newData.modelUuid, asset.newData); break;
|
||||
case 'WallAsset': updateWallAssetToBackend(asset.newData.modelUuid, asset.newData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': copyAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': copyWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleDuplicate = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': duplicateAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const addAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => {
|
||||
updateAsset(modelUuid, updatedData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: updatedData.modelUuid,
|
||||
modelName: updatedData.modelName,
|
||||
assetId: updatedData.assetId,
|
||||
position: updatedData.position,
|
||||
rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const copyAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const addWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const deleteWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const copyWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const duplicateWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
return { handleRedo };
|
||||
}
|
||||
|
||||
export default use3DRedoHandler;
|
||||
@@ -0,0 +1,323 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../../../functions/getUserData";
|
||||
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../sceneContext";
|
||||
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
|
||||
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi";
|
||||
|
||||
function use3DUndoHandler() {
|
||||
const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext();
|
||||
const { deleteEvent } = productStore();
|
||||
const { addEvent, removeEvent } = eventStore();
|
||||
const { updateAsset, removeAsset, addAsset } = assetStore();
|
||||
const { undo3D, peekUndo3D } = undoRedo3DStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { socket } = useSocketStore();
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleUndo = () => {
|
||||
const unDoData = peekUndo3D();
|
||||
if (!unDoData) return;
|
||||
|
||||
if (unDoData.type === 'Scene') {
|
||||
const { actions } = unDoData;
|
||||
|
||||
actions.forEach(action => {
|
||||
const { actionType } = action;
|
||||
|
||||
if ('asset' in action) {
|
||||
const asset = action.asset;
|
||||
|
||||
if (actionType === 'Asset-Add') {
|
||||
handleDelete(asset);
|
||||
} else if (actionType === 'Asset-Delete') {
|
||||
handleAdd(asset);
|
||||
} else if (actionType === 'Asset-Update') {
|
||||
handleUpdate(asset);
|
||||
} else if (actionType === 'Asset-Copied') {
|
||||
handleCopy(asset);
|
||||
} else if (actionType === 'Asset-Duplicated') {
|
||||
handleDuplicate(asset);
|
||||
}
|
||||
|
||||
} else if ('assets' in action) {
|
||||
const assets = action.assets;
|
||||
|
||||
if (actionType === 'Assets-Add') {
|
||||
assets.forEach(handleDelete);
|
||||
} else if (actionType === 'Assets-Delete') {
|
||||
assets.forEach(handleAdd);
|
||||
} else if (actionType === 'Assets-Update') {
|
||||
assets.forEach(handleUpdate);
|
||||
} else if (actionType === 'Assets-Copied') {
|
||||
assets.forEach(handleCopy);
|
||||
} else if (actionType === 'Assets-Duplicated') {
|
||||
assets.forEach(handleDuplicate);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (unDoData.type === 'UI') {
|
||||
// Handle UI actions if needed
|
||||
}
|
||||
|
||||
undo3D();
|
||||
};
|
||||
|
||||
|
||||
const handleAdd = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': addAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': addWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': deleteAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': updateAssetToBackend(asset.assetData.modelUuid, asset.assetData); break;
|
||||
case 'WallAsset': updateWallAssetToBackend(asset.assetData.modelUuid, asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': copyAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': copyWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleDuplicate = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': duplicateAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const addAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => {
|
||||
updateAsset(modelUuid, updatedData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: updatedData.modelUuid,
|
||||
modelName: updatedData.modelName,
|
||||
assetId: updatedData.assetId,
|
||||
position: updatedData.position,
|
||||
rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const copyAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const addWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const deleteWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const copyWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const duplicateWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
return { handleUndo };
|
||||
}
|
||||
|
||||
export default use3DUndoHandler;
|
||||
@@ -4,15 +4,15 @@ import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModi
|
||||
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
|
||||
import { useVersionContext } from '../../../../builder/version/versionContext';
|
||||
|
||||
import useUndoHandler from '../handlers/useUndoHandler';
|
||||
import useRedoHandler from '../handlers/useRedoHandler';
|
||||
import use2DUndoHandler from '../handlers/use2DUndoHandler';
|
||||
import use2DRedoHandler from '../handlers/use2DRedoHandler';
|
||||
|
||||
function UndoRedo2DControls() {
|
||||
const { undoRedo2DStore } = useSceneContext();
|
||||
const { undoStack, redoStack } = undoRedo2DStore();
|
||||
const { toggleView } = useToggleView();
|
||||
const { handleUndo } = useUndoHandler();
|
||||
const { handleRedo } = useRedoHandler();
|
||||
const { handleUndo } = use2DUndoHandler();
|
||||
const { handleRedo } = use2DRedoHandler();
|
||||
const { socket } = useSocketStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useSceneContext } from '../../../sceneContext'
|
||||
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
|
||||
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
|
||||
import { useVersionContext } from '../../../../builder/version/versionContext';
|
||||
import useModuleStore from '../../../../../store/useModuleStore';
|
||||
|
||||
import use3DUndoHandler from '../handlers/use3DUndoHandler';
|
||||
import use3DRedoHandler from '../handlers/use3DRedoHandler';
|
||||
|
||||
function UndoRedo3DControls() {
|
||||
const { undoRedo3DStore } = useSceneContext();
|
||||
const { undoStack, redoStack } = undoRedo3DStore();
|
||||
const { toggleView } = useToggleView();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { handleUndo } = use3DUndoHandler();
|
||||
const { handleRedo } = use3DRedoHandler();
|
||||
const { socket } = useSocketStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
|
||||
useEffect(() => {
|
||||
console.log(undoStack, redoStack);
|
||||
}, [undoStack, redoStack]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (keyCombination === 'Ctrl+Z') {
|
||||
handleUndo();
|
||||
}
|
||||
|
||||
if (keyCombination === 'Ctrl+Y') {
|
||||
handleRedo();
|
||||
}
|
||||
};
|
||||
|
||||
if (!toggleView) {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [toggleView, undoStack, redoStack, socket, selectedVersion, activeModule]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default UndoRedo3DControls;
|
||||
22
app/src/modules/scene/helpers/StatsHelper.tsx
Normal file
22
app/src/modules/scene/helpers/StatsHelper.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Stats } from "@react-three/drei";
|
||||
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
|
||||
|
||||
export default function StatsHelper() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
if (keyCombination === "F1") {
|
||||
event.preventDefault();
|
||||
setVisible(prev => !prev);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return visible ? <Stats className="stats" /> : null;
|
||||
}
|
||||
@@ -2,8 +2,7 @@ import { useEffect, useMemo } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { Physics } from "@react-three/rapier";
|
||||
import { Color, SRGBColorSpace } from "three";
|
||||
import { KeyboardControls, Stats } from "@react-three/drei";
|
||||
import { useSceneContext } from "./sceneContext";
|
||||
import { KeyboardControls } from "@react-three/drei";
|
||||
|
||||
import Builder from "../builder/builder";
|
||||
import Visualization from "../visualization/visualization";
|
||||
@@ -15,9 +14,12 @@ import useModuleStore from "../../store/useModuleStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
||||
import { getUserData } from "../../functions/getUserData";
|
||||
import { useSceneContext } from "./sceneContext";
|
||||
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
||||
|
||||
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
|
||||
import StatsHelper from "./helpers/StatsHelper";
|
||||
|
||||
export default function Scene({ layout }: { readonly layout: "Main Layout" | "Comparison Layout"; }) {
|
||||
const map = useMemo(() => [
|
||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||
@@ -34,28 +36,27 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId && loadingProgress > 1) return;
|
||||
getAllProjects(userId, organization)
|
||||
.then((projects) => {
|
||||
if (!projects || !projects.Projects) return;
|
||||
let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId);
|
||||
const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName('canvas')[0];
|
||||
if (!canvas) return;
|
||||
const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png");
|
||||
const updateProjects = {
|
||||
projectId: project?.projectUuid,
|
||||
organization,
|
||||
userId,
|
||||
projectName: project?.projectName,
|
||||
thumbnail: screenshotDataUrl,
|
||||
};
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:update", updateProjects);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [activeModule, assets, loadingProgress])
|
||||
getAllProjects(userId, organization).then((projects) => {
|
||||
if (!projects || !projects.Projects) return;
|
||||
let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId);
|
||||
const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0];
|
||||
if (!canvas) return;
|
||||
const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png");
|
||||
const updateProjects = {
|
||||
projectId: project?._id,
|
||||
organization,
|
||||
userId,
|
||||
projectName: project?.projectName,
|
||||
thumbnail: screenshotDataUrl,
|
||||
};
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:update", updateProjects);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [activeModule, assets, loadingProgress]);
|
||||
|
||||
return (
|
||||
<KeyboardControls map={map}>
|
||||
@@ -64,14 +65,10 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
||||
shadows
|
||||
color="#aaaa"
|
||||
eventPrefix="client"
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onContextMenu={(e) => { e.preventDefault(); }}
|
||||
performance={{ min: 0.9, max: 1.0 }}
|
||||
onCreated={(e) => {
|
||||
e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d);
|
||||
}}
|
||||
gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }}
|
||||
onCreated={(e) => { e.scene.background = layout === "Main Layout" ? null : new Color(0x19191d); }}
|
||||
gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true }}
|
||||
>
|
||||
<Setup />
|
||||
<Collaboration />
|
||||
@@ -84,7 +81,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
||||
<PhysicsSimulator />
|
||||
</Physics>
|
||||
<Visualization />
|
||||
<Stats />
|
||||
<StatsHelper />
|
||||
</Canvas>
|
||||
</KeyboardControls>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore
|
||||
import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore';
|
||||
|
||||
import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore';
|
||||
import { createUndoRedo3DStore, UndoRedo3DStoreType } from '../../store/builder/useUndoRedo3DStore';
|
||||
|
||||
import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore';
|
||||
import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore';
|
||||
@@ -19,6 +20,7 @@ import { createConveyorStore, ConveyorStoreType } from '../../store/simulation/u
|
||||
import { createVehicleStore, VehicleStoreType } from '../../store/simulation/useVehicleStore';
|
||||
import { createStorageUnitStore, StorageUnitStoreType } from '../../store/simulation/useStorageUnitStore';
|
||||
import { createHumanStore, HumanStoreType } from '../../store/simulation/useHumanStore';
|
||||
import { createCraneStore, CraneStoreType } from '../../store/simulation/useCraneStore';
|
||||
|
||||
import { createColliderStore, ColliderStoreType } from '../../store/useColliderStore';
|
||||
|
||||
@@ -32,6 +34,7 @@ type SceneContextValue = {
|
||||
floorStore: FloorStoreType,
|
||||
|
||||
undoRedo2DStore: UndoRedo2DStoreType,
|
||||
undoRedo3DStore: UndoRedo3DStoreType,
|
||||
|
||||
eventStore: EventStoreType,
|
||||
productStore: ProductStoreType,
|
||||
@@ -43,10 +46,12 @@ type SceneContextValue = {
|
||||
vehicleStore: VehicleStoreType;
|
||||
storageUnitStore: StorageUnitStoreType;
|
||||
humanStore: HumanStoreType;
|
||||
craneStore: CraneStoreType;
|
||||
|
||||
colliderStore: ColliderStoreType;
|
||||
|
||||
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
|
||||
craneEventManagerRef: React.RefObject<CraneEventManagerState>;
|
||||
|
||||
clearStores: () => void;
|
||||
|
||||
@@ -71,6 +76,7 @@ export function SceneProvider({
|
||||
const floorStore = useMemo(() => createFloorStore(), []);
|
||||
|
||||
const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []);
|
||||
const undoRedo3DStore = useMemo(() => createUndoRedo3DStore(), []);
|
||||
|
||||
const eventStore = useMemo(() => createEventStore(), []);
|
||||
const productStore = useMemo(() => createProductStore(), []);
|
||||
@@ -82,10 +88,12 @@ export function SceneProvider({
|
||||
const vehicleStore = useMemo(() => createVehicleStore(), []);
|
||||
const storageUnitStore = useMemo(() => createStorageUnitStore(), []);
|
||||
const humanStore = useMemo(() => createHumanStore(), []);
|
||||
const craneStore = useMemo(() => createCraneStore(), []);
|
||||
|
||||
const colliderStore = useMemo(() => createColliderStore(), []);
|
||||
|
||||
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
|
||||
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
|
||||
|
||||
const clearStores = useMemo(() => () => {
|
||||
assetStore.getState().clearAssets();
|
||||
@@ -95,6 +103,7 @@ export function SceneProvider({
|
||||
zoneStore.getState().clearZones();
|
||||
floorStore.getState().clearFloors();
|
||||
undoRedo2DStore.getState().clearUndoRedo2D();
|
||||
undoRedo3DStore.getState().clearUndoRedo3D();
|
||||
eventStore.getState().clearEvents();
|
||||
productStore.getState().clearProducts();
|
||||
materialStore.getState().clearMaterials();
|
||||
@@ -104,9 +113,11 @@ export function SceneProvider({
|
||||
vehicleStore.getState().clearVehicles();
|
||||
storageUnitStore.getState().clearStorageUnits();
|
||||
humanStore.getState().clearHumans();
|
||||
colliderStore.getState().clearColliders();
|
||||
craneStore.getState().clearCranes();
|
||||
humanEventManagerRef.current.humanStates = [];
|
||||
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, colliderStore]);
|
||||
craneEventManagerRef.current.craneStates = [];
|
||||
colliderStore.getState().clearColliders();
|
||||
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, undoRedo3DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, colliderStore]);
|
||||
|
||||
const contextValue = useMemo(() => (
|
||||
{
|
||||
@@ -117,6 +128,7 @@ export function SceneProvider({
|
||||
zoneStore,
|
||||
floorStore,
|
||||
undoRedo2DStore,
|
||||
undoRedo3DStore,
|
||||
eventStore,
|
||||
productStore,
|
||||
materialStore,
|
||||
@@ -126,12 +138,14 @@ export function SceneProvider({
|
||||
vehicleStore,
|
||||
storageUnitStore,
|
||||
humanStore,
|
||||
colliderStore,
|
||||
craneStore,
|
||||
humanEventManagerRef,
|
||||
craneEventManagerRef,
|
||||
colliderStore,
|
||||
clearStores,
|
||||
layout
|
||||
}
|
||||
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, colliderStore, clearStores, layout]);
|
||||
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, undoRedo3DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, colliderStore, clearStores, layout]);
|
||||
|
||||
return (
|
||||
<SceneContext.Provider value={contextValue}>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useCallback } from "react";
|
||||
import { useSceneContext } from "../../../../scene/sceneContext";
|
||||
import { useProductContext } from "../../../products/productContext";
|
||||
|
||||
export function usePickAndDropHandler() {
|
||||
const { materialStore, craneStore, productStore } = useSceneContext();
|
||||
const { getMaterialById } = materialStore();
|
||||
const { getModelUuidByActionUuid } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { incrementCraneLoad, addCurrentMaterial, addCurrentAction } = craneStore();
|
||||
|
||||
const pickAndDropLogStatus = (materialUuid: string, status: string) => {
|
||||
echo.info(`${materialUuid}, ${status}`);
|
||||
}
|
||||
|
||||
const handlePickAndDrop = useCallback((action: CraneAction, materialId?: string) => {
|
||||
if (!action || action.actionType !== 'pickAndDrop' || !materialId) return;
|
||||
|
||||
const material = getMaterialById(materialId);
|
||||
if (!material) return;
|
||||
|
||||
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
||||
if (!modelUuid) return;
|
||||
|
||||
incrementCraneLoad(modelUuid, 1);
|
||||
addCurrentAction(modelUuid, action.actionUuid, material.materialType, materialId);
|
||||
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
|
||||
|
||||
pickAndDropLogStatus(material.materialName, `performing pickAndDrop action`);
|
||||
|
||||
}, [getMaterialById]);
|
||||
|
||||
return {
|
||||
handlePickAndDrop,
|
||||
};
|
||||
}
|
||||
36
app/src/modules/simulation/actions/crane/useCraneActions.ts
Normal file
36
app/src/modules/simulation/actions/crane/useCraneActions.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { usePickAndDropHandler } from './actionHandler/usePickAndDropHandler';
|
||||
|
||||
export function useCraneActions() {
|
||||
const { handlePickAndDrop } = usePickAndDropHandler();
|
||||
|
||||
const handleWorkerAction = useCallback((action: CraneAction, materialId: string) => {
|
||||
handlePickAndDrop(action, materialId);
|
||||
}, [handlePickAndDrop]);
|
||||
|
||||
const handleCraneAction = useCallback((action: CraneAction, materialId: string) => {
|
||||
if (!action) return;
|
||||
|
||||
switch (action.actionType) {
|
||||
case 'pickAndDrop':
|
||||
handleWorkerAction(action, materialId);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown Human action type: ${action.actionType}`);
|
||||
}
|
||||
}, [handleWorkerAction]);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
handleCraneAction,
|
||||
cleanup
|
||||
};
|
||||
}
|
||||
@@ -6,13 +6,15 @@ import { useProductContext } from "../../../products/productContext";
|
||||
import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager";
|
||||
|
||||
export function useRetrieveHandler() {
|
||||
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext();
|
||||
const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore, humanEventManagerRef } = useSceneContext();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { addMaterial } = materialStore();
|
||||
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore();
|
||||
const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore();
|
||||
const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore();
|
||||
const { getConveyorById } = conveyorStore();
|
||||
const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
|
||||
const { getCraneById, incrementCraneLoad, addCurrentMaterial: addCurrentMaterialToCrane, addCurrentAction: addCurrentActionToCrane } = craneStore();
|
||||
const { getAssetById, setCurrentAnimation } = assetStore();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { getArmBotById, addCurrentAction } = armBotStore();
|
||||
@@ -27,6 +29,7 @@ export function useRetrieveHandler() {
|
||||
const retrievalTimeRef = useRef<Map<string, number>>(new Map());
|
||||
const retrievalCountRef = useRef<Map<string, number>>(new Map());
|
||||
const monitoredHumansRef = useRef<Set<string>>(new Set());
|
||||
const cranePickupLockRef = useRef<Map<string, boolean>>(new Map());
|
||||
|
||||
const [initialDelayComplete, setInitialDelayComplete] = useState(false);
|
||||
const delayTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -60,9 +63,9 @@ export function useRetrieveHandler() {
|
||||
actionUuid: action.actionUuid
|
||||
},
|
||||
current: {
|
||||
modelUuid: action.triggers[0]?.triggeredAsset.triggeredModel.modelUuid,
|
||||
pointUuid: action.triggers[0]?.triggeredAsset.triggeredPoint.pointUuid,
|
||||
actionUuid: action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid
|
||||
modelUuid: modelUuid,
|
||||
pointUuid: pointUuid,
|
||||
actionUuid: action.actionUuid
|
||||
},
|
||||
};
|
||||
|
||||
@@ -371,6 +374,32 @@ export function useRetrieveHandler() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (triggeredModel?.type === 'transfer') {
|
||||
const model = getConveyorById(triggeredModel.modelUuid);
|
||||
if (model && !model.isPaused) {
|
||||
if (humanAsset?.animationState?.current === 'idle') {
|
||||
setCurrentAnimation(human.modelUuid, 'pickup', true, false, false);
|
||||
} else if (humanAsset?.animationState?.current === 'pickup' && humanAsset.animationState.isCompleted) {
|
||||
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
||||
if (lastMaterial) {
|
||||
const material = createNewMaterial(
|
||||
lastMaterial.materialId,
|
||||
lastMaterial.materialType,
|
||||
storageUnit.point.action
|
||||
);
|
||||
if (material) {
|
||||
removeLastMaterial(storageUnit.modelUuid);
|
||||
updateCurrentLoad(storageUnit.modelUuid, -1);
|
||||
incrementHumanLoad(human.modelUuid, 1);
|
||||
addCurrentMaterialToHuman(human.modelUuid, material.materialType, material.materialId);
|
||||
retrieveLogStatus(material.materialName, `is picked by ${human.modelName}`);
|
||||
|
||||
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (triggeredModel?.type === 'machine') {
|
||||
const machine = getMachineById(triggeredModel.modelUuid);
|
||||
if (machine && !machine.isActive && machine.state === 'idle' && !machine.currentAction) {
|
||||
@@ -423,7 +452,57 @@ export function useRetrieveHandler() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (triggeredModel?.type === 'crane') {
|
||||
const crane = getCraneById(triggeredModel.modelUuid);
|
||||
const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '');
|
||||
const currentCount = retrievalCountRef.current.get(actionUuid) ?? 0;
|
||||
|
||||
if (!crane) return;
|
||||
|
||||
const hasLock = cranePickupLockRef.current.get(crane.modelUuid) || false;
|
||||
|
||||
if (action && action.actionType === 'pickAndDrop' && !hasLock && !crane.isCarrying && !crane.isActive && crane.currentLoad < (action?.maxPickUpCount || 0)) {
|
||||
const material = getLastMaterial(storageUnit.modelUuid);
|
||||
if (material) {
|
||||
if (action.triggers[0].triggeredAsset?.triggeredModel.modelUuid && action.triggers[0].triggeredAsset.triggeredAction?.actionUuid) {
|
||||
const human = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid);
|
||||
if (human && human.type === 'human') {
|
||||
if (!monitoredHumansRef.current.has(human.modelUuid)) {
|
||||
addHumanToMonitor(human.modelUuid, () => {
|
||||
incrementCraneLoad(crane.modelUuid, 1);
|
||||
addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId);
|
||||
addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId);
|
||||
cranePickupLockRef.current.set(crane.modelUuid, true);
|
||||
}, action.triggers[0].triggeredAsset.triggeredAction?.actionUuid)
|
||||
}
|
||||
monitoredHumansRef.current.add(human.modelUuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (crane.isCarrying && crane.currentPhase === 'pickup-drop' && hasLock) {
|
||||
cranePickupLockRef.current.set(crane.modelUuid, false);
|
||||
|
||||
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
||||
if (lastMaterial) {
|
||||
const material = createNewMaterial(
|
||||
lastMaterial.materialId,
|
||||
lastMaterial.materialType,
|
||||
storageUnit.point.action
|
||||
);
|
||||
if (material) {
|
||||
removeLastMaterial(storageUnit.modelUuid);
|
||||
updateCurrentLoad(storageUnit.modelUuid, -1);
|
||||
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
||||
}
|
||||
}
|
||||
} else if (!action) {
|
||||
const action = getActionByUuid(selectedProduct.productUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid || '');
|
||||
if (action) {
|
||||
addCurrentActionToCrane(crane.modelUuid, action.actionUuid, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (hasChanges || completedActions.length > 0) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
|
||||
import { useStorageActions } from "./storageUnit/useStorageUnitActions";
|
||||
import { useVehicleActions } from "./vehicle/useVehicleActions";
|
||||
import { useHumanActions } from "./human/useHumanActions";
|
||||
import { useCraneActions } from "./crane/useCraneActions";
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
export function useActionHandler() {
|
||||
@@ -19,6 +20,7 @@ export function useActionHandler() {
|
||||
const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions();
|
||||
const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions();
|
||||
const { handleHumanAction, cleanup: cleanupHuman } = useHumanActions();
|
||||
const { handleCraneAction, cleanup: cleanupCrane } = useCraneActions();
|
||||
|
||||
const handleAction = useCallback((action: Action, materialId?: string) => {
|
||||
if (!action) return;
|
||||
@@ -42,6 +44,9 @@ export function useActionHandler() {
|
||||
case 'worker': case 'assembly':
|
||||
handleHumanAction(action as HumanAction, materialId as string);
|
||||
break;
|
||||
case 'pickAndDrop':
|
||||
handleCraneAction(action as CraneAction, materialId as string);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown action type: ${(action as Action).actionType}`);
|
||||
}
|
||||
@@ -49,7 +54,7 @@ export function useActionHandler() {
|
||||
echo.error("Failed to handle action");
|
||||
console.error("Error handling action:", error);
|
||||
}
|
||||
}, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction]);
|
||||
}, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction, handleCraneAction]);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
cleanupConveyor();
|
||||
@@ -58,7 +63,8 @@ export function useActionHandler() {
|
||||
cleanupMachine();
|
||||
cleanupStorage();
|
||||
cleanupHuman();
|
||||
}, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman]);
|
||||
cleanupCrane();
|
||||
}, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman, cleanupCrane]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
15
app/src/modules/simulation/crane/crane.tsx
Normal file
15
app/src/modules/simulation/crane/crane.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import CraneInstances from './instances/craneInstances'
|
||||
|
||||
function Crane() {
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<CraneInstances />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Crane
|
||||
@@ -0,0 +1,170 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../products/productContext';
|
||||
|
||||
export function useCraneEventManager() {
|
||||
const { craneStore, productStore, assetStore, vehicleStore, armBotStore, machineStore, craneEventManagerRef } = useSceneContext();
|
||||
const { getCraneById, setCurrentPhase, removeCurrentAction } = craneStore();
|
||||
const { getAssetById } = assetStore();
|
||||
const { getVehicleById } = vehicleStore();
|
||||
const { getArmBotById } = armBotStore();
|
||||
const { getMachineById } = machineStore();
|
||||
const { getActionByUuid, getEventByModelUuid } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
|
||||
useEffect(() => {
|
||||
if ((isReset || !isPlaying) && craneEventManagerRef.current) {
|
||||
craneEventManagerRef.current.craneStates = [];
|
||||
}
|
||||
}, [isReset, isPlaying]);
|
||||
|
||||
const addCraneToMonitor = (craneId: string, callback: () => void, actionUuid: string) => {
|
||||
const crane = getCraneById(craneId);
|
||||
const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as CraneAction | undefined;
|
||||
|
||||
if (!crane || !action || action.actionType !== 'pickAndDrop' || !craneEventManagerRef.current) return;
|
||||
|
||||
let state = craneEventManagerRef.current.craneStates.find(c => c.craneId === craneId);
|
||||
if (!state) {
|
||||
state = {
|
||||
craneId,
|
||||
pendingActions: [],
|
||||
currentAction: null,
|
||||
isProcessing: false
|
||||
};
|
||||
craneEventManagerRef.current.craneStates.push(state);
|
||||
}
|
||||
|
||||
state.pendingActions.push({
|
||||
actionUuid,
|
||||
callback
|
||||
});
|
||||
|
||||
if (!state.isProcessing) {
|
||||
processNextAction(state);
|
||||
}
|
||||
};
|
||||
|
||||
const processNextAction = (state: CraneEventState) => {
|
||||
if (state.pendingActions.length === 0) {
|
||||
state.currentAction = null;
|
||||
return;
|
||||
}
|
||||
|
||||
state.isProcessing = true;
|
||||
state.currentAction = state.pendingActions.shift() || null;
|
||||
};
|
||||
|
||||
const completeCurrentAction = (state: CraneEventState) => {
|
||||
processNextAction(state);
|
||||
};
|
||||
|
||||
useFrame(() => {
|
||||
if (!craneEventManagerRef.current || craneEventManagerRef.current.craneStates.length === 0 || !isPlaying || isPaused) return;
|
||||
|
||||
for (const craneState of craneEventManagerRef.current.craneStates) {
|
||||
if (!craneState.currentAction || !craneState.isProcessing) continue;
|
||||
|
||||
const { craneId, currentAction } = craneState;
|
||||
const crane = getCraneById(craneId);
|
||||
const craneAsset = getAssetById(craneId);
|
||||
const currentCraneAction = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '') as CraneAction | undefined;
|
||||
|
||||
if (!crane || !craneAsset || !currentCraneAction) continue;
|
||||
if (crane.isActive || crane.state !== "idle") continue;
|
||||
|
||||
if (currentCraneAction.actionType === 'pickAndDrop' && crane.currentLoad < currentCraneAction.maxPickUpCount) {
|
||||
const humanAction = getActionByUuid(selectedProduct.productUuid, currentCraneAction.triggers[0].triggeredAsset?.triggeredAction?.actionUuid || '');
|
||||
if (humanAction) {
|
||||
const nextEvent = getEventByModelUuid(selectedProduct.productUuid, humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid || '')
|
||||
if (nextEvent) {
|
||||
if (nextEvent.type === 'transfer') {
|
||||
if (currentAction.actionUuid !== crane.currentAction?.actionUuid) {
|
||||
setCurrentPhase(crane.modelUuid, 'init');
|
||||
removeCurrentAction(crane.modelUuid);
|
||||
}
|
||||
|
||||
craneState.isProcessing = false;
|
||||
currentAction.callback();
|
||||
|
||||
setTimeout(() => {
|
||||
completeCurrentAction(craneState);
|
||||
}, 1000);
|
||||
} else if (nextEvent.type === 'vehicle') {
|
||||
const vehicle = getVehicleById(nextEvent.modelUuid);
|
||||
|
||||
if (vehicle && !vehicle.isActive && vehicle.currentPhase === 'picking') {
|
||||
if (currentAction.actionUuid !== crane.currentAction?.actionUuid) {
|
||||
setCurrentPhase(crane.modelUuid, 'init');
|
||||
removeCurrentAction(crane.modelUuid);
|
||||
}
|
||||
|
||||
craneState.isProcessing = false;
|
||||
currentAction.callback();
|
||||
|
||||
setTimeout(() => {
|
||||
completeCurrentAction(craneState);
|
||||
}, 1000);
|
||||
}
|
||||
} else if (nextEvent.type === 'roboticArm') {
|
||||
const armBot = getArmBotById(nextEvent.modelUuid);
|
||||
|
||||
if (armBot && !armBot.isActive) {
|
||||
if (currentAction.actionUuid !== crane.currentAction?.actionUuid) {
|
||||
setCurrentPhase(crane.modelUuid, 'init');
|
||||
removeCurrentAction(crane.modelUuid);
|
||||
}
|
||||
|
||||
craneState.isProcessing = false;
|
||||
currentAction.callback();
|
||||
|
||||
setTimeout(() => {
|
||||
completeCurrentAction(craneState);
|
||||
}, 1000);
|
||||
}
|
||||
} else if (nextEvent.type === 'machine') {
|
||||
const machine = getMachineById(nextEvent.modelUuid);
|
||||
|
||||
if (machine && !machine.isActive) {
|
||||
if (currentAction.actionUuid !== crane.currentAction?.actionUuid) {
|
||||
setCurrentPhase(crane.modelUuid, 'init');
|
||||
removeCurrentAction(crane.modelUuid);
|
||||
}
|
||||
|
||||
craneState.isProcessing = false;
|
||||
currentAction.callback();
|
||||
|
||||
setTimeout(() => {
|
||||
completeCurrentAction(craneState);
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
if (currentAction.actionUuid !== crane.currentAction?.actionUuid) {
|
||||
setCurrentPhase(crane.modelUuid, 'init');
|
||||
removeCurrentAction(crane.modelUuid);
|
||||
}
|
||||
|
||||
craneState.isProcessing = false;
|
||||
currentAction.callback();
|
||||
|
||||
setTimeout(() => {
|
||||
completeCurrentAction(craneState);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
addCraneToMonitor
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { MaterialModel } from '../../../materials/instances/material/materialModel';
|
||||
|
||||
type MaterialAnimatorProps = {
|
||||
crane: CraneStatus;
|
||||
};
|
||||
|
||||
export default function MaterialAnimator({ crane }: Readonly<MaterialAnimatorProps>) {
|
||||
const materialRef = useRef<any>(null);
|
||||
const { scene } = useThree();
|
||||
const [isRendered, setIsRendered] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (crane.isCarrying) {
|
||||
setIsRendered(true);
|
||||
} else {
|
||||
setIsRendered(false);
|
||||
}
|
||||
}, [crane.isCarrying]);
|
||||
|
||||
useFrame(() => {
|
||||
const craneModel = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||
if (!materialRef.current || !craneModel) return;
|
||||
|
||||
const base = craneModel.getObjectByName('hook');
|
||||
if (!base) return;
|
||||
|
||||
if (crane.isCarrying) {
|
||||
const boneWorldPos = new THREE.Vector3();
|
||||
base.getWorldPosition(boneWorldPos);
|
||||
|
||||
const yOffset = -0.65;
|
||||
boneWorldPos.y += yOffset;
|
||||
|
||||
materialRef.current.position.copy(boneWorldPos);
|
||||
|
||||
materialRef.current.up.set(0, 1, 0);
|
||||
materialRef.current.lookAt(
|
||||
materialRef.current.position.clone().add(new THREE.Vector3(0, 0, 1))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRendered && (
|
||||
<MaterialModel
|
||||
materialId={crane.currentAction?.materialId ?? ''}
|
||||
matRef={materialRef}
|
||||
materialType={crane.currentAction?.materialType ?? 'Default material'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../../products/productContext';
|
||||
import { dragAction } from '@use-gesture/react';
|
||||
|
||||
function PillarJibAnimator({
|
||||
crane,
|
||||
points,
|
||||
setPoints,
|
||||
animationPhase,
|
||||
setAnimationPhase,
|
||||
onAnimationComplete
|
||||
}: {
|
||||
crane: CraneStatus;
|
||||
points: [THREE.Vector3, THREE.Vector3] | null;
|
||||
setPoints: (points: [THREE.Vector3, THREE.Vector3] | null) => void;
|
||||
animationPhase: string;
|
||||
setAnimationPhase: (phase: string) => void;
|
||||
onAnimationComplete: (action: string) => void;
|
||||
}) {
|
||||
const { scene } = useThree();
|
||||
const { assetStore, productStore, materialStore } = useSceneContext();
|
||||
const { getActionByUuid, getPointByUuid } = productStore();
|
||||
const { resetAsset } = assetStore();
|
||||
const { setIsVisible } = materialStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
|
||||
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlaying || isReset) {
|
||||
resetAsset(crane.modelUuid);
|
||||
setAnimationPhase('idle');
|
||||
setPoints(null);
|
||||
}
|
||||
}, [isPlaying, scene, crane.modelUuid, isReset]);
|
||||
|
||||
useEffect(() => {
|
||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||
|
||||
if (!model) return;
|
||||
const hook = model.getObjectByName('hook');
|
||||
|
||||
if (!hook) return;
|
||||
const hookWorld = new THREE.Vector3();
|
||||
hook.getWorldPosition(hookWorld);
|
||||
|
||||
if (crane.currentPhase === 'init-pickup') {
|
||||
if (crane.currentMaterials.length > 0) {
|
||||
const materials = scene.getObjectsByProperty('uuid', crane.currentMaterials[0].materialId);
|
||||
console.log('materials: ', materials);
|
||||
const material = materials.find((material) => material.visible === true);
|
||||
if (material) {
|
||||
const materialWorld = new THREE.Vector3();
|
||||
material.getWorldPosition(materialWorld);
|
||||
setAnimationPhase('init-hook-adjust');
|
||||
setPoints(
|
||||
[
|
||||
new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z),
|
||||
new THREE.Vector3(materialWorld.x, materialWorld.y + 0.5, materialWorld.z)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (crane.currentPhase === 'pickup-drop') {
|
||||
if (crane.currentMaterials.length > 0) {
|
||||
const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '');
|
||||
const humanAction = getActionByUuid(selectedProduct.productUuid, action?.triggers[0].triggeredAsset?.triggeredAction?.actionUuid || '');
|
||||
|
||||
if (humanAction) {
|
||||
const point = getPointByUuid(selectedProduct.productUuid, humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid || '', humanAction.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid || '');
|
||||
const eventModel = scene.getObjectByProperty('uuid', humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid);
|
||||
if (point && eventModel) {
|
||||
const pointLocal = new THREE.Vector3(...point.position);
|
||||
const pointWorld = pointLocal.clone().applyMatrix4(eventModel.matrixWorld);
|
||||
|
||||
const startPoint = new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z);
|
||||
const endPoint = new THREE.Vector3(pointWorld.x, pointWorld.y + 0.5, pointWorld.z);
|
||||
|
||||
setIsVisible(crane.currentMaterials[0].materialId, false);
|
||||
|
||||
setAnimationPhase('init-hook-adjust');
|
||||
setPoints([startPoint, endPoint]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [crane.currentPhase])
|
||||
|
||||
useEffect(() => {
|
||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||
if (!model || !model.userData.fieldData) return;
|
||||
|
||||
const base = model.getObjectByName('base');
|
||||
const trolley = model.getObjectByName('trolley');
|
||||
const hook = model.getObjectByName('hook');
|
||||
|
||||
if (!base || !trolley || !hook || !points) return;
|
||||
|
||||
const baseWorld = new THREE.Vector3();
|
||||
base.getWorldPosition(baseWorld);
|
||||
|
||||
const trolleyWorld = new THREE.Vector3();
|
||||
trolley.getWorldPosition(trolleyWorld);
|
||||
|
||||
const hookWorld = new THREE.Vector3();
|
||||
hook.getWorldPosition(hookWorld);
|
||||
|
||||
const trolleyMinOffset = model.userData.trolleyMinOffset || -1;
|
||||
const trolleyMaxOffset = model.userData.trolleyMaxOffset || 1.75;
|
||||
const hookMinOffset = model.userData.hookMinOffset || 0.25;
|
||||
const hookMaxOffset = model.userData.hookMaxOffset || -1.5;
|
||||
|
||||
const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
|
||||
const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05);
|
||||
const outerRadius = Math.max(
|
||||
new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length() + trolleyMaxOffset,
|
||||
innerRadius
|
||||
);
|
||||
|
||||
const yMin = hookWorld.y + hookMaxOffset;
|
||||
const yMax = hookWorld.y + hookMinOffset;
|
||||
|
||||
function clampToCylinder(pos: THREE.Vector3) {
|
||||
const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
|
||||
const distance = xzDist.length();
|
||||
|
||||
let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
|
||||
if (distance > 0) {
|
||||
clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
|
||||
}
|
||||
|
||||
const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
|
||||
const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);
|
||||
|
||||
return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
|
||||
}
|
||||
|
||||
const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()];
|
||||
const newIsInside: [boolean, boolean] = [false, false];
|
||||
|
||||
points.forEach((point, i) => {
|
||||
const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length();
|
||||
const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius;
|
||||
const insideY = point.y >= yMin && point.y <= yMax;
|
||||
newIsInside[i] = insideXZ && insideY;
|
||||
|
||||
newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point);
|
||||
});
|
||||
|
||||
setClampedPoints(newClampedPoints);
|
||||
}, [crane.modelUuid, points]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return;
|
||||
|
||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||
if (!model) return;
|
||||
|
||||
const base = model.getObjectByName('base');
|
||||
const trolley = model.getObjectByName('trolley');
|
||||
const hook = model.getObjectByName('hook');
|
||||
|
||||
if (!base || !trolley || !hook || !trolley.parent) return;
|
||||
|
||||
const baseWorld = new THREE.Vector3();
|
||||
base.getWorldPosition(baseWorld);
|
||||
|
||||
const hookWorld = new THREE.Vector3();
|
||||
hook.getWorldPosition(hookWorld);
|
||||
|
||||
if (!model.userData.animationData) {
|
||||
model.userData.animationData = {
|
||||
originalHookY: hook.position.y,
|
||||
targetHookY: clampedPoints[0].y - baseWorld.y,
|
||||
targetDirection: new THREE.Vector2(),
|
||||
targetTrolleyX: 0,
|
||||
targetWorldPosition: clampedPoints[0].clone(),
|
||||
finalHookTargetY: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const { animationData } = model.userData;
|
||||
const hookSpeed = (model.userData.hookSpeed || 0.01) * speed;
|
||||
const rotationSpeed = (model.userData.rotationSpeed || 0.005) * speed;
|
||||
const trolleySpeed = (model.userData.trolleySpeed || 0.01) * speed;
|
||||
|
||||
const threshold = Math.max(0.01, 0.05 / speed);
|
||||
|
||||
switch (animationPhase) {
|
||||
case 'init-hook-adjust': {
|
||||
const hookWorld = new THREE.Vector3();
|
||||
hook.getWorldPosition(hookWorld);
|
||||
const targetY = clampedPoints[0].y;
|
||||
const direction = Math.sign(targetY - hookWorld.y);
|
||||
|
||||
hook.position.y = THREE.MathUtils.lerp(
|
||||
hook.position.y,
|
||||
hook.position.y + direction * 0.1,
|
||||
Math.min(hookSpeed, 0.9)
|
||||
);
|
||||
|
||||
if (Math.abs(hookWorld.y - targetY) < threshold) {
|
||||
hook.position.y = targetY - baseWorld.y;
|
||||
setAnimationPhase('init-rotate-base');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'init-rotate-base': {
|
||||
const baseForward = new THREE.Vector3(1, 0, 0);
|
||||
base.localToWorld(baseForward);
|
||||
baseForward.sub(baseWorld);
|
||||
|
||||
const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();
|
||||
const targetWorld = clampedPoints[0];
|
||||
const targetDir = new THREE.Vector2(
|
||||
targetWorld.x - baseWorld.x,
|
||||
targetWorld.z - baseWorld.z
|
||||
).normalize();
|
||||
|
||||
const currentAngle = Math.atan2(currentDir.y, currentDir.x);
|
||||
const targetAngle = Math.atan2(targetDir.y, targetDir.x);
|
||||
let angleDiff = currentAngle - targetAngle;
|
||||
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
|
||||
|
||||
if (Math.abs(angleDiff) > threshold) {
|
||||
base.rotation.y = THREE.MathUtils.lerp(
|
||||
base.rotation.y,
|
||||
base.rotation.y - angleDiff,
|
||||
Math.min(rotationSpeed, 0.9)
|
||||
);
|
||||
} else {
|
||||
base.rotation.y -= angleDiff;
|
||||
const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone());
|
||||
animationData.targetTrolleyX = localTarget?.x;
|
||||
setAnimationPhase('init-move-trolley');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'init-move-trolley': {
|
||||
const dx = animationData.targetTrolleyX - trolley.position.x;
|
||||
|
||||
trolley.position.x = THREE.MathUtils.lerp(
|
||||
trolley.position.x,
|
||||
animationData.targetTrolleyX,
|
||||
Math.min(trolleySpeed, 0.9)
|
||||
);
|
||||
|
||||
if (Math.abs(dx) < threshold) {
|
||||
trolley.position.x = animationData.targetTrolleyX;
|
||||
animationData.finalHookTargetY = clampedPoints[0].y - baseWorld.y;
|
||||
setAnimationPhase('init-final-hook-adjust');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'init-final-hook-adjust': {
|
||||
const targetY = clampedPoints[0].y - baseWorld.y;
|
||||
|
||||
hook.position.y = THREE.MathUtils.lerp(
|
||||
hook.position.y,
|
||||
targetY,
|
||||
Math.min(hookSpeed, 0.9)
|
||||
);
|
||||
|
||||
if (Math.abs(hook.position.y - targetY) < threshold) {
|
||||
hook.position.y = targetY;
|
||||
|
||||
model.userData.animationData = {
|
||||
originalHookY: hook.position.y,
|
||||
targetHookY: clampedPoints[1].y - baseWorld.y + 0.5,
|
||||
targetDirection: new THREE.Vector2(),
|
||||
targetTrolleyX: 0,
|
||||
targetWorldPosition: clampedPoints[1].clone(),
|
||||
finalHookTargetY: 0,
|
||||
};
|
||||
|
||||
setAnimationPhase('starting');
|
||||
onAnimationComplete('starting');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'first-hook-adjust': {
|
||||
const direction = Math.sign(animationData.targetHookY - hook.position.y);
|
||||
hook.position.y += direction * hookSpeed;
|
||||
|
||||
if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) {
|
||||
hook.position.y = animationData.targetHookY;
|
||||
setAnimationPhase('first-rotate-base');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'first-rotate-base': {
|
||||
const baseForward = new THREE.Vector3(1, 0, 0);
|
||||
base.localToWorld(baseForward);
|
||||
baseForward.sub(baseWorld);
|
||||
|
||||
const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();
|
||||
|
||||
const targetWorld = clampedPoints[1];
|
||||
const targetDir = new THREE.Vector2(
|
||||
targetWorld.x - baseWorld.x,
|
||||
targetWorld.z - baseWorld.z
|
||||
).normalize();
|
||||
|
||||
const currentAngle = Math.atan2(currentDir.y, currentDir.x);
|
||||
const targetAngle = Math.atan2(targetDir.y, targetDir.x);
|
||||
let angleDiff = currentAngle - targetAngle;
|
||||
|
||||
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
|
||||
|
||||
if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) {
|
||||
base.rotation.y += Math.sign(angleDiff) * rotationSpeed;
|
||||
} else {
|
||||
base.rotation.y += angleDiff;
|
||||
const localTarget = trolley.parent.worldToLocal(clampedPoints[1].clone());
|
||||
animationData.targetTrolleyX = localTarget?.x;
|
||||
setAnimationPhase('first-move-trolley');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'first-move-trolley': {
|
||||
const dx = animationData.targetTrolleyX - trolley.position.x;
|
||||
const direction = Math.sign(dx);
|
||||
trolley.position.x += direction * trolleySpeed;
|
||||
|
||||
if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) {
|
||||
trolley.position.x = animationData.targetTrolleyX;
|
||||
|
||||
animationData.finalHookTargetY = hook.position.y - 0.5;
|
||||
setAnimationPhase('first-final-hook-adjust');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'first-final-hook-adjust': {
|
||||
const dy = animationData.finalHookTargetY - hook.position.y;
|
||||
const direction = Math.sign(dy);
|
||||
hook.position.y += direction * hookSpeed;
|
||||
|
||||
if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) {
|
||||
hook.position.y = animationData.finalHookTargetY;
|
||||
if (crane.currentPhase === 'init-pickup') {
|
||||
setAnimationPhase('picking');
|
||||
onAnimationComplete('picking');
|
||||
} else if (crane.currentPhase === 'pickup-drop') {
|
||||
setAnimationPhase('dropping');
|
||||
onAnimationComplete('dropping');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PillarJibAnimator;
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import PillarJibInstance from './instance/pillarJibInstance';
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
|
||||
function CraneInstances() {
|
||||
const { craneStore } = useSceneContext();
|
||||
const { cranes } = craneStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{cranes.map((crane: CraneStatus) => (
|
||||
<React.Fragment key={crane.modelUuid}>
|
||||
|
||||
{crane.subType === "pillarJib" &&
|
||||
<PillarJibInstance key={crane.modelUuid} crane={crane} />
|
||||
}
|
||||
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CraneInstances
|
||||
@@ -0,0 +1,171 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { Box, Sphere } from "@react-three/drei";
|
||||
|
||||
function PillarJibHelper({
|
||||
crane,
|
||||
points,
|
||||
isHelperNeeded
|
||||
}: {
|
||||
crane: CraneStatus,
|
||||
points: [THREE.Vector3, THREE.Vector3] | null;
|
||||
isHelperNeeded: boolean;
|
||||
}) {
|
||||
const { scene } = useThree();
|
||||
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
||||
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
|
||||
|
||||
const baseWorldRef = useRef<THREE.Vector3 | null>(null);
|
||||
const trolleyWorldRef = useRef<THREE.Vector3 | null>(null);
|
||||
const hookWorldRef = useRef<THREE.Vector3 | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const model = scene.getObjectByProperty("uuid", crane.modelUuid);
|
||||
if (!model) return;
|
||||
|
||||
const base = model.getObjectByName("base");
|
||||
const trolley = model.getObjectByName("trolley");
|
||||
const hook = model.getObjectByName("hook");
|
||||
|
||||
if (!base || !trolley || !hook) return;
|
||||
|
||||
const bw = new THREE.Vector3();
|
||||
const tw = new THREE.Vector3();
|
||||
const hw = new THREE.Vector3();
|
||||
|
||||
base.getWorldPosition(bw);
|
||||
trolley.getWorldPosition(tw);
|
||||
hook.getWorldPosition(hw);
|
||||
|
||||
baseWorldRef.current = bw;
|
||||
trolleyWorldRef.current = tw;
|
||||
hookWorldRef.current = hw;
|
||||
}, [scene, crane.modelUuid]);
|
||||
|
||||
const { geometry, position } = useMemo(() => {
|
||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||
if (!model || !baseWorldRef.current || !trolleyWorldRef.current || !hookWorldRef.current) return { geometry: null, position: null };
|
||||
|
||||
const base = model.getObjectByName('base');
|
||||
const trolley = model.getObjectByName('trolley');
|
||||
const hook = model.getObjectByName('hook');
|
||||
|
||||
if (!base || !trolley || !hook || !points) return { geometry: null, position: null };
|
||||
|
||||
const baseWorld = baseWorldRef.current;
|
||||
|
||||
const trolleyWorld = trolleyWorldRef.current;
|
||||
|
||||
const hookWorld = hookWorldRef.current;
|
||||
|
||||
const trolleyMinOffset = model.userData.trolleyMinOffset || -1;
|
||||
const trolleyMaxOffset = model.userData.trolleyMaxOffset || 1.75;
|
||||
const hookMinOffset = model.userData.hookMinOffset || 0.25;
|
||||
const hookMaxOffset = model.userData.hookMaxOffset || -1.5;
|
||||
|
||||
const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
|
||||
const outerRadius = distFromBase + trolleyMaxOffset;
|
||||
const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05);
|
||||
const height = (hookMinOffset - (hookMaxOffset));
|
||||
const cylinderYPosition = hookWorld.y + (height / 2) + (hookMaxOffset + hookMinOffset) / 2;
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false);
|
||||
|
||||
const hole = new THREE.Path();
|
||||
hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true);
|
||||
shape.holes.push(hole);
|
||||
|
||||
const extrudeSettings = {
|
||||
depth: height,
|
||||
bevelEnabled: false,
|
||||
steps: 1
|
||||
};
|
||||
|
||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||
const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];
|
||||
|
||||
const yMin = hookWorld.y + hookMaxOffset;
|
||||
const yMax = hookWorld.y + hookMinOffset;
|
||||
|
||||
function clampToCylinder(pos: THREE.Vector3) {
|
||||
const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
|
||||
const distance = xzDist.length();
|
||||
|
||||
let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
|
||||
if (distance > 0) {
|
||||
clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
|
||||
}
|
||||
|
||||
const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
|
||||
const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);
|
||||
|
||||
return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
|
||||
}
|
||||
|
||||
const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()];
|
||||
const newIsInside: [boolean, boolean] = [false, false];
|
||||
|
||||
points.forEach((point, i) => {
|
||||
const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length();
|
||||
const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius;
|
||||
const insideY = point.y >= yMin && point.y <= yMax;
|
||||
newIsInside[i] = insideXZ && insideY;
|
||||
|
||||
newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point);
|
||||
});
|
||||
|
||||
setClampedPoints(newClampedPoints);
|
||||
setIsInside(newIsInside);
|
||||
|
||||
return { geometry, position };
|
||||
}, [scene, crane.modelUuid, points]);
|
||||
|
||||
if (!geometry || !position) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isHelperNeeded &&
|
||||
<>
|
||||
<mesh
|
||||
geometry={geometry}
|
||||
position={position}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={0x888888}
|
||||
metalness={0.5}
|
||||
roughness={0.4}
|
||||
side={THREE.DoubleSide}
|
||||
transparent={true}
|
||||
opacity={0.3}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{points && points.map((point, i) => (
|
||||
<Box
|
||||
key={`original-${i}`}
|
||||
position={point}
|
||||
args={[0.15, 0.15, 0.15]}
|
||||
>
|
||||
<meshStandardMaterial color="yellow" />
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{clampedPoints && clampedPoints.map((point, i) => (
|
||||
<Sphere
|
||||
key={`clamped-${i}`}
|
||||
position={point}
|
||||
args={[0.1, 16, 16]}
|
||||
>
|
||||
<meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
|
||||
</Sphere>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PillarJibHelper;
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import * as THREE from 'three'
|
||||
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../../products/productContext';
|
||||
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
|
||||
|
||||
import PillarJibAnimator from '../animator/pillarJibAnimator'
|
||||
import PillarJibHelper from '../helper/pillarJibHelper'
|
||||
import MaterialAnimator from '../animator/materialAnimator';
|
||||
|
||||
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { craneStore, productStore, humanStore, assetStore } = useSceneContext();
|
||||
const { triggerPointActions } = useTriggerHandler();
|
||||
const { getActionByUuid } = productStore();
|
||||
const { setCurrentPhase, setCraneActive, setIsCaryying, removeCurrentAction, removeLastMaterial, decrementCraneLoad } = craneStore();
|
||||
const { setCurrentPhase: setCurrentPhaseHuman, setHumanActive, setHumanState, getHumanById } = humanStore();
|
||||
const { setCurrentAnimation, getAssetById } = assetStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const [animationPhase, setAnimationPhase] = useState<string>('idle');
|
||||
const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null);
|
||||
const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '');
|
||||
const actionTriggers = action?.triggers || [];
|
||||
const humanId = actionTriggers?.[0]?.triggeredAsset?.triggeredModel?.modelUuid ?? null;
|
||||
const humanAsset = getAssetById(humanId || '');
|
||||
const humanAction = getActionByUuid(selectedProduct.productUuid, actionTriggers?.[0]?.triggeredAsset?.triggeredAction?.actionUuid ?? '');
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlaying) {
|
||||
const human = getHumanById(humanId || '');
|
||||
if (!human || !humanAsset || !humanId || !action || action.actionType !== 'pickAndDrop') return;
|
||||
|
||||
if (!crane.isActive && crane.currentPhase === 'init' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) {
|
||||
setCurrentPhase(crane.modelUuid, 'init-pickup');
|
||||
} else if (crane.currentPhase === 'picking' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && !crane.isCarrying) {
|
||||
if (action.triggers.length > 0) {
|
||||
if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted && humanId && humanAction && humanAction.actionType === 'operator') {
|
||||
setCurrentAnimation(humanId, 'idle', true, true, true);
|
||||
setIsCaryying(crane.modelUuid, true);
|
||||
setCurrentPhase(crane.modelUuid, 'pickup-drop');
|
||||
} else {
|
||||
setCurrentPhaseHuman(humanId, 'hooking');
|
||||
setHumanActive(humanId, true);
|
||||
setHumanState(humanId, 'running');
|
||||
setCurrentAnimation(humanId, 'working_standing', true, false, false);
|
||||
}
|
||||
}
|
||||
} else if (crane.currentPhase === 'dropping' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length && crane.isCarrying && human.currentPhase === 'hooking') {
|
||||
setCurrentPhaseHuman(humanId, 'loadPoint-unloadPoint');
|
||||
} else if (human.state === 'running' && human.currentPhase === 'unhooking') {
|
||||
if (humanAsset?.animationState?.current === "working_standing" && humanAsset?.animationState?.isCompleted) {
|
||||
setCurrentPhase(crane.modelUuid, 'init');
|
||||
setCraneActive(crane.modelUuid, false);
|
||||
setCurrentAnimation(humanId, 'idle', true, true, true);
|
||||
setCurrentPhaseHuman(humanId, 'init');
|
||||
setHumanActive(humanId, false);
|
||||
setHumanState(humanId, 'idle');
|
||||
handleMaterialDrop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [crane, humanAsset?.animationState?.isCompleted])
|
||||
|
||||
const handleMaterialDrop = () => {
|
||||
if (humanAction && humanAction.actionType === 'operator') {
|
||||
setIsCaryying(crane.modelUuid, false);
|
||||
removeCurrentAction(crane.modelUuid);
|
||||
const removedMaterial = removeLastMaterial(crane.modelUuid);
|
||||
decrementCraneLoad(crane.modelUuid, 1);
|
||||
|
||||
if (removedMaterial && humanAction.triggers[0].triggeredAsset?.triggeredAction?.actionUuid) {
|
||||
triggerPointActions(humanAction, removedMaterial.materialId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleAnimationComplete = (action: string) => {
|
||||
if (action === 'starting') {
|
||||
setAnimationPhase('first-hook-adjust');
|
||||
} else if (action === 'picking') {
|
||||
setCurrentPhase(crane.modelUuid, 'picking');
|
||||
} else if (action === 'dropping') {
|
||||
setCurrentPhase(crane.modelUuid, 'dropping');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<PillarJibAnimator
|
||||
key={crane.modelUuid}
|
||||
crane={crane}
|
||||
points={points}
|
||||
setPoints={setPoints}
|
||||
animationPhase={animationPhase}
|
||||
setAnimationPhase={setAnimationPhase}
|
||||
onAnimationComplete={handleAnimationComplete}
|
||||
/>
|
||||
|
||||
<MaterialAnimator crane={crane} />
|
||||
|
||||
<PillarJibHelper
|
||||
crane={crane}
|
||||
points={points}
|
||||
isHelperNeeded={false}
|
||||
/>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PillarJibInstance;
|
||||
@@ -40,7 +40,7 @@ function PointsCalculator(
|
||||
}
|
||||
|
||||
return {
|
||||
points: [worldTopMiddle]
|
||||
points: [new THREE.Vector3(worldTopMiddle.x, worldTopMiddle.y + 0.1, worldTopMiddle.z)]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ function PointInstances() {
|
||||
machine: "purple",
|
||||
storageUnit: "red",
|
||||
human: "white",
|
||||
crane: "yellow",
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -173,6 +173,22 @@ function TriggerConnector() {
|
||||
});
|
||||
});
|
||||
}
|
||||
// Handle Human point
|
||||
else if (event.type === "crane" && 'point' in event) {
|
||||
const point = event.point;
|
||||
point.actions?.forEach(action => {
|
||||
action.triggers?.forEach(trigger => {
|
||||
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||
newConnections.push({
|
||||
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||
startPointUuid: point.uuid,
|
||||
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
trigger
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setConnections(newConnections);
|
||||
@@ -182,31 +198,23 @@ function TriggerConnector() {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
let drag = false;
|
||||
let isRightMouseDown = false;
|
||||
let isLeftMouseDown = false;
|
||||
|
||||
const onMouseDown = (evt: MouseEvent) => {
|
||||
if (selectedAsset) {
|
||||
clearSelectedAsset();
|
||||
}
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown = true;
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = (evt: MouseEvent) => {
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown = false;
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isRightMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRightClick = (evt: MouseEvent) => {
|
||||
if (drag) return;
|
||||
evt.preventDefault();
|
||||
|
||||
@@ -368,13 +376,16 @@ function TriggerConnector() {
|
||||
} else if (firstSelectedPoint) {
|
||||
setFirstSelectedPoint(null);
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseMove = () => {
|
||||
drag = true;
|
||||
};
|
||||
|
||||
if (subModule === 'mechanics' && toolMode === 'cursor' && selectedAction.actionId && selectedAction.actionName) {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener('contextmenu', handleRightClick);
|
||||
} else {
|
||||
setFirstSelectedPoint(null);
|
||||
}
|
||||
@@ -383,7 +394,6 @@ function TriggerConnector() {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener('contextmenu', handleRightClick);
|
||||
};
|
||||
|
||||
}, [gl, subModule, selectedProduct, firstSelectedPoint, toolMode, selectedAction]);
|
||||
|
||||
@@ -25,7 +25,7 @@ export function useHumanEventManager() {
|
||||
const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => {
|
||||
const human = getHumanById(humanId);
|
||||
const action = getActionByUuid(selectedProduct.productUuid, actionUuid);
|
||||
if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker') || !humanEventManagerRef.current) return;
|
||||
if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker' && action.actionType !== 'operator') || !humanEventManagerRef.current) return;
|
||||
|
||||
let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId);
|
||||
if (!state) {
|
||||
@@ -36,7 +36,7 @@ export function useHumanEventManager() {
|
||||
const existingAction = state.actionQueue.find(a => a.actionUuid === actionUuid);
|
||||
if (existingAction) {
|
||||
const currentCount = existingAction.count ?? 0;
|
||||
if (existingAction.actionType === 'worker') {
|
||||
if (existingAction.actionType === 'worker' || existingAction.actionType === 'operator') {
|
||||
if (currentCount < existingAction.maxLoadCount) {
|
||||
existingAction.callback = callback;
|
||||
existingAction.isMonitored = true;
|
||||
@@ -98,8 +98,8 @@ export function useHumanEventManager() {
|
||||
|
||||
let conditionMet = false;
|
||||
|
||||
if (currentAction.actionType === 'worker') {
|
||||
if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) {
|
||||
if (currentAction.actionType === 'worker' || currentAction.actionType === 'operator') {
|
||||
if ((action.actionType === 'worker' || action.actionType === 'operator') && human.currentLoad < currentAction.loadCapacity) {
|
||||
conditionMet = true;
|
||||
} else if (action.actionType === 'assembly') {
|
||||
conditionMet = true;
|
||||
@@ -107,7 +107,7 @@ export function useHumanEventManager() {
|
||||
} else if (currentAction.actionType === 'assembly') {
|
||||
if (action.actionType === 'assembly') {
|
||||
conditionMet = true;
|
||||
} else if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) {
|
||||
} else if ((action.actionType === 'worker' || action.actionType === 'operator') && human.currentLoad < currentAction.loadCapacity) {
|
||||
conditionMet = true;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export function useHumanEventManager() {
|
||||
action.callback();
|
||||
action.count = (action.count ?? 0) + 1;
|
||||
action.isMonitored = false;
|
||||
if ((action.actionType === 'worker' && action.count >= action.maxLoadCount) ||
|
||||
if (((action.actionType === 'worker' || action.actionType === 'operator') && action.count >= action.maxLoadCount) ||
|
||||
(action.actionType === 'assembly' && action.count >= action.maxAssemblyCount)) {
|
||||
action.isCompleted = true;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const MaterialAnimator = ({ human }: { human: HumanStatus; }) => {
|
||||
{hasLoad && action && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && (
|
||||
<MaterialModel
|
||||
matRef={meshRef}
|
||||
materialId={human.currentMaterials[0].materialId || ''}
|
||||
materialId={`human-${human.currentMaterials[0].materialId}` || ''}
|
||||
materialType={human.currentMaterials[0].materialType || 'Default material'}
|
||||
visible={isAttached}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { Line } from '@react-three/drei';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../../products/productContext';
|
||||
|
||||
interface WorkerAnimatorProps {
|
||||
path: [number, number, number][];
|
||||
handleCallBack: () => void;
|
||||
reset: () => void;
|
||||
human: HumanStatus;
|
||||
}
|
||||
|
||||
function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly<WorkerAnimatorProps>) {
|
||||
const { humanStore, assetStore, productStore } = useSceneContext();
|
||||
const { getActionByUuid } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { getHumanById } = humanStore();
|
||||
const { setCurrentAnimation } = assetStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { isReset, setReset } = useResetButtonStore();
|
||||
const progressRef = useRef<number>(0);
|
||||
const movingForward = useRef<boolean>(true);
|
||||
const completedRef = useRef<boolean>(false);
|
||||
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
|
||||
const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.pickUpPoint?.rotation || [0, 0, 0]);
|
||||
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||
const { scene } = useThree();
|
||||
|
||||
useEffect(() => {
|
||||
if (!human.currentAction?.actionUuid) return;
|
||||
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
|
||||
if (human.currentPhase === 'init-loadPoint' && path.length > 0) {
|
||||
setCurrentPath(path);
|
||||
setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null);
|
||||
} else if (human.currentPhase === 'loadPoint-unloadPoint' && path.length > 0) {
|
||||
setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null);
|
||||
setCurrentPath(path);
|
||||
} else if (human.currentPhase === 'unloadPoint-loadPoint' && path.length > 0) {
|
||||
setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null);
|
||||
setCurrentPath(path);
|
||||
}
|
||||
}, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]);
|
||||
|
||||
useEffect(() => {
|
||||
completedRef.current = false;
|
||||
}, [currentPath]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset || !isPlaying) {
|
||||
reset();
|
||||
setCurrentPath([]);
|
||||
completedRef.current = false;
|
||||
movingForward.current = true;
|
||||
progressRef.current = 0;
|
||||
setReset(false);
|
||||
setRestingRotation(true);
|
||||
const object = scene.getObjectByProperty('uuid', human.modelUuid);
|
||||
const humanData = getHumanById(human.modelUuid);
|
||||
if (object && humanData) {
|
||||
object.position.set(humanData.position[0], humanData.position[1], humanData.position[2]);
|
||||
object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]);
|
||||
}
|
||||
}
|
||||
}, [isReset, isPlaying]);
|
||||
|
||||
const lastTimeRef = useRef(performance.now());
|
||||
|
||||
useFrame(() => {
|
||||
const now = performance.now();
|
||||
const delta = (now - lastTimeRef.current) / 1000;
|
||||
lastTimeRef.current = now;
|
||||
|
||||
const object = scene.getObjectByProperty('uuid', human.modelUuid);
|
||||
if (!object || currentPath.length < 2) return;
|
||||
if (isPaused || !isPlaying) return;
|
||||
|
||||
let totalDistance = 0;
|
||||
const distances = [];
|
||||
let accumulatedDistance = 0;
|
||||
let index = 0;
|
||||
const rotationSpeed = 1.5;
|
||||
|
||||
for (let i = 0; i < currentPath.length - 1; i++) {
|
||||
const start = new THREE.Vector3(...currentPath[i]);
|
||||
const end = new THREE.Vector3(...currentPath[i + 1]);
|
||||
const segmentDistance = start.distanceTo(end);
|
||||
distances.push(segmentDistance);
|
||||
totalDistance += segmentDistance;
|
||||
}
|
||||
|
||||
while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) {
|
||||
accumulatedDistance += distances[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < distances.length) {
|
||||
const start = new THREE.Vector3(...currentPath[index]);
|
||||
const end = new THREE.Vector3(...currentPath[index + 1]);
|
||||
const segmentDistance = distances[index];
|
||||
|
||||
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(
|
||||
new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0))
|
||||
);
|
||||
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
||||
targetQuaternion.multiply(y180);
|
||||
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
if (angle < 0.01) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
} else {
|
||||
const step = rotationSpeed * delta * speed * human.speed;
|
||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
||||
}
|
||||
|
||||
const isAligned = angle < 0.01;
|
||||
|
||||
if (isAligned) {
|
||||
progressRef.current += delta * (speed * human.speed);
|
||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||
const position = start.clone().lerp(end, t);
|
||||
object.position.copy(position);
|
||||
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
|
||||
} else {
|
||||
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
if (restRotation && objectRotation) {
|
||||
const targetEuler = new THREE.Euler(0, objectRotation[1], 0);
|
||||
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
|
||||
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
||||
const targetQuaternion = baseQuaternion.multiply(y180);
|
||||
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
if (angle < 0.01) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
setRestingRotation(false);
|
||||
} else {
|
||||
const step = rotationSpeed * delta * speed * human.speed;
|
||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
||||
}
|
||||
|
||||
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
setRestingRotation(true);
|
||||
progressRef.current = 0;
|
||||
movingForward.current = !movingForward.current;
|
||||
setCurrentPath([]);
|
||||
handleCallBack();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentPath.length > 0 && (
|
||||
<group visible={false}>
|
||||
<Line points={currentPath} color="blue" lineWidth={3} />
|
||||
{currentPath.map((point, index) => (
|
||||
<mesh key={index} position={point}>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OperatorAnimator;
|
||||
@@ -0,0 +1,168 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import { NavMeshQuery } from '@recast-navigation/core';
|
||||
import { useNavMesh } from '../../../../../../store/builder/store';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../../store/usePlayButtonStore';
|
||||
import { useTriggerHandler } from '../../../../triggers/triggerHandler/useTriggerHandler';
|
||||
import { useSceneContext } from '../../../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../../../products/productContext';
|
||||
|
||||
import OperatorAnimator from '../../animator/operatorAnimator';
|
||||
|
||||
function OperatorInstance({ human }: { human: HumanStatus }) {
|
||||
const { navMesh } = useNavMesh();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { scene } = useThree();
|
||||
const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, craneStore, productStore } = useSceneContext();
|
||||
const { removeMaterial, setEndTime, setIsVisible } = materialStore();
|
||||
const { getStorageUnitById } = storageUnitStore();
|
||||
const { getArmBotById } = armBotStore();
|
||||
const { getConveyorById } = conveyorStore();
|
||||
const { getVehicleById } = vehicleStore();
|
||||
const { getMachineById } = machineStore();
|
||||
const { getCraneById } = craneStore();
|
||||
const { triggerPointActions } = useTriggerHandler();
|
||||
const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore();
|
||||
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime, setCurrentPhase } = humanStore();
|
||||
|
||||
const [path, setPath] = useState<[number, number, number][]>([]);
|
||||
const pauseTimeRef = useRef<number | null>(null);
|
||||
const idleTimeRef = useRef<number>(0);
|
||||
const activeTimeRef = useRef<number>(0);
|
||||
const isPausedRef = useRef<boolean>(false);
|
||||
const isSpeedRef = useRef<number>(0);
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const previousTimeRef = useRef<number | null>(null);
|
||||
const animationFrameIdRef = useRef<number | null>(null);
|
||||
const humanAsset = getAssetById(human.modelUuid);
|
||||
|
||||
useEffect(() => {
|
||||
isPausedRef.current = isPaused;
|
||||
}, [isPaused]);
|
||||
|
||||
useEffect(() => {
|
||||
isSpeedRef.current = speed;
|
||||
}, [speed]);
|
||||
|
||||
const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => {
|
||||
try {
|
||||
const navMeshQuery = new NavMeshQuery(navMesh);
|
||||
let startPoint = new THREE.Vector3(start[0], start[1], start[2]);
|
||||
let endPoint = new THREE.Vector3(end[0], end[1], end[2]);
|
||||
const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint);
|
||||
if (
|
||||
segmentPath.length > 0 &&
|
||||
Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) &&
|
||||
Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.z)
|
||||
) {
|
||||
return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || [];
|
||||
} else {
|
||||
console.log("There is no path here...Choose valid path")
|
||||
const { path: segmentPaths } = navMeshQuery.computePath(startPoint, startPoint);
|
||||
return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || [];
|
||||
}
|
||||
} catch {
|
||||
console.error("Failed to compute path");
|
||||
return [];
|
||||
}
|
||||
}, [navMesh]);
|
||||
|
||||
function humanStatus(modelId: string, status: string) {
|
||||
// console.log(`${modelId} , ${status}`);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
setCurrentPhase(human.modelUuid, 'init');
|
||||
setHumanActive(human.modelUuid, false);
|
||||
setHumanState(human.modelUuid, 'idle');
|
||||
setHumanScheduled(human.modelUuid, false);
|
||||
setHumanLoad(human.modelUuid, 0);
|
||||
resetAnimation(human.modelUuid);
|
||||
setPath([]);
|
||||
isPausedRef.current = false;
|
||||
pauseTimeRef.current = 0;
|
||||
resetTime(human.modelUuid)
|
||||
activeTimeRef.current = 0
|
||||
idleTimeRef.current = 0
|
||||
previousTimeRef.current = null
|
||||
if (animationFrameIdRef.current !== null) {
|
||||
cancelAnimationFrame(animationFrameIdRef.current)
|
||||
animationFrameIdRef.current = null
|
||||
}
|
||||
const object = scene.getObjectByProperty('uuid', human.modelUuid);
|
||||
if (object && human) {
|
||||
object.position.set(human.position[0], human.position[1], human.position[2]);
|
||||
object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlaying) {
|
||||
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
|
||||
if (!action || action.actionType !== 'operator' || !action.pickUpPoint || !action.dropPoint) return;
|
||||
|
||||
if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') {
|
||||
const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid);
|
||||
if (!humanMesh) return;
|
||||
|
||||
const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]);
|
||||
|
||||
setPath(toPickupPath);
|
||||
setCurrentPhase(human.modelUuid, 'init-loadPoint');
|
||||
setHumanState(human.modelUuid, 'running');
|
||||
setHumanActive(human.modelUuid, true);
|
||||
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
|
||||
humanStatus(human.modelUuid, 'Started from init, heading to loadPoint');
|
||||
} else if (human.isActive && human.currentPhase === 'loadPoint-unloadPoint') {
|
||||
if (action.pickUpPoint && action.dropPoint && humanAsset?.animationState?.current === 'idle') {
|
||||
const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]);
|
||||
setPath(toDrop);
|
||||
setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
|
||||
humanStatus(human.modelUuid, 'Started from loadPoint, heading to unloadPoint');
|
||||
}
|
||||
} else if (human.state === 'idle' && human.currentPhase === 'unhooking') {
|
||||
setHumanState(human.modelUuid, 'running');
|
||||
setHumanActive(human.modelUuid, true);
|
||||
setCurrentAnimation(human.modelUuid, 'working_standing', true, false, false);
|
||||
}
|
||||
} else {
|
||||
reset()
|
||||
}
|
||||
}, [human, human.currentAction, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]);
|
||||
|
||||
function handleCallBack() {
|
||||
if (human.currentPhase === 'init-loadPoint') {
|
||||
setCurrentPhase(human.modelUuid, 'waiting');
|
||||
setHumanState(human.modelUuid, 'idle');
|
||||
setHumanActive(human.modelUuid, false);
|
||||
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
|
||||
humanStatus(human.modelUuid, 'Reached loadPoint point, waiting for material');
|
||||
setPath([]);
|
||||
} else if (human.currentPhase === 'loadPoint-unloadPoint') {
|
||||
setCurrentPhase(human.modelUuid, 'unhooking');
|
||||
setHumanState(human.modelUuid, 'idle');
|
||||
setHumanActive(human.modelUuid, false);
|
||||
setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
|
||||
humanStatus(human.modelUuid, 'Reached loadPoint point, waiting for material');
|
||||
setPath([]);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<OperatorAnimator
|
||||
path={path}
|
||||
handleCallBack={handleCallBack}
|
||||
human={human}
|
||||
reset={reset}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OperatorInstance;
|
||||
@@ -6,6 +6,7 @@ import { useProductContext } from '../../../products/productContext';
|
||||
import MaterialAnimator from '../animator/materialAnimator';
|
||||
import AssemblerInstance from './actions/assemberInstance';
|
||||
import WorkerInstance from './actions/workerInstance';
|
||||
import OperatorInstance from './actions/operatorInstance';
|
||||
|
||||
function HumanInstance({ human }: { human: HumanStatus }) {
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
@@ -86,6 +87,9 @@ function HumanInstance({ human }: { human: HumanStatus }) {
|
||||
{action && action.actionType === 'assembly' &&
|
||||
<AssemblerInstance human={human} />
|
||||
}
|
||||
{action && action.actionType === 'operator' &&
|
||||
<OperatorInstance human={human} />
|
||||
}
|
||||
|
||||
<MaterialAnimator human={human} />
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useGLTF } from '@react-three/drei';
|
||||
import * as THREE from 'three'
|
||||
import { Tube, useGLTF } from '@react-three/drei';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore';
|
||||
import { useProductContext } from '../../../products/productContext';
|
||||
@@ -27,7 +28,7 @@ function HumanUi() {
|
||||
const { humanStore, productStore } = useSceneContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { humans, getHumanById } = humanStore();
|
||||
const { updateEvent, updateAction, getActionByUuid } = productStore();
|
||||
const { updateEvent, getActionByUuid } = productStore();
|
||||
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]);
|
||||
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]);
|
||||
const [assemblyPosition, setAssemblyPosition] = useState<[number, number, number]>([0, 1, 0]);
|
||||
@@ -313,64 +314,49 @@ function HumanUi() {
|
||||
rotation={[0, Math.PI, 0]}
|
||||
>
|
||||
{isAssembly ? (
|
||||
<primitive
|
||||
ref={assemblyMarker}
|
||||
<MarkerPrimitive
|
||||
name="assemblyMarker"
|
||||
refProp={assemblyMarker}
|
||||
object={assemblyScene}
|
||||
position={assemblyPosition}
|
||||
rotation={assemblyRotation}
|
||||
onPointerDown={(e: any) => {
|
||||
if (e.object.parent.name === "handle") {
|
||||
handlePointerDown(e, "assembly", "assembly");
|
||||
} else {
|
||||
handlePointerDown(e, "assembly", "assembly");
|
||||
}
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setIsDragging(null);
|
||||
setIsRotating(null);
|
||||
if (controls) (controls as any).enabled = true;
|
||||
}}
|
||||
outerGroupRef={outerGroup}
|
||||
type="assembly"
|
||||
subtype="assembly"
|
||||
color="#0f87f7"
|
||||
setIsDragging={setIsDragging}
|
||||
setIsRotating={setIsRotating}
|
||||
handlePointerDown={handlePointerDown}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<primitive
|
||||
<MarkerPrimitive
|
||||
name="startMarker"
|
||||
refProp={startMarker}
|
||||
object={startScene}
|
||||
ref={startMarker}
|
||||
position={startPosition}
|
||||
rotation={startRotation}
|
||||
onPointerDown={(e: any) => {
|
||||
if (e.object.parent.name === "handle") {
|
||||
handlePointerDown(e, "start", "start");
|
||||
} else {
|
||||
handlePointerDown(e, "start", "start");
|
||||
}
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setIsDragging(null);
|
||||
setIsRotating(null);
|
||||
if (controls) (controls as any).enabled = true;
|
||||
}}
|
||||
outerGroupRef={outerGroup}
|
||||
type="start"
|
||||
subtype="start"
|
||||
color="lightgreen"
|
||||
setIsDragging={setIsDragging}
|
||||
setIsRotating={setIsRotating}
|
||||
handlePointerDown={handlePointerDown}
|
||||
/>
|
||||
|
||||
<primitive
|
||||
<MarkerPrimitive
|
||||
name="endMarker"
|
||||
refProp={endMarker}
|
||||
object={endScene}
|
||||
ref={endMarker}
|
||||
position={endPosition}
|
||||
rotation={endRotation}
|
||||
onPointerDown={(e: any) => {
|
||||
if (e.object.parent.name === "handle") {
|
||||
handlePointerDown(e, "end", "end");
|
||||
} else {
|
||||
handlePointerDown(e, "end", "end");
|
||||
}
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setIsDragging(null);
|
||||
setIsRotating(null);
|
||||
if (controls) (controls as any).enabled = true;
|
||||
}}
|
||||
outerGroupRef={outerGroup}
|
||||
type="end"
|
||||
subtype="end"
|
||||
color="darkorange"
|
||||
setIsDragging={setIsDragging}
|
||||
setIsRotating={setIsRotating}
|
||||
handlePointerDown={handlePointerDown}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -380,4 +366,101 @@ function HumanUi() {
|
||||
);
|
||||
}
|
||||
|
||||
export default HumanUi;
|
||||
export default HumanUi;
|
||||
|
||||
const MarkerPrimitive = ({
|
||||
name,
|
||||
refProp,
|
||||
object,
|
||||
position,
|
||||
rotation,
|
||||
outerGroupRef,
|
||||
type,
|
||||
subtype,
|
||||
color,
|
||||
setIsDragging,
|
||||
setIsRotating,
|
||||
handlePointerDown,
|
||||
}: {
|
||||
name: string;
|
||||
refProp: any;
|
||||
object: THREE.Object3D;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
outerGroupRef: React.RefObject<THREE.Group>;
|
||||
type: string;
|
||||
subtype: string;
|
||||
color: string;
|
||||
setIsDragging: (val: any) => void;
|
||||
setIsRotating: (val: any) => void;
|
||||
handlePointerDown: any;
|
||||
}) => {
|
||||
const { controls, scene, camera } = useThree();
|
||||
const [hitPoint, setHitPoint] = useState<THREE.Vector3 | null>(null);
|
||||
const [curve, setCurve] = useState<THREE.CatmullRomCurve3 | null>(null);
|
||||
|
||||
useFrame(() => {
|
||||
if (!refProp.current || !outerGroupRef.current || !scene) return;
|
||||
|
||||
const worldPos = new THREE.Vector3();
|
||||
refProp.current.getWorldPosition(worldPos);
|
||||
const localMarkerPos = outerGroupRef.current.worldToLocal(worldPos.clone());
|
||||
|
||||
const rayOrigin = worldPos.clone();
|
||||
const direction = new THREE.Vector3(0, -1, 0);
|
||||
const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000);
|
||||
raycaster.camera = camera;
|
||||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||||
|
||||
const hit = intersects.find(i => i.object.name !== name);
|
||||
|
||||
if (hit) {
|
||||
const localHit = outerGroupRef.current.worldToLocal(hit.point.clone());
|
||||
setHitPoint(localHit);
|
||||
|
||||
const newCurve = new THREE.CatmullRomCurve3([
|
||||
localMarkerPos.clone(),
|
||||
localHit.clone(),
|
||||
]);
|
||||
setCurve(newCurve);
|
||||
} else {
|
||||
setHitPoint(null);
|
||||
setCurve(null);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<primitive
|
||||
name={name}
|
||||
ref={refProp}
|
||||
object={object}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
onPointerDown={(e: any) => {
|
||||
handlePointerDown(e, type, subtype);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setIsDragging(null);
|
||||
setIsRotating(null);
|
||||
if (controls) (controls as any).enabled = true;
|
||||
}}
|
||||
/>
|
||||
|
||||
{hitPoint && (
|
||||
<>
|
||||
<mesh name={name} position={hitPoint} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<torusGeometry args={[0.15, 0.02, 3, 32]} />
|
||||
<meshBasicMaterial color={color} depthWrite={false} />
|
||||
</mesh>
|
||||
|
||||
{curve && (
|
||||
<Tube args={[curve, 20, 0.01, 8, true]} >
|
||||
<meshBasicMaterial color={color} depthWrite={false} />
|
||||
</Tube>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -61,30 +61,12 @@ function MaterialInstance({ material }: { readonly material: MaterialSchema }) {
|
||||
function getCurrentSpeed(productUuid: string, modelUuid: string) {
|
||||
const event = getEventByModelUuid(productUuid, modelUuid)
|
||||
if (event) {
|
||||
if (event.type === 'transfer') {
|
||||
return event.speed;
|
||||
}
|
||||
|
||||
if (event.type === 'vehicle') {
|
||||
return event.speed;
|
||||
}
|
||||
|
||||
if (event.type === 'machine') {
|
||||
if (event.type === 'transfer' || event.type === 'machine' || event.type === 'storageUnit') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (event.type === 'roboticArm') {
|
||||
if (event.type === 'vehicle' || event.type === 'roboticArm' || event.type === 'human') {
|
||||
return event.speed;
|
||||
}
|
||||
|
||||
if (event.type === 'storageUnit') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (event.type === 'human') {
|
||||
return event.speed;
|
||||
}
|
||||
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import MaterialInstance from './instance/materialInstance'
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom';
|
||||
import { useVersionContext } from '../../builder/version/versionContext';
|
||||
|
||||
function Products() {
|
||||
const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, layout, productStore } = useSceneContext();
|
||||
const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, layout, productStore } = useSceneContext();
|
||||
const { products, getProductById, addProduct, setProducts } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { setMainProduct } = useMainProduct();
|
||||
@@ -20,7 +20,8 @@ function Products() {
|
||||
const { addMachine, clearMachines } = machineStore();
|
||||
const { addConveyor, clearConveyors } = conveyorStore();
|
||||
const { setCurrentMaterials, clearStorageUnits, updateCurrentLoad, addStorageUnit } = storageUnitStore();
|
||||
const { addHuman, addCurrentAction, clearHumans } = humanStore();
|
||||
const { addHuman, addCurrentAction: addCurrentActionHuman, clearHumans } = humanStore();
|
||||
const { addCrane, addCurrentAction: addCurrentActionCrane, clearCranes } = craneStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { mainProduct } = useMainProduct();
|
||||
@@ -164,7 +165,25 @@ function Products() {
|
||||
addHuman(selectedProduct.productUuid, events);
|
||||
|
||||
if (events.point.actions.length > 0) {
|
||||
addCurrentAction(events.modelUuid, events.point.actions[0].actionUuid);
|
||||
addCurrentActionHuman(events.modelUuid, events.point.actions[0].actionUuid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [selectedProduct, products, isReset, isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProduct.productUuid) {
|
||||
const product = getProductById(selectedProduct.productUuid);
|
||||
if (product) {
|
||||
clearCranes();
|
||||
product.eventDatas.forEach(events => {
|
||||
if (events.type === 'crane') {
|
||||
addCrane(selectedProduct.productUuid, events);
|
||||
|
||||
if (events.point.actions.length > 0) {
|
||||
addCurrentActionCrane(events.modelUuid, events.point.actions[0].actionUuid, null, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { Line, Text } from '@react-three/drei';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../../products/productContext';
|
||||
|
||||
type PointWithDegree = {
|
||||
position: [number, number, number];
|
||||
degree: number;
|
||||
};
|
||||
|
||||
function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone, armBot, path }: any) {
|
||||
interface RoboticArmAnimatorProps {
|
||||
HandleCallback: () => void;
|
||||
restPosition: THREE.Vector3;
|
||||
ikSolver: any;
|
||||
targetBone: string;
|
||||
armBot: ArmBotStatus;
|
||||
path: [number, number, number][];
|
||||
currentPhase: string;
|
||||
}
|
||||
|
||||
function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone, armBot, path, currentPhase }: RoboticArmAnimatorProps) {
|
||||
const progressRef = useRef(0);
|
||||
const curveRef = useRef<THREE.Vector3[] | null>(null);
|
||||
const totalDistanceRef = useRef(0);
|
||||
@@ -19,6 +31,14 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
|
||||
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
|
||||
const [circlePointsWithDegrees, setCirclePointsWithDegrees] = useState<PointWithDegree[]>([]);
|
||||
const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null);
|
||||
const { armBotStore, productStore, materialStore } = useSceneContext();
|
||||
const { getArmBotById } = armBotStore();
|
||||
const { getMaterialById, getMaterialPosition } = materialStore();
|
||||
const { getEventByModelUuid, getActionByUuid, getPointByUuid } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { scene } = useThree();
|
||||
|
||||
let curveHeight = 1.75
|
||||
const CIRCLE_RADIUS = 1.6
|
||||
|
||||
@@ -143,10 +163,106 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
|
||||
|
||||
// Handle nearest points and final path (including arc points)
|
||||
useEffect(() => {
|
||||
if (circlePoints.length > 0 && currentPath.length > 0) {
|
||||
if (circlePoints.length > 0 && currentPath.length > 0 && ikSolver.mesh) {
|
||||
|
||||
const start = currentPath[0];
|
||||
const end = currentPath[currentPath.length - 1];
|
||||
let start = currentPath[0];
|
||||
let end = currentPath[currentPath.length - 1];
|
||||
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
|
||||
|
||||
const armbotStatus = getArmBotById(armBot.modelUuid);
|
||||
const currentMaterial = armbotStatus?.currentAction?.materialId;
|
||||
const currentAction = getActionByUuid(selectedProduct.productUuid, armbotStatus?.currentAction?.actionUuid || '');
|
||||
|
||||
if (armbotStatus && currentMaterial && currentAction && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end' || currentPhase === 'end-to-rest')) {
|
||||
const materialData = getMaterialById(currentMaterial);
|
||||
if (materialData) {
|
||||
const prevModel = getEventByModelUuid(selectedProduct.productUuid, materialData.current.modelUuid);
|
||||
const nextModel = getEventByModelUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '');
|
||||
const nextPoint = getPointByUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '', currentAction?.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '');
|
||||
|
||||
if (prevModel && prevModel.type === 'transfer') {
|
||||
const material = scene.getObjectByProperty("uuid", currentMaterial);
|
||||
const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
|
||||
if (material && armbotModel) {
|
||||
const materialWorldPos = new THREE.Vector3();
|
||||
material.getWorldPosition(materialWorldPos);
|
||||
|
||||
const armbotWorldPos = new THREE.Vector3();
|
||||
armbotModel.getWorldPosition(armbotWorldPos);
|
||||
|
||||
const materialLocalPos = materialWorldPos.clone();
|
||||
armbotModel.worldToLocal(materialLocalPos);
|
||||
|
||||
if (currentPhase === 'rest-to-start') {
|
||||
end = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
|
||||
} else if (currentPhase === 'start-to-end') {
|
||||
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
|
||||
}
|
||||
}
|
||||
} else if (prevModel && prevModel.type === 'storageUnit') {
|
||||
const position = getMaterialPosition(prevModel.modelUuid, currentMaterial);
|
||||
const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
|
||||
|
||||
if (armbotModel) {
|
||||
const armbotWorldPos = new THREE.Vector3();
|
||||
|
||||
let materialWorldPos = new THREE.Vector3();
|
||||
|
||||
if (position) {
|
||||
materialWorldPos.copy(position);
|
||||
} else {
|
||||
materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos));
|
||||
}
|
||||
|
||||
const materialLocalPos = materialWorldPos.clone();
|
||||
armbotModel.worldToLocal(materialLocalPos);
|
||||
|
||||
if (currentPhase === 'rest-to-start') {
|
||||
end = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
|
||||
} else if (currentPhase === 'start-to-end') {
|
||||
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
|
||||
} else if (currentPhase === 'end-to-rest') {
|
||||
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextModel && nextPoint && nextModel.type === 'transfer') {
|
||||
const conveyorModel = scene.getObjectByProperty("uuid", nextModel.modelUuid);
|
||||
const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
|
||||
if (conveyorModel && armbotModel) {
|
||||
const localPoint = new THREE.Vector3(
|
||||
nextPoint.position[0],
|
||||
nextPoint.position[1],
|
||||
nextPoint.position[2]
|
||||
);
|
||||
|
||||
const worldPoint = conveyorModel.localToWorld(localPoint);
|
||||
|
||||
armbotModel.worldToLocal(worldPoint);
|
||||
|
||||
if (currentPhase === 'start-to-end') {
|
||||
end = [worldPoint.x, worldPoint.y + 0.35, worldPoint.z];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (currentPhase === 'end-to-rest') {
|
||||
const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
|
||||
const armbotWorldPos = new THREE.Vector3();
|
||||
|
||||
if (armbotModel) {
|
||||
let materialWorldPos = new THREE.Vector3();
|
||||
materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos));
|
||||
|
||||
const materialLocalPos = materialWorldPos.clone();
|
||||
armbotModel.worldToLocal(materialLocalPos);
|
||||
start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
|
||||
}
|
||||
}
|
||||
|
||||
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
|
||||
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
|
||||
|
||||
@@ -334,7 +334,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
|
||||
|
||||
}, [currentPhase, armBot, isPlaying, isReset, ikSolver])
|
||||
|
||||
|
||||
function createCurveBetweenTwoPoints(p1: any, p2: any) {
|
||||
const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
|
||||
const points = [p1, mid, p2];
|
||||
@@ -342,7 +341,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
|
||||
}
|
||||
|
||||
const HandleCallback = () => {
|
||||
|
||||
if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") {
|
||||
logStatus(armBot.modelUuid, "Callback triggered: rest");
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
@@ -370,7 +368,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("rest");
|
||||
setPath([])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +386,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) {
|
||||
ikSolver={ikSolver}
|
||||
targetBone={targetBone}
|
||||
armBot={armBot}
|
||||
logStatus={logStatus}
|
||||
path={path}
|
||||
currentPhase={currentPhase}
|
||||
/>
|
||||
|
||||
@@ -22,7 +22,7 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
||||
const trySetup = () => {
|
||||
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
|
||||
|
||||
if (!targetMesh || !targetMesh.userData.iks || targetMesh.userData.iks.length < 1) {
|
||||
if (!targetMesh || !targetMesh.userData.fieldData || targetMesh.userData.fieldData.length < 1) {
|
||||
retryId = setTimeout(trySetup, 100);
|
||||
return;
|
||||
}
|
||||
@@ -34,8 +34,8 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
||||
});
|
||||
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
|
||||
|
||||
const rawIks: IK[] = targetMesh.userData.iks;
|
||||
const iks = rawIks.map((ik) => ({
|
||||
const rawIks: IK[] = targetMesh.userData.fieldData;
|
||||
const fieldData = rawIks.map((ik) => ({
|
||||
target: ik.target,
|
||||
effector: ik.effector,
|
||||
links: ik.links.map((link) => ({
|
||||
@@ -51,10 +51,10 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
||||
minheight: ik.minheight,
|
||||
}));
|
||||
|
||||
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
|
||||
const solver = new CCDIKSolver(OOI.Skinned_Mesh, fieldData);
|
||||
setIkSolver(solver);
|
||||
|
||||
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
|
||||
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, fieldData, 0.05)
|
||||
// scene.add(helper);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import Materials from './materials/materials';
|
||||
import Machine from './machine/machine';
|
||||
import StorageUnit from './storageUnit/storageUnit';
|
||||
import Human from './human/human';
|
||||
import Crane from './crane/crane';
|
||||
import Simulator from './simulator/simulator';
|
||||
import Products from './products/products';
|
||||
import Trigger from './triggers/trigger';
|
||||
@@ -55,6 +56,8 @@ function Simulation() {
|
||||
|
||||
<Human />
|
||||
|
||||
<Crane />
|
||||
|
||||
<Simulator />
|
||||
|
||||
<SimulationAnalysis />
|
||||
|
||||
@@ -125,7 +125,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
event.type === 'human' ||
|
||||
event.type === 'crane'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -17,7 +17,8 @@ export async function determineExecutionMachineSequences(products: productsSchem
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
event.type === 'human' ||
|
||||
event.type === 'crane'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -20,7 +20,8 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
event.type === 'human' ||
|
||||
event.type === 'crane'
|
||||
) {
|
||||
pointMap.set(event.point.uuid, event.point);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -17,7 +17,8 @@ export async function determineExecutionSequences(products: productsSchema): Pro
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
event.type === 'human' ||
|
||||
event.type === 'crane'
|
||||
) {
|
||||
pointMap.set(event.point.uuid, event.point);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -92,7 +92,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
event.type === 'human' ||
|
||||
event.type === 'crane'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -55,6 +55,7 @@ const ArmBotUI = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch and setup selected ArmBot data
|
||||
useEffect(() => {
|
||||
if (selectedEventSphere) {
|
||||
const selectedArmBot = getEventByModelUuid(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid);
|
||||
@@ -85,11 +86,13 @@ const ArmBotUI = () => {
|
||||
const modelData = getEventByModelUuid(selectedProduct.productUuid, modelUuid);
|
||||
|
||||
if (modelData?.type === "roboticArm") {
|
||||
const baseY = modelData.point.position?.[1] || 0;
|
||||
const baseX = modelData.point.position?.[0] || 0;
|
||||
const baseY = modelData.point.position?.[1] || 0;;
|
||||
const baseZ = modelData.point.position?.[2] || 0;
|
||||
return {
|
||||
pick: [0, baseY, 0 + 0.5],
|
||||
drop: [0, baseY, 0 - 0.5],
|
||||
default: [0, baseY, 0],
|
||||
pick: [baseX, baseY, baseZ + 0.5],
|
||||
drop: [baseX, baseY, baseZ - 0.5],
|
||||
default: [baseX, baseY, baseZ],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -170,13 +173,16 @@ const ArmBotUI = () => {
|
||||
|
||||
const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || '');
|
||||
|
||||
const fieldData = targetMesh?.userData?.fieldData;
|
||||
const firstIK = Array.isArray(fieldData) && fieldData.length > 0 ? fieldData[0] : {};
|
||||
|
||||
const { handlePointerDown } = useDraggableGLTF(
|
||||
updatePointToState,
|
||||
{
|
||||
minDistance: targetMesh?.userData?.iks[0]?.minDistance || 1.2,
|
||||
maxDistance: targetMesh?.userData?.iks[0]?.maxDistance || 2,
|
||||
maxheight: targetMesh?.userData?.iks[0]?.maxheight || 0.6,
|
||||
minheight: targetMesh?.userData?.iks[0]?.minheight || 1.9,
|
||||
minDistance: firstIK.minDistance ?? 1.2,
|
||||
maxDistance: firstIK.maxDistance ?? 2,
|
||||
maxheight: firstIK.maxheight ?? 0.6,
|
||||
minheight: firstIK.minheight ?? 1.9,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -222,7 +228,7 @@ const ArmBotUI = () => {
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
return null; // important! must return something
|
||||
}
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -183,7 +183,11 @@ export default function useDraggableGLTF(
|
||||
targetPosition.z = centerZ + finalLocal.z;
|
||||
|
||||
// Clamp Y axis using variables
|
||||
targetPosition.y = Math.min(Math.max(targetPosition.y, minHeight), maxHeight);
|
||||
|
||||
targetPosition.y = Math.min(
|
||||
Math.max(targetPosition.y, Math.min(minHeight, maxHeight)),
|
||||
Math.max(minHeight, maxHeight)
|
||||
);
|
||||
|
||||
// Convert to local if parent exists
|
||||
if (parent) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
import { Tube, useGLTF } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useSelectedEventSphere, useIsDragging, useIsRotating, } from "../../../../store/simulation/useSimulationStore";
|
||||
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
@@ -16,8 +17,6 @@ import { useVersionContext } from "../../../builder/version/versionContext";
|
||||
const VehicleUI = () => {
|
||||
const { scene: startScene } = useGLTF(startPoint) as any;
|
||||
const { scene: endScene } = useGLTF(startEnd) as any;
|
||||
const startMarker = useRef<Group>(null);
|
||||
const endMarker = useRef<Group>(null);
|
||||
const prevMousePos = useRef({ x: 0, y: 0 });
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
@@ -162,7 +161,7 @@ const VehicleUI = () => {
|
||||
let globalStartPosition = null;
|
||||
let globalEndPosition = null;
|
||||
|
||||
if (outerGroup.current && startMarker.current && endMarker.current) {
|
||||
if (outerGroup.current) {
|
||||
const worldPosStart = new Vector3(...startPosition);
|
||||
globalStartPosition = outerGroup.current.localToWorld(
|
||||
worldPosStart.clone()
|
||||
@@ -277,24 +276,19 @@ const VehicleUI = () => {
|
||||
const currentPointerX = state.pointer.x;
|
||||
const deltaX = currentPointerX - prevMousePos.current.x;
|
||||
prevMousePos.current.x = currentPointerX;
|
||||
const marker =
|
||||
isRotating === "start" ? startMarker.current : endMarker.current;
|
||||
if (marker) {
|
||||
const rotationSpeed = 10;
|
||||
marker.rotation.y += deltaX * rotationSpeed;
|
||||
if (isRotating === "start") {
|
||||
setStartRotation([
|
||||
marker.rotation.x,
|
||||
marker.rotation.y,
|
||||
marker.rotation.z,
|
||||
]);
|
||||
} else {
|
||||
setEndRotation([
|
||||
marker.rotation.x,
|
||||
marker.rotation.y,
|
||||
marker.rotation.z,
|
||||
]);
|
||||
}
|
||||
const rotationSpeed = 10;
|
||||
if (isRotating === "start") {
|
||||
setStartRotation([
|
||||
startRotation[0],
|
||||
startRotation[1] + deltaX * rotationSpeed,
|
||||
startRotation[2],
|
||||
]);
|
||||
} else {
|
||||
setEndRotation([
|
||||
endRotation[0],
|
||||
endRotation[1] + deltaX * rotationSpeed,
|
||||
endRotation[2],
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -342,41 +336,130 @@ const VehicleUI = () => {
|
||||
</group>
|
||||
|
||||
{/* Start Marker */}
|
||||
<primitive
|
||||
<VehicleMarkerPrimitive
|
||||
name="startMarker"
|
||||
object={startScene}
|
||||
ref={startMarker}
|
||||
position={startPosition}
|
||||
rotation={startRotation}
|
||||
onPointerDown={(e: any) => {
|
||||
e.stopPropagation();
|
||||
handlePointerDown(e, "start", "start");
|
||||
}}
|
||||
outerGroupRef={outerGroup}
|
||||
color="green"
|
||||
handlePointerDown={handlePointerDown}
|
||||
setIsDragging={setIsDragging}
|
||||
setIsRotating={setIsRotating}
|
||||
/>
|
||||
|
||||
{/* End Marker */}
|
||||
<VehicleMarkerPrimitive
|
||||
name="endMarker"
|
||||
object={endScene}
|
||||
position={endPosition}
|
||||
rotation={endRotation}
|
||||
outerGroupRef={outerGroup}
|
||||
color="orange"
|
||||
handlePointerDown={handlePointerDown}
|
||||
setIsDragging={setIsDragging}
|
||||
setIsRotating={setIsRotating}
|
||||
/>
|
||||
</group>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default VehicleUI;
|
||||
|
||||
export const VehicleMarkerPrimitive = ({
|
||||
name,
|
||||
object,
|
||||
position,
|
||||
rotation,
|
||||
outerGroupRef,
|
||||
color,
|
||||
handlePointerDown,
|
||||
setIsDragging,
|
||||
setIsRotating,
|
||||
}: {
|
||||
name: string;
|
||||
object: THREE.Object3D;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
outerGroupRef: React.RefObject<THREE.Group>;
|
||||
color: string;
|
||||
handlePointerDown: (e: any, type: "start" | "end", rotation: "start" | "end") => void;
|
||||
setIsDragging: (val: any) => void;
|
||||
setIsRotating: (val: any) => void;
|
||||
}) => {
|
||||
const { scene, camera } = useThree();
|
||||
const markerRef = useRef<THREE.Group>(null);
|
||||
const [hitPoint, setHitPoint] = useState<THREE.Vector3 | null>(null);
|
||||
const [curve, setCurve] = useState<THREE.CatmullRomCurve3 | null>(null);
|
||||
|
||||
useFrame(() => {
|
||||
if (!markerRef.current || !outerGroupRef.current) return;
|
||||
|
||||
const worldPos = new THREE.Vector3();
|
||||
markerRef.current.getWorldPosition(worldPos);
|
||||
const localMarkerPos = outerGroupRef.current.worldToLocal(worldPos.clone());
|
||||
|
||||
const rayOrigin = worldPos.clone();
|
||||
const direction = new THREE.Vector3(0, -1, 0);
|
||||
const raycaster = new THREE.Raycaster(rayOrigin, direction, 0.1, 1000);
|
||||
raycaster.camera = camera;
|
||||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||||
|
||||
const hit = intersects.find(i => i.object.name !== name);
|
||||
if (hit) {
|
||||
const localHit = outerGroupRef.current.worldToLocal(hit.point.clone());
|
||||
setHitPoint(localHit);
|
||||
|
||||
const newCurve = new THREE.CatmullRomCurve3([
|
||||
localMarkerPos.clone(),
|
||||
localHit.clone(),
|
||||
]);
|
||||
setCurve(newCurve);
|
||||
} else {
|
||||
setHitPoint(null);
|
||||
setCurve(null);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<primitive
|
||||
name={name}
|
||||
ref={markerRef}
|
||||
object={object}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
onPointerDown={(e: any) =>
|
||||
handlePointerDown(
|
||||
e,
|
||||
name === "startMarker" ? "start" : "end",
|
||||
name === "startMarker" ? "start" : "end"
|
||||
)
|
||||
}
|
||||
onPointerMissed={() => {
|
||||
controls.enabled = true;
|
||||
setIsDragging(null);
|
||||
setIsRotating(null);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* End Marker */}
|
||||
<primitive
|
||||
name="endMarker"
|
||||
object={endScene}
|
||||
ref={endMarker}
|
||||
position={endPosition}
|
||||
rotation={endRotation}
|
||||
onPointerDown={(e: any) => {
|
||||
e.stopPropagation();
|
||||
handlePointerDown(e, "end", "end");
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
controls.enabled = true;
|
||||
setIsDragging(null);
|
||||
setIsRotating(null);
|
||||
}}
|
||||
/>
|
||||
</group>
|
||||
) : null;
|
||||
};
|
||||
export default VehicleUI;
|
||||
{hitPoint && (
|
||||
<>
|
||||
<mesh
|
||||
name={`${name}-torus`}
|
||||
position={hitPoint}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<torusGeometry args={[0.15, 0.02, 3, 32]} />
|
||||
<meshBasicMaterial color={color} depthWrite={false} />
|
||||
</mesh>
|
||||
|
||||
{curve && (
|
||||
<Tube args={[curve, 20, 0.01, 8, true]}>
|
||||
<meshBasicMaterial color={color} depthWrite={false} />
|
||||
</Tube>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,72 +1,84 @@
|
||||
import { useRef, useMemo } from "react";
|
||||
import { MaterialModel } from "../../../materials/instances/material/materialModel";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Object3D, Box3, Vector3 } from "three";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { useLoadingProgress } from "../../../../../store/builder/store";
|
||||
import { MaterialModel } from "../../../materials/instances/material/materialModel";
|
||||
import { useSceneContext } from "../../../../scene/sceneContext";
|
||||
|
||||
const MaterialAnimator = ({
|
||||
storage,
|
||||
storage,
|
||||
}: Readonly<{ storage: StorageUnitStatus }>) => {
|
||||
const meshRef = useRef<any>(null!);
|
||||
const { scene } = useThree();
|
||||
const padding = 0.1;
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { scene } = useThree();
|
||||
const padding = 0.1;
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { materialStore } = useSceneContext();
|
||||
const { materialPositions, setMaterialPositions } = materialStore();
|
||||
|
||||
const storageModel = useMemo(() => {
|
||||
return scene.getObjectByProperty("uuid", storage.modelUuid) as Object3D;
|
||||
}, [scene, storage.modelUuid, loadingProgress]);
|
||||
const storageModel = useMemo(() => {
|
||||
return scene.getObjectByProperty("uuid", storage.modelUuid) as Object3D;
|
||||
}, [scene, storage.modelUuid, loadingProgress]);
|
||||
|
||||
const materialPositions = useMemo(() => {
|
||||
if (!storageModel || storage.currentMaterials.length === 0) return [];
|
||||
useEffect(() => {
|
||||
if (!storageModel || storage.currentMaterials.length === 0) {
|
||||
setMaterialPositions(storage.modelUuid, []);
|
||||
return;
|
||||
}
|
||||
|
||||
const box = new Box3().setFromObject(storageModel);
|
||||
const size = new Vector3();
|
||||
box.getSize(size);
|
||||
const box = new Box3().setFromObject(storageModel);
|
||||
const size = new Vector3();
|
||||
box.getSize(size);
|
||||
|
||||
const matCount = storage.currentMaterials.length;
|
||||
const materialWidth = 0.45;
|
||||
const materialDepth = 0.45;
|
||||
const materialHeight = 0.3;
|
||||
const cols = Math.floor(size.x / materialWidth);
|
||||
const rows = Math.floor(size.z / materialDepth);
|
||||
const itemsPerLayer = cols * rows;
|
||||
|
||||
// Assumed size each material needs in world units
|
||||
const materialWidth = 0.45;
|
||||
const materialDepth = 0.45;
|
||||
const materialHeight = 0.3;
|
||||
const origin = new Vector3(
|
||||
box.min.x + materialWidth / 2,
|
||||
box.max.y + padding,
|
||||
box.min.z + materialDepth / 2
|
||||
);
|
||||
|
||||
const cols = Math.floor(size.x / materialWidth);
|
||||
const rows = Math.floor(size.z / materialDepth);
|
||||
const itemsPerLayer = cols * rows;
|
||||
const newMaterials = storage.currentMaterials.map((mat, i) => {
|
||||
const layer = Math.floor(i / itemsPerLayer);
|
||||
const layerIndex = i % itemsPerLayer;
|
||||
const row = Math.floor(layerIndex / cols);
|
||||
const col = layerIndex % cols;
|
||||
|
||||
const origin = new Vector3(
|
||||
box.min.x + materialWidth / 2,
|
||||
box.max.y + padding, // slightly above the surface
|
||||
box.min.z + materialDepth / 2
|
||||
const position = new Vector3(
|
||||
origin.x + col * materialWidth,
|
||||
origin.y + layer * (materialHeight + padding),
|
||||
origin.z + row * materialDepth
|
||||
);
|
||||
|
||||
return {
|
||||
materialId: mat.materialId,
|
||||
position,
|
||||
};
|
||||
});
|
||||
|
||||
setMaterialPositions(storage.modelUuid, newMaterials);
|
||||
}, [storageModel, storage.currentMaterials]);
|
||||
|
||||
return (
|
||||
<group position={[0, -padding, 0]}>
|
||||
{(materialPositions[storage.modelUuid] || []).map(({ materialId, position }) => {
|
||||
const mat = storage.currentMaterials.find((m) => m.materialId === materialId);
|
||||
return (
|
||||
<MaterialModel
|
||||
key={materialId}
|
||||
materialId={materialId}
|
||||
matRef={null}
|
||||
materialType={mat?.materialType ?? "Default material"}
|
||||
position={position}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
|
||||
return Array.from({ length: matCount }, (_, i) => {
|
||||
const layer = Math.floor(i / itemsPerLayer);
|
||||
const layerIndex = i % itemsPerLayer;
|
||||
const row = Math.floor(layerIndex / cols);
|
||||
const col = layerIndex % cols;
|
||||
|
||||
return new Vector3(
|
||||
origin.x + col * materialWidth,
|
||||
origin.y + layer * (materialHeight + padding),
|
||||
origin.z + row * materialDepth
|
||||
);
|
||||
});
|
||||
}, [storageModel, storage.currentMaterials]);
|
||||
|
||||
return (
|
||||
<group {...{ position: [0, -padding, 0] }}>
|
||||
{storage.currentMaterials.map((mat, index) => (
|
||||
<MaterialModel
|
||||
key={`${index}-${mat.materialId}`}
|
||||
materialId={mat.materialId}
|
||||
matRef={meshRef}
|
||||
materialType={mat.materialType ?? "Default material"}
|
||||
position={materialPositions[index]}
|
||||
/>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
export default MaterialAnimator;
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useViewSceneStore } from "../../../../store/builder/store";
|
||||
function StorageUnitInstances() {
|
||||
const { storageUnitStore } = useSceneContext();
|
||||
const { storageUnits } = storageUnitStore();
|
||||
// console.log('storageUnits: ', storageUnits);
|
||||
const { viewSceneLabels } = useViewSceneStore();
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import StorageUnitInstances from './instances/storageUnitInstances'
|
||||
|
||||
function StorageUnit() {
|
||||
|
||||
@@ -7,9 +7,10 @@ import { useMachineEventManager } from '../../machine/eventManager/useMachineEve
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../products/productContext';
|
||||
import { useHumanEventManager } from '../../human/eventManager/useHumanEventManager';
|
||||
import { useCraneEventManager } from '../../crane/eventManager/useCraneEventManager';
|
||||
|
||||
export function useTriggerHandler() {
|
||||
const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
|
||||
const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, humanStore, craneStore, storageUnitStore, productStore } = useSceneContext();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { handleAction } = useActionHandler();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
@@ -21,8 +22,10 @@ export function useTriggerHandler() {
|
||||
const { addVehicleToMonitor } = useVehicleEventManager();
|
||||
const { addMachineToMonitor } = useMachineEventManager();
|
||||
const { addHumanToMonitor } = useHumanEventManager();
|
||||
const { addCraneToMonitor } = useCraneEventManager();
|
||||
const { getVehicleById } = vehicleStore();
|
||||
const { getHumanById, setHumanScheduled } = humanStore();
|
||||
const { getCraneById, setCraneScheduled, addCurrentAction } = craneStore();
|
||||
const { getMachineById, setMachineActive } = machineStore();
|
||||
const { getStorageUnitById } = storageUnitStore();
|
||||
const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore();
|
||||
@@ -370,40 +373,78 @@ export function useTriggerHandler() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (human.isActive === false && human.state === 'idle') {
|
||||
|
||||
// Handle current action from arm bot
|
||||
setIsPaused(materialId, true);
|
||||
handleAction(action, materialId);
|
||||
|
||||
} else {
|
||||
|
||||
// Handle current action using Event Manager
|
||||
setHumanScheduled(human.modelUuid, true);
|
||||
setIsPaused(materialId, true);
|
||||
addHumanToMonitor(human.modelUuid, () => {
|
||||
handleAction(action, materialId)
|
||||
}, action.actionUuid);
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (human.isActive === false && human.state === 'idle') {
|
||||
|
||||
// Handle current action from arm bot
|
||||
setIsPaused(materialId, true);
|
||||
setHumanScheduled(human.modelUuid, true);
|
||||
handleAction(action, materialId);
|
||||
|
||||
} else {
|
||||
|
||||
// Handle current action using Event Manager
|
||||
setIsPaused(materialId, true);
|
||||
setHumanScheduled(human.modelUuid, true);
|
||||
addHumanToMonitor(human.modelUuid, () => {
|
||||
handleAction(action, materialId)
|
||||
}, action.actionUuid);
|
||||
}
|
||||
} else {
|
||||
setHumanScheduled(human.modelUuid, true);
|
||||
setIsPaused(materialId, true);
|
||||
addHumanToMonitor(human.modelUuid, () => {
|
||||
handleAction(action, materialId)
|
||||
}, action.actionUuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (toEvent?.type === 'crane') {
|
||||
// Transfer to Human
|
||||
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
|
||||
const material = getMaterialById(materialId);
|
||||
if (material) {
|
||||
|
||||
// Handle current action of the material
|
||||
handleAction(action, materialId);
|
||||
|
||||
if (material.next) {
|
||||
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||
const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.next.modelUuid,
|
||||
pointUuid: material.next.pointUuid,
|
||||
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, null);
|
||||
|
||||
if (action) {
|
||||
|
||||
if (crane) {
|
||||
if (action && action.triggers.length > 0 &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) {
|
||||
const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
||||
if (model?.type === 'human') {
|
||||
const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||
if (crane) {
|
||||
setIsPaused(materialId, true);
|
||||
setCraneScheduled(crane.modelUuid, true);
|
||||
const human = getHumanById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
|
||||
if (human) {
|
||||
addHumanToMonitor(human.modelUuid, () => {
|
||||
addCurrentAction(crane.modelUuid, action.actionUuid, null, null);
|
||||
addCraneToMonitor(crane.modelUuid, () => {
|
||||
setIsPaused(materialId, true);
|
||||
|
||||
handleAction(action, materialId);
|
||||
}, action.actionUuid)
|
||||
}, action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -580,6 +621,66 @@ export function useTriggerHandler() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (toEvent?.type === 'crane') {
|
||||
// Vehicle to Human
|
||||
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
|
||||
const material = getMaterialById(materialId);
|
||||
if (material) {
|
||||
|
||||
// Handle current action of the material
|
||||
handleAction(action, materialId);
|
||||
|
||||
if (material.next) {
|
||||
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||
const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.next.modelUuid,
|
||||
pointUuid: material.next.pointUuid,
|
||||
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, null);
|
||||
|
||||
if (action) {
|
||||
|
||||
if (crane) {
|
||||
if (action && action.triggers.length > 0 &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) {
|
||||
const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
||||
if (model?.type === 'human') {
|
||||
const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||
if (crane) {
|
||||
setIsPaused(materialId, true);
|
||||
setCraneScheduled(crane.modelUuid, true);
|
||||
const human = getHumanById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
|
||||
if (human) {
|
||||
addHumanToMonitor(human.modelUuid, () => {
|
||||
addCurrentAction(crane.modelUuid, action.actionUuid, null, null);
|
||||
addCraneToMonitor(crane.modelUuid, () => {
|
||||
setIsPaused(materialId, true);
|
||||
|
||||
handleAction(action, materialId);
|
||||
}, action.actionUuid)
|
||||
}, action.triggers[0]?.triggeredAsset.triggeredAction.actionUuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (fromEvent?.type === 'machine') {
|
||||
if (toEvent?.type === 'transfer') {
|
||||
@@ -1260,12 +1361,6 @@ export function useTriggerHandler() {
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
});
|
||||
|
||||
setIsPaused(material.materialId, true);
|
||||
setIsVisible(material.materialId, true);
|
||||
|
||||
@@ -1279,6 +1374,13 @@ export function useTriggerHandler() {
|
||||
|
||||
if (model?.type === 'roboticArm') {
|
||||
addArmBotToMonitor(model.modelUuid, () => {
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
@@ -1288,6 +1390,13 @@ export function useTriggerHandler() {
|
||||
})
|
||||
} else if (model?.type === 'vehicle') {
|
||||
addVehicleToMonitor(model.modelUuid, () => {
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
@@ -1296,6 +1405,13 @@ export function useTriggerHandler() {
|
||||
setIsPaused(material.materialId, false);
|
||||
})
|
||||
} else if (model?.type === 'transfer') {
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
@@ -1303,15 +1419,48 @@ export function useTriggerHandler() {
|
||||
|
||||
setIsPaused(material.materialId, false);
|
||||
} else if (model?.type === 'human') {
|
||||
addHumanToMonitor(model.modelUuid, () => {
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
})
|
||||
|
||||
if (fromEvent.modelUuid === model.modelUuid) {
|
||||
setIsPaused(material.materialId, false);
|
||||
}, action.actionUuid)
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setIsPaused(material.materialId, false);
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
})
|
||||
}, 0)
|
||||
|
||||
} else {
|
||||
addHumanToMonitor(model.modelUuid, () => {
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
})
|
||||
|
||||
setIsPaused(material.materialId, false);
|
||||
}, action.actionUuid)
|
||||
}
|
||||
} else {
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
@@ -1320,6 +1469,134 @@ export function useTriggerHandler() {
|
||||
setIsPaused(material.materialId, false);
|
||||
}
|
||||
}
|
||||
} else if (material && action.actionType === 'operator') {
|
||||
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid,
|
||||
pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||
});
|
||||
|
||||
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||
|
||||
if (action && action.triggers.length > 0 &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid &&
|
||||
action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) {
|
||||
const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
||||
if (model?.type === 'roboticArm') {
|
||||
|
||||
handleAction(action, material.materialId);
|
||||
|
||||
} else if (model?.type === 'vehicle') {
|
||||
const nextAction = getActionByUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredAction.actionUuid);
|
||||
|
||||
if (action) {
|
||||
handleAction(action, material.materialId);
|
||||
|
||||
const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid,
|
||||
pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid,
|
||||
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, null);
|
||||
|
||||
if (nextAction) {
|
||||
|
||||
if (vehicle) {
|
||||
|
||||
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
|
||||
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '',
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, null);
|
||||
|
||||
setIsVisible(materialId, false);
|
||||
setIsPaused(materialId, false);
|
||||
|
||||
// Handle current action from vehicle
|
||||
handleAction(nextAction, materialId);
|
||||
|
||||
} else {
|
||||
|
||||
// Handle current action using Event Manager
|
||||
setIsPaused(materialId, true);
|
||||
|
||||
addVehicleToMonitor(vehicle.modelUuid, () => {
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '',
|
||||
pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '',
|
||||
actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '',
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, null);
|
||||
|
||||
setIsPaused(materialId, false);
|
||||
setIsVisible(materialId, false);
|
||||
handleAction(nextAction, materialId);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (model?.type === 'transfer') {
|
||||
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
|
||||
if (conveyor) {
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid,
|
||||
pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid,
|
||||
})
|
||||
setIsPaused(material.materialId, false);
|
||||
setIsVisible(material.materialId, true);
|
||||
handleAction(action, material.materialId);
|
||||
}
|
||||
} else {
|
||||
setNextLocation(material.materialId, {
|
||||
modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid,
|
||||
pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid,
|
||||
})
|
||||
|
||||
handleAction(action, material.materialId);
|
||||
}
|
||||
} else if (action) {
|
||||
setNextLocation(material.materialId, null)
|
||||
|
||||
handleAction(action, material.materialId);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1521,6 +1798,29 @@ export function useTriggerHandler() {
|
||||
} else if (toEvent?.type === 'human') {
|
||||
// Human to Human
|
||||
|
||||
}
|
||||
} else if (fromEvent?.type === 'crane') {
|
||||
if (toEvent?.type === 'transfer') {
|
||||
// Crane Unit to Transfer
|
||||
|
||||
} else if (toEvent?.type === 'vehicle') {
|
||||
// Crane Unit to Vehicle
|
||||
|
||||
} else if (toEvent?.type === 'machine') {
|
||||
// Crane Unit to Machine
|
||||
|
||||
} else if (toEvent?.type === 'roboticArm') {
|
||||
// Crane Unit to Robotic Arm
|
||||
|
||||
} else if (toEvent?.type === 'storageUnit') {
|
||||
// Crane Unit to Storage Unit
|
||||
|
||||
} else if (toEvent?.type === 'human') {
|
||||
// Crane Unit to Human
|
||||
|
||||
} else if (toEvent?.type === 'crane') {
|
||||
// Crane Unit to Human
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,30 +13,44 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => {
|
||||
useEffect(() => {
|
||||
const loadState = agvDetail.currentLoad > 0;
|
||||
setHasLoad(loadState);
|
||||
|
||||
|
||||
if (!loadState) {
|
||||
setIsAttached(false);
|
||||
if (meshRef.current?.parent) {
|
||||
meshRef.current.parent.remove(meshRef.current);
|
||||
const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D;
|
||||
if (agvModel) {
|
||||
const material = agvModel.getObjectByName('Sample-Material');
|
||||
if (material) {
|
||||
agvModel.remove(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [agvDetail.currentLoad]);
|
||||
|
||||
useFrame(() => {
|
||||
// if (agvDetail.currentMaterials.length === 0 || agvDetail.currentLoad === 0) {
|
||||
// const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D;
|
||||
// if (agvModel) {
|
||||
// const material = agvModel.getObjectByName('Sample-Material');
|
||||
// if (material) {
|
||||
// agvModel.remove(material);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if (!hasLoad || !meshRef.current || isAttached) return;
|
||||
|
||||
const agvModel = scene.getObjectByProperty("uuid", agvDetail.modelUuid) as THREE.Object3D;
|
||||
if (agvModel && !isAttached) {
|
||||
if (meshRef.current.parent) {
|
||||
meshRef.current.parent.remove(meshRef.current);
|
||||
const material = agvModel.getObjectByName('Sample-Material');
|
||||
if (material) {
|
||||
agvModel.remove(material);
|
||||
}
|
||||
|
||||
|
||||
agvModel.add(meshRef.current);
|
||||
|
||||
|
||||
meshRef.current.position.copy(offset);
|
||||
meshRef.current.rotation.set(0, 0, 0);
|
||||
meshRef.current.scale.set(1, 1, 1);
|
||||
|
||||
|
||||
setIsAttached(true);
|
||||
}
|
||||
});
|
||||
@@ -45,6 +59,7 @@ const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => {
|
||||
<>
|
||||
{hasLoad && agvDetail.currentMaterials.length > 0 && (
|
||||
<MaterialModel
|
||||
name={'Sample-Material'}
|
||||
matRef={meshRef}
|
||||
materialId={agvDetail.currentMaterials[0].materialId || ''}
|
||||
materialType={agvDetail.currentMaterials[0].materialType || 'Default material'}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useFrame, useThree, ThreeEvent } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { Line } from '@react-three/drei';
|
||||
import { RapierRigidBody } from '@react-three/rapier';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useActiveTool, useSelectedPath } from '../../../../../store/builder/store';
|
||||
import { RapierRigidBody } from '@react-three/rapier';
|
||||
|
||||
interface VehicleAnimatorProps {
|
||||
path: [number, number, number][];
|
||||
@@ -236,7 +236,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
||||
return (
|
||||
<>
|
||||
{selectedPath === "auto" &&
|
||||
<group>
|
||||
<group visible={false}>
|
||||
{currentPath.map((pos, i) => {
|
||||
if (i < currentPath.length - 1) {
|
||||
return (
|
||||
|
||||
@@ -12,24 +12,26 @@ import MaterialAnimator from '../animator/materialAnimator';
|
||||
import VehicleAnimator from '../animator/vehicleAnimator';
|
||||
|
||||
import { useHumanEventManager } from '../../../human/eventManager/useHumanEventManager';
|
||||
import { useCraneEventManager } from '../../../crane/eventManager/useCraneEventManager';
|
||||
|
||||
function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) {
|
||||
const { navMesh } = useNavMesh();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore, assetStore } = useSceneContext();
|
||||
const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, productStore, assetStore } = useSceneContext();
|
||||
const { removeMaterial, setEndTime, setIsVisible } = materialStore();
|
||||
const { getStorageUnitById } = storageUnitStore();
|
||||
const { getHumanById, addCurrentAction, addCurrentMaterial, incrementHumanLoad } = humanStore();
|
||||
const { getCraneById, addCurrentAction: addCraneAction, addCurrentMaterial: addCraneMaterial, incrementCraneLoad } = craneStore();
|
||||
const { getArmBotById } = armBotStore();
|
||||
const { getConveyorById } = conveyorStore();
|
||||
const { triggerPointActions } = useTriggerHandler();
|
||||
const { setCurrentAnimation, getAssetById } = assetStore();
|
||||
const { addHumanToMonitor } = useHumanEventManager();
|
||||
const { addCraneToMonitor } = useCraneEventManager();
|
||||
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { vehicles, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = vehicleStore();
|
||||
const [currentPhase, setCurrentPhase] = useState<string>('stationed');
|
||||
const { vehicles, setCurrentPhase, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = vehicleStore();
|
||||
const [path, setPath] = useState<[number, number, number][]>([]);
|
||||
const pauseTimeRef = useRef<number | null>(null);
|
||||
const idleTimeRef = useRef<number>(0);
|
||||
@@ -80,7 +82,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
|
||||
// Function to reset everything
|
||||
function reset() {
|
||||
setCurrentPhase('stationed');
|
||||
setCurrentPhase(agvDetail.modelUuid, 'stationed');
|
||||
setVehicleActive(agvDetail.modelUuid, false);
|
||||
setVehiclePicking(agvDetail.modelUuid, false);
|
||||
setVehicleState(agvDetail.modelUuid, 'idle');
|
||||
@@ -103,20 +105,20 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
if (isPlaying && selectedPath === "auto") {
|
||||
if (!agvDetail.point.action.unLoadPoint || !agvDetail.point.action.pickUpPoint) return;
|
||||
|
||||
if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') {
|
||||
if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'stationed') {
|
||||
const toPickupPath = computePath(
|
||||
new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]),
|
||||
agvDetail?.point?.action?.pickUpPoint?.position
|
||||
);
|
||||
|
||||
setPath(toPickupPath);
|
||||
setCurrentPhase('stationed-pickup');
|
||||
setCurrentPhase(agvDetail.modelUuid, 'stationed-pickup');
|
||||
setVehicleState(agvDetail.modelUuid, 'running');
|
||||
setVehiclePicking(agvDetail.modelUuid, false);
|
||||
setVehicleActive(agvDetail.modelUuid, true);
|
||||
vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup');
|
||||
return;
|
||||
} else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'picking') {
|
||||
} else if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'picking') {
|
||||
if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity && agvDetail.currentMaterials.length > 0) {
|
||||
if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) {
|
||||
const toDrop = computePath(
|
||||
@@ -124,21 +126,21 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
agvDetail.point.action.unLoadPoint.position
|
||||
);
|
||||
setPath(toDrop);
|
||||
setCurrentPhase('pickup-drop');
|
||||
setCurrentPhase(agvDetail.modelUuid, 'pickup-drop');
|
||||
setVehicleState(agvDetail.modelUuid, 'running');
|
||||
setVehiclePicking(agvDetail.modelUuid, false);
|
||||
setVehicleActive(agvDetail.modelUuid, true);
|
||||
vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point');
|
||||
}
|
||||
}
|
||||
} else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'dropping' && agvDetail.currentLoad === 0) {
|
||||
} else if (!agvDetail.isActive && agvDetail.state === 'idle' && agvDetail.currentPhase === 'dropping' && agvDetail.currentLoad === 0) {
|
||||
if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) {
|
||||
const dropToPickup = computePath(
|
||||
agvDetail.point.action.unLoadPoint.position,
|
||||
agvDetail.point.action.pickUpPoint.position
|
||||
);
|
||||
setPath(dropToPickup);
|
||||
setCurrentPhase('drop-pickup');
|
||||
setCurrentPhase(agvDetail.modelUuid, 'drop-pickup');
|
||||
setVehicleState(agvDetail.modelUuid, 'running');
|
||||
setVehiclePicking(agvDetail.modelUuid, false);
|
||||
setVehicleActive(agvDetail.modelUuid, true);
|
||||
@@ -149,7 +151,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
else {
|
||||
reset()
|
||||
}
|
||||
}, [vehicles, currentPhase, path, isPlaying, selectedPath]);
|
||||
}, [vehicles, agvDetail.currentPhase, path, isPlaying, selectedPath]);
|
||||
|
||||
function animate(currentTime: number) {
|
||||
if (previousTimeRef.current === null) {
|
||||
@@ -198,22 +200,22 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
}, [agvDetail, isPlaying]);
|
||||
|
||||
function handleCallBack() {
|
||||
if (currentPhase === 'stationed-pickup') {
|
||||
setCurrentPhase('picking');
|
||||
if (agvDetail.currentPhase === 'stationed-pickup') {
|
||||
setCurrentPhase(agvDetail.modelUuid, 'picking');
|
||||
setVehicleState(agvDetail.modelUuid, 'idle');
|
||||
setVehiclePicking(agvDetail.modelUuid, true);
|
||||
setVehicleActive(agvDetail.modelUuid, false);
|
||||
vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material');
|
||||
setPath([]);
|
||||
} else if (currentPhase === 'pickup-drop') {
|
||||
setCurrentPhase('dropping');
|
||||
} else if (agvDetail.currentPhase === 'pickup-drop') {
|
||||
setCurrentPhase(agvDetail.modelUuid, 'dropping');
|
||||
setVehicleState(agvDetail.modelUuid, 'idle');
|
||||
setVehiclePicking(agvDetail.modelUuid, false);
|
||||
setVehicleActive(agvDetail.modelUuid, false);
|
||||
vehicleStatus(agvDetail.modelUuid, 'Reached drop point');
|
||||
setPath([]);
|
||||
} else if (currentPhase === 'drop-pickup') {
|
||||
setCurrentPhase('picking');
|
||||
} else if (agvDetail.currentPhase === 'drop-pickup') {
|
||||
setCurrentPhase(agvDetail.modelUuid, 'picking');
|
||||
setVehicleState(agvDetail.modelUuid, 'idle');
|
||||
setVehiclePicking(agvDetail.modelUuid, true);
|
||||
setVehicleActive(agvDetail.modelUuid, false);
|
||||
@@ -252,6 +254,12 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
if (action && (triggeredAction?.actionType === 'assembly' || triggeredAction?.actionType === 'worker')) {
|
||||
handleMaterialDropToHuman(model, triggeredAction);
|
||||
}
|
||||
} else if (model.type === 'crane') {
|
||||
const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid);
|
||||
if (action && (triggeredAction?.actionType === 'pickAndDrop')) {
|
||||
handleMaterialDropToCrane(model, triggeredAction);
|
||||
addCraneAction(model.modelUuid, triggeredAction.actionUuid, null, null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const droppedMaterial = agvDetail.currentLoad;
|
||||
@@ -265,6 +273,64 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
}
|
||||
}
|
||||
|
||||
function handleMaterialDropToCrane(model: CraneEventSchema, action: CraneAction) {
|
||||
|
||||
if (model) {
|
||||
if (action.actionType === 'pickAndDrop') {
|
||||
addCraneToMonitor(model.modelUuid, () => {
|
||||
loopMaterialDropToCrane(
|
||||
agvDetail,
|
||||
model.modelUuid,
|
||||
action.actionUuid
|
||||
);
|
||||
}, action.actionUuid || '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loopMaterialDropToCrane(
|
||||
vehicle: VehicleStatus,
|
||||
craneId: string,
|
||||
craneActionId: string
|
||||
) {
|
||||
let currentVehicleLoad = vehicle.currentLoad;
|
||||
|
||||
const unloadLoop = () => {
|
||||
const crane = getCraneById(craneId);
|
||||
const craneaction = crane?.point.actions.find((action) => action.actionUuid === craneActionId);
|
||||
|
||||
if (!crane || crane.currentAction?.actionUuid !== craneaction?.actionUuid) return;
|
||||
if (crane.isCarrying) {
|
||||
decrementVehicleLoad(vehicle.modelUuid, 1);
|
||||
currentVehicleLoad -= 1;
|
||||
const material = removeLastMaterial(vehicle.modelUuid);
|
||||
if (material) {
|
||||
setIsVisible(material.materialId, false);
|
||||
}
|
||||
return;
|
||||
} else if (!crane.isCarrying && !crane.isActive && crane.currentLoad < (craneaction?.maxPickUpCount || 0) && craneaction?.actionType === 'pickAndDrop') {
|
||||
const material = getLastMaterial(vehicle.modelUuid);
|
||||
if (material) {
|
||||
incrementCraneLoad(craneId, 1);
|
||||
addCraneAction(craneId, craneActionId, material.materialType, material.materialId);
|
||||
addCraneMaterial(craneId, material.materialType, material.materialId);
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(unloadLoop);
|
||||
}, 500)
|
||||
};
|
||||
|
||||
const crane = getCraneById(craneId);
|
||||
const craneaction = crane?.point.actions.find((action) => action.actionUuid === craneActionId);
|
||||
if (crane && crane.currentLoad < (craneaction?.maxPickUpCount || 0)) {
|
||||
setTimeout(() => {
|
||||
unloadLoop();
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleMaterialDropToHuman(model: HumanEventSchema, action: HumanAction) {
|
||||
|
||||
if (model) {
|
||||
@@ -576,7 +642,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
<VehicleAnimator
|
||||
path={path}
|
||||
handleCallBack={handleCallBack}
|
||||
currentPhase={currentPhase}
|
||||
currentPhase={agvDetail.currentPhase}
|
||||
agvUuid={agvDetail?.modelUuid}
|
||||
agvDetail={agvDetail}
|
||||
reset={reset}
|
||||
|
||||
@@ -34,7 +34,7 @@ const Project: React.FC = () => {
|
||||
const { setUserName } = useUserName();
|
||||
const { setOrganization } = useOrganization();
|
||||
const { projectId } = useParams();
|
||||
const { setProjectName } = useProjectName();
|
||||
const { projectName, setProjectName } = useProjectName();
|
||||
const { userId, email, organization, userName } = getUserData();
|
||||
const { selectedUser } = useSelectedUserStore();
|
||||
const { isLogListVisible } = useLogger();
|
||||
@@ -57,7 +57,6 @@ const Project: React.FC = () => {
|
||||
const matchedProject = allProjects.find(
|
||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||
);
|
||||
|
||||
if (matchedProject) {
|
||||
setProjectName(matchedProject.projectName);
|
||||
await viewProject(organization, matchedProject._id, userId);
|
||||
|
||||
@@ -202,10 +202,10 @@ const UserAuth: React.FC = () => {
|
||||
</div>
|
||||
{!isSignIn && (
|
||||
<div className="policy-checkbox">
|
||||
<input type="checkbox" name="" id="" required />
|
||||
<div className="label">
|
||||
<input type="checkbox" id="tos" required />
|
||||
<label htmlFor="tos" className="label">
|
||||
I have read and agree to the terms of service
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<button id="form-submit" type="submit" className="continue-button">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
|
||||
export const duplicateProject = async (
|
||||
refProjectID: string,
|
||||
projectUuid: string,
|
||||
thumbnail: string,
|
||||
projectName: string
|
||||
@@ -16,27 +17,32 @@ export const duplicateProject = async (
|
||||
token: localStorage.getItem("token") || "", // Coerce null to empty string
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
body: JSON.stringify({ projectUuid, thumbnail, projectName }),
|
||||
body: JSON.stringify({
|
||||
refProjectID,
|
||||
projectUuid,
|
||||
thumbnail,
|
||||
projectName,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const newAccessToken = response.headers.get("x-access-token");
|
||||
if (newAccessToken) {
|
||||
//console.log("New token received:", newAccessToken);
|
||||
//
|
||||
localStorage.setItem("token", newAccessToken);
|
||||
}
|
||||
// console.log("response: ", response);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to add project");
|
||||
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
// console.log("result: ", result);
|
||||
//
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.log(error.message);
|
||||
|
||||
} else {
|
||||
console.log("An unknown error occurred");
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user