Merge branch 'main-dev' into dev-contextMenu

This commit is contained in:
2025-08-13 10:39:36 +05:30
44 changed files with 2723 additions and 615 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 && (

View File

@@ -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;

View File

@@ -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

View File

@@ -6,8 +6,8 @@ import RenameInput from "../../../../../ui/inputs/RenameInput";
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
import Trigger from "../trigger/Trigger";
import ActionsList from "../components/ActionsList";
import WorkerAction from "../actions/workerAction";
import AssemblyAction from "../actions/assemblyAction";
import WorkerAction from "../actions/WorkerAction";
import AssemblyAction from "../actions/AssemblyAction";
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";

View File

@@ -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;

View File

@@ -24,17 +24,17 @@ export function useModelEventHandlers({
boundingBox: THREE.Box3 | null,
groupRef: React.RefObject<THREE.Group>,
}) {
const { controls, gl } = useThree();
const { controls, gl, camera } = useThree();
const { activeTool } = useActiveTool();
const { activeModule } = useModuleStore();
const { toggleView } = useToggleView();
const { subModule } = useSubModuleStore();
const { socket } = useSocketStore();
const { eventStore, productStore, assetStore } = useSceneContext();
const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
const { removeAsset } = assetStore();
const { removeEvent } = eventStore();
const { removeEvent, getEventByModelUuid } = eventStore();
const { getIsEventInProduct, addPoint, deleteEvent } = productStore();
const { getEventByModelUuid } = eventStore();
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { setSelectedFloorItem } = useSelectedFloorItem();
@@ -68,25 +68,47 @@ export function useModelEventHandlers({
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);
}
@@ -130,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!");
}

View File

@@ -8,7 +8,7 @@ import useModuleStore from '../../../../../store/useModuleStore';
import { useSceneContext } from '../../../../scene/sceneContext';
import { SkeletonUtils } from 'three-stdlib';
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
import { getAssetFieldApi } from '../../../../../services/factoryBuilder/asset/floorAsset/getAssetField';
import { ModelAnimator } from './animator/modelAnimator';
import { useModelEventHandlers } from './eventHandlers/useEventHandlers';
@@ -26,19 +26,31 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
const [isSelected, setIsSelected] = useState(false);
const groupRef = useRef<THREE.Group>(null);
const [ikData, setIkData] = useState<any>();
const [fieldData, setFieldData] = useState<any>();
const { selectedAssets } = useSelectedAssets();
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;
setFieldData(fieldData);
}
} else if (data.type === 'Crane') {
if (data.data) {
const fieldData = data.data;
setFieldData(fieldData);
}
}
})
}
}, [asset.modelUuid, ikData])
}, [asset.modelUuid, fieldData])
useEffect(() => {
setDeletableFloorItem(null);
@@ -157,7 +169,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
position={asset.position}
rotation={asset.rotation}
visible={asset.isVisible}
userData={{ ...asset, iks: ikData }}
userData={{ ...asset, fieldData: fieldData }}
castShadow
receiveShadow
onDoubleClick={(e) => {

View File

@@ -17,6 +17,7 @@ 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);
@@ -145,6 +146,8 @@ export default function Controls() {
<UndoRedo2DControls />
<UndoRedo3DControls />
<TransformControl />
<ContextControls />

View File

@@ -28,7 +28,8 @@ 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, updateAsset, removeAsset, getAssetById } = assetStore();
@@ -209,6 +210,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;
@@ -540,9 +544,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();
};

View File

@@ -26,7 +26,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();
@@ -207,6 +208,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;
@@ -538,9 +542,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();
};

View File

@@ -37,7 +37,8 @@ function MoveControls3D({
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();
@@ -292,6 +293,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;
@@ -299,6 +303,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,
@@ -376,6 +406,27 @@ 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();

View File

@@ -32,7 +32,8 @@ 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();
@@ -227,12 +228,43 @@ function RotateControls3D({
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,
@@ -310,6 +342,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]);

View File

@@ -31,9 +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();
@@ -274,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
@@ -329,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) => {

View File

@@ -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}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View 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;
}

View File

@@ -14,8 +14,9 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects";
import { getUserData } from "../../functions/getUserData";
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
import { Color, SRGBColorSpace } from "three";
import StatsHelper from "./helpers/StatsHelper";
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
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"] },
@@ -32,28 +33,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);
});
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])
}, [activeModule, assets, loadingProgress]);
return (
<KeyboardControls map={map}>
@@ -62,20 +62,17 @@ 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 />
<Builder />
<Simulation />
<Visualization />
<StatsHelper />
</Canvas>
</KeyboardControls>
);

View File

@@ -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';
@@ -31,6 +32,7 @@ type SceneContextValue = {
floorStore: FloorStoreType,
undoRedo2DStore: UndoRedo2DStoreType,
undoRedo3DStore: UndoRedo3DStoreType,
eventStore: EventStoreType,
productStore: ProductStoreType,
@@ -45,6 +47,7 @@ type SceneContextValue = {
craneStore: CraneStoreType;
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
craneEventManagerRef: React.RefObject<CraneEventManagerState>;
clearStores: () => void;
@@ -69,6 +72,7 @@ export function SceneProvider({
const floorStore = useMemo(() => createFloorStore(), []);
const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []);
const undoRedo3DStore = useMemo(() => createUndoRedo3DStore(), []);
const eventStore = useMemo(() => createEventStore(), []);
const productStore = useMemo(() => createProductStore(), []);
@@ -83,6 +87,7 @@ export function SceneProvider({
const craneStore = useMemo(() => createCraneStore(), []);
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
const clearStores = useMemo(() => () => {
assetStore.getState().clearAssets();
@@ -92,6 +97,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();
@@ -103,7 +109,8 @@ export function SceneProvider({
humanStore.getState().clearHumans();
craneStore.getState().clearCranes();
humanEventManagerRef.current.humanStates = [];
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]);
craneEventManagerRef.current.craneStates = [];
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, undoRedo3DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]);
const contextValue = useMemo(() => (
{
@@ -114,6 +121,7 @@ export function SceneProvider({
zoneStore,
floorStore,
undoRedo2DStore,
undoRedo3DStore,
eventStore,
productStore,
materialStore,
@@ -125,10 +133,11 @@ export function SceneProvider({
humanStore,
craneStore,
humanEventManagerRef,
craneEventManagerRef,
clearStores,
layout
}
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]);
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, undoRedo3DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]);
return (
<SceneContext.Provider value={contextValue}>

View File

@@ -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);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
pickAndDropLogStatus(material.materialName, `performing pickAndDrop action`);
}, [getMaterialById]);
return {
handlePickAndDrop,
};
}

View 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
};
}

View File

@@ -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 () => {

View File

@@ -0,0 +1,99 @@
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, craneEventManagerRef } = useSceneContext();
const { getCraneById, setCurrentPhase, removeCurrentAction } = craneStore();
const { getAssetById } = assetStore();
const { getActionByUuid } = 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) {
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
};
}

View File

@@ -1,82 +1,79 @@
import { useEffect, useState } from 'react';
import * as THREE from 'three';
import { useEffect, useMemo } from 'react';
import { useThree } from '@react-three/fiber';
import { useFrame, useThree } from '@react-three/fiber';
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { useSceneContext } from '../../../../scene/sceneContext';
function PillarJibAnimator({ crane }: { crane: CraneStatus }) {
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 } = useSceneContext();
const { resetAsset } = assetStore();
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) {
const base = model.getObjectByName('base');
const trolley = model.getObjectByName('trolley');
const hook = model.getObjectByName('hook');
if (!model) return;
const hook = model.getObjectByName('hook');
if (base && trolley && hook) {
let trolleyDir = 1;
let hookDir = 1;
if (!hook) return;
const hookWorld = new THREE.Vector3();
hook.getWorldPosition(hookWorld);
const trolleySpeed = 0.01;
const hookSpeed = 0.01;
const rotationSpeed = 0.005;
const trolleyMinOffset = -1;
const trolleyMaxOffset = 1.75;
const hookMinOffset = 0.25;
const hookMaxOffset = -1.5;
const originalTrolleyX = trolley.position.x;
const originalHookY = hook.position.y;
const animate = () => {
if (base) {
base.rotation.y += rotationSpeed;
}
if (trolley) {
trolley.position.x += trolleyDir * trolleySpeed;
if (trolley.position.x >= originalTrolleyX + trolleyMaxOffset ||
trolley.position.x <= originalTrolleyX + trolleyMinOffset) {
trolleyDir *= -1;
}
}
if (hook) {
hook.position.y += hookDir * hookSpeed;
if (hook.position.y >= originalHookY + hookMinOffset ||
hook.position.y <= originalHookY + hookMaxOffset) {
hookDir *= -1;
}
}
requestAnimationFrame(animate);
};
animate();
if (crane.currentPhase === 'init-pickup') {
if (crane.currentMaterials.length > 0) {
const material = scene.getObjectByProperty('uuid', crane.currentMaterials[0].materialId);
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)
]
);
}
}
}
}, [crane, scene]);
}, [crane.currentPhase])
return (
<PillarJibHelper crane={crane} />
);
}
export default PillarJibAnimator;
function PillarJibHelper({ crane }: { crane: CraneStatus }) {
const { scene } = useThree();
const { geometry, position } = useMemo(() => {
useEffect(() => {
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
if (!model) return { geometry: null, position: null };
if (!model) return;
const base = model.getObjectByName('base');
const trolley = model.getObjectByName('trolley');
const hook = model.getObjectByName('hook');
if (!base || !trolley || !hook) return { geometry: null, position: null };
if (!base || !trolley || !hook || !points) return;
const baseWorld = new THREE.Vector3();
base.getWorldPosition(baseWorld);
@@ -87,47 +84,244 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) {
const hookWorld = new THREE.Vector3();
hook.getWorldPosition(hookWorld);
const trolleyMinOffset = -1;
const trolleyMaxOffset = 1.75;
const hookMinOffset = 0.25;
const hookMaxOffset = -1.5;
const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
const outerRadius = distFromBase + 1.75;
const innerRadius = Math.max(distFromBase - 1, 0.05);
const height = (0.25 - (-1.5));
const cylinderYPosition = hookWorld.y + (height / 2) + (-1.5 + 0.25) / 2;
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 shape = new THREE.Shape();
shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false);
const yMin = hookWorld.y + hookMaxOffset;
const yMax = hookWorld.y + hookMinOffset;
const hole = new THREE.Path();
hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true);
shape.holes.push(hole);
function clampToCylinder(pos: THREE.Vector3) {
const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
const distance = xzDist.length();
const extrudeSettings = {
depth: height,
bevelEnabled: false,
steps: 1
};
let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
if (distance > 0) {
clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
}
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];
const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);
return { geometry, position };
}, [scene, crane.modelUuid]);
return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
}
if (!geometry || !position) return null;
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 = 0.01 * speed;
const rotationSpeed = 0.005 * speed;
const trolleySpeed = 0.01 * speed;
switch (animationPhase) {
case 'init-hook-adjust': {
const hookWorld = new THREE.Vector3();
hook.getWorldPosition(hookWorld);
const direction = Math.sign((clampedPoints[0].y - baseWorld.y) - hookWorld.y);
hook.position.y += direction * hookSpeed;
if (parseFloat(Math.abs(hookWorld.y - clampedPoints[0].y).toFixed(2)) < 0.05) {
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 (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[0].clone());
animationData.targetTrolleyX = localTarget?.x;
setAnimationPhase('init-move-trolley');
}
break;
}
case 'init-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;
setAnimationPhase('init-final-hook-adjust');
}
break;
}
case 'init-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;
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 (
<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>
<>
</>
);
}
export default PillarJibAnimator;

View File

@@ -12,7 +12,7 @@ function CraneInstances() {
<React.Fragment key={crane.modelUuid}>
{crane.subType === "pillarJib" &&
<PillarJibInstance crane={crane} />
<PillarJibInstance key={crane.modelUuid} crane={crane} />
}
</React.Fragment>

View File

@@ -0,0 +1,141 @@
import { useMemo, 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
}: {
crane: CraneStatus,
points: [THREE.Vector3, THREE.Vector3] | null;
}) {
const { scene } = useThree();
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
const { geometry, position } = useMemo(() => {
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
if (!model) 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 = new THREE.Vector3();
base.getWorldPosition(baseWorld);
const trolleyWorld = new THREE.Vector3();
trolley.getWorldPosition(trolleyWorld);
const hookWorld = new THREE.Vector3();
hook.getWorldPosition(hookWorld);
const trolleyMinOffset = -1;
const trolleyMaxOffset = 1.75;
const hookMinOffset = 0.25;
const 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 (
<>
<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;

View File

@@ -1,14 +1,61 @@
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 PillarJibAnimator from '../animator/pillarJibAnimator'
import PillarJibHelper from '../helper/pillarJibHelper'
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
const { isPlaying } = usePlayButtonStore();
const { craneStore, productStore } = useSceneContext();
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
const { getCraneById, setCurrentPhase } = craneStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const [animationPhase, setAnimationPhase] = useState<string>('idle');
const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null);
useEffect(() => {
if (isPlaying) {
const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '');
if (!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');
}
}
}, [crane])
const handleAnimationComplete = (action: string) => {
if (action === 'starting') {
setAnimationPhase('first-hook-adjust');
} else if (action === 'picking') {
setCurrentPhase(crane.modelUuid, 'picking');
}
}
return (
<>
<PillarJibAnimator crane={crane} />
<PillarJibAnimator
key={crane.modelUuid}
crane={crane}
points={points}
setPoints={setPoints}
animationPhase={animationPhase}
setAnimationPhase={setAnimationPhase}
onAnimationComplete={handleAnimationComplete}
/>
<PillarJibHelper
crane={crane}
points={points}
/>
</>
)
}
export default PillarJibInstance
export default PillarJibInstance;

View File

@@ -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);
};

View File

@@ -173,8 +173,8 @@ const ArmBotUI = () => {
const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || '');
const iks = targetMesh?.userData?.iks;
const firstIK = Array.isArray(iks) && iks.length > 0 ? iks[0] : {};
const fieldData = targetMesh?.userData?.fieldData;
const firstIK = Array.isArray(fieldData) && fieldData.length > 0 ? fieldData[0] : {};
const { handlePointerDown } = useDraggableGLTF(
updatePointToState,

View File

@@ -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 } = craneStore();
const { getMachineById, setMachineActive } = machineStore();
const { getStorageUnitById } = storageUnitStore();
const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore();
@@ -388,6 +391,65 @@ export function useTriggerHandler() {
}
}
}
} 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 === 'transfer') {
const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (crane) {
setIsPaused(materialId, true);
setCraneScheduled(crane.modelUuid, true);
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (conveyor) {
addConveyorToMonitor(conveyor.modelUuid, () => {
addCraneToMonitor(crane.modelUuid, () => {
setIsPaused(materialId, true);
handleAction(action, materialId);
}, action.actionUuid)
})
}
}
}
}
}
}
}
}
}
}
} else if (fromEvent?.type === 'vehicle') {
if (toEvent?.type === 'transfer') {
@@ -1547,6 +1609,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
}
}
}

View File

@@ -33,7 +33,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();
@@ -56,7 +56,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);

View File

@@ -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");
}
}
};

View File

@@ -1,9 +1,9 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
export const getAssetIksApi = async (assetId: string) => {
export const getAssetFieldApi = async (assetId: string) => {
try {
const response = await fetch(
`${url_Backend_dwinzo}/api/v2/getAssetIks/${assetId}`,
`${url_Backend_dwinzo}/api/v2/getAssetField/${assetId}`,
{
method: "GET",
headers: {
@@ -20,13 +20,13 @@ export const getAssetIksApi = async (assetId: string) => {
}
if (!response.ok) {
console.error("Failed to fetch assetIks");
console.error("Failed to fetch asset field");
}
const result = await response.json();
return result;
} catch (error) {
echo.error("Failed to get assetIks");
echo.error("Failed to get asset field");
if (error instanceof Error) {
console.log(error.message);
} else {

View File

@@ -0,0 +1,78 @@
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { undoRedoConfig } from '../../types/world/worldConstants';
type UndoRedo3DStore = {
undoStack: UndoRedo3DTypes[];
redoStack: UndoRedo3DTypes[];
push3D: (entry: UndoRedo3DTypes) => void;
undo3D: () => UndoRedo3DTypes | undefined;
redo3D: () => UndoRedo3DTypes | undefined;
clearUndoRedo3D: () => void;
peekUndo3D: () => UndoRedo3DTypes | undefined;
peekRedo3D: () => UndoRedo3DTypes | undefined;
};
export const createUndoRedo3DStore = () => {
return create<UndoRedo3DStore>()(
immer((set, get) => ({
undoStack: [],
redoStack: [],
push3D: (entry) => {
set((state) => {
state.undoStack.push(entry);
if (state.undoStack.length > undoRedoConfig.undoRedoCount) {
state.undoStack.shift();
}
state.redoStack = [];
});
},
undo3D: () => {
let lastAction: UndoRedo3DTypes | undefined;
set((state) => {
lastAction = state.undoStack.pop();
if (lastAction) {
state.redoStack.unshift(lastAction);
}
});
return lastAction;
},
redo3D: () => {
let redoAction: UndoRedo3DTypes | undefined;
set((state) => {
redoAction = state.redoStack.shift();
if (redoAction) {
state.undoStack.push(redoAction);
}
});
return redoAction;
},
clearUndoRedo3D: () => {
set((state) => {
state.undoStack = [];
state.redoStack = [];
});
},
peekUndo3D: () => {
const stack = get().undoStack;
return stack.length > 0 ? stack[stack.length - 1] : undefined;
},
peekRedo3D: () => {
const stack = get().redoStack;
return stack.length > 0 ? stack[0] : undefined;
},
}))
);
}
export type UndoRedo3DStoreType = ReturnType<typeof createUndoRedo3DStore>;

View File

@@ -253,4 +253,69 @@ type UndoRedo2DTypes = UndoRedo2DDraw | UndoRedo2DUi
type UndoRedo2D = {
undoStack: UndoRedo2DTypes[];
redoStack: UndoRedo2DTypes[];
};
// Undo/Redo 3D
type AssetType = {
type: "Asset";
assetData: Asset;
newData?: Asset;
eventMetaData?: EventsSchema;
timeStap: string;
}
type WallAssetType = {
type: "WallAsset";
assetData: WallAsset;
newData?: WallAsset;
timeStap: string;
}
type AssetData = AssetType | WallAssetType;
type UndoRedo3DActionBuilderSchema = {
module: "builder";
actionType: "Asset-Add" | "Asset-Delete" | "Asset-Update" | "Asset-Duplicated" | "Asset-Copied" | "Wall-Asset-Add" | "Wall-Asset-Delete" | "Wall-Asset-Update";
asset: AssetData;
}
type UndoRedo3DActionSimulationSchema = {
module: "simulation";
actionType: '';
}
type UndoRedo3DActionSchema = UndoRedo3DActionBuilderSchema | UndoRedo3DActionSimulationSchema;
type UndoRedo3DActionsBuilderSchema = {
module: "builder";
actionType: "Assets-Add" | "Assets-Delete" | "Assets-Update" | "Assets-Duplicated" | "Assets-Copied" | "Wall-Assets-Add" | "Wall-Assets-Delete" | "Wall-Assets-Update";
assets: AssetData[];
}
type UndoRedo3DActionsSimulationSchema = {
module: "simulation";
actionType: '';
}
type UndoRedo3DActionsSchema = UndoRedo3DActionsBuilderSchema | UndoRedo3DActionsSimulationSchema;
type UndoRedo3DAction = UndoRedo3DActionSchema | UndoRedo3DActionsSchema;
type UndoRedo3DDraw = {
type: 'Scene';
actions: UndoRedo3DAction[];
};
type UndoRedo3DUi = {
type: 'UI';
action: any; // Define UI actions as needed
}
type UndoRedo3DTypes = UndoRedo3DDraw | UndoRedo3DUi;
type UndoRedo3D = {
undoStack: UndoRedo3DTypes[];
redoStack: UndoRedo3DTypes[];
};

View File

@@ -336,6 +336,23 @@ type HumanEventManagerState = {
humanStates: HumanEventState[];
};
type CraneEventState = {
craneId: string;
pendingActions: {
actionUuid: string;
callback: () => void;
}[];
currentAction: {
actionUuid: string;
callback: () => void;
} | null;
isProcessing: boolean;
};
type CraneEventManagerState = {
craneStates: CraneEventState[];
};
// Materials

View File

@@ -191,12 +191,9 @@ const KeyPressListener: React.FC = () => {
};
const handleKeyPress = (event: KeyboardEvent) => {
if (isTextInput(document.activeElement)) return;
const keyCombination = detectModifierKeys(event);
if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE")
return;
if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") return;
if (keyCombination === "ESCAPE") {
setWalkMode(false);