folder structure change

This commit is contained in:
2025-04-21 11:53:42 +05:30
parent 31561428ef
commit 54cc3deb98
118 changed files with 3014 additions and 13419 deletions

View File

@@ -5,8 +5,8 @@ import Header from "./Header";
import useToggleStore from "../../../store/useUIToggleStore";
import Assets from "./Assets";
import useModuleStore from "../../../store/useModuleStore";
import Widgets from "./visualization/widgets/Widgets";
import Templates from "../../../modules/visualization/template/Templates";
import Widgets from ".//visualization/widgets/Widgets";
import Templates from "../../../modules//visualization/template/Templates";
import Search from "../../ui/inputs/Search";
const SideBarLeft: React.FC = () => {

View File

@@ -5,11 +5,11 @@ import {
GlobeIcon,
WalletIcon,
} from "../../../../icons/3dChartIcons";
import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard";
import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard";
import WarehouseThroughput from "../../../../../modules/visualization/widgets/floating/cards/WarehouseThroughput";
import ProductivityDashboard from "../../../../../modules/visualization/widgets/floating/cards/ProductivityDashboard";
import FleetEfficiency from "../../../../../modules/visualization/widgets/floating/cards/FleetEfficiency";
import WarehouseThroughput from "../../../../../modules//visualization/widgets/floating/cards/WarehouseThroughput";
import ProductivityDashboard from "../../../../../modules//visualization/widgets/floating/cards/ProductivityDashboard";
import FleetEfficiency from "../../../../../modules//visualization/widgets/floating/cards/FleetEfficiency";
interface Widget {
id: string;

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { AppDockIcon } from "../../icons/HeaderIcons";
import orgImg from "../../../assets/orgTemp.png";
import { useActiveUsers } from "../../../store/store";
import { getAvatarColor } from "../../../modules/collaboration/users/functions/getAvatarColor";
import { getAvatarColor } from "../../../functions/users/functions/getAvatarColor";
import { ActiveUser } from "../../../types/users";
import CollaborationPopup from "../../templates/CollaborationPopup";

View File

@@ -10,25 +10,19 @@ import {
SimulationIcon,
} from "../../icons/SimulationIcons";
import useToggleStore from "../../../store/useUIToggleStore";
import ConveyorMechanics from "./mechanics/ConveyorMechanics";
import Visualization from "./visualization/Visualization";
import Analysis from "./analysis/Analysis";
import Simulations from "./simulation/Simulations";
import {
useSelectedActionSphere,
useSelectedFloorItem,
} from "../../../store/store";
import GlobalProperties from "./properties/GlobalProperties";
import AsstePropertiies from "./properties/AssetProperties";
import ZoneProperties from "./properties/ZoneProperties";
import VehicleMechanics from "./mechanics/VehicleMechanics";
import StaticMachineMechanics from "./mechanics/StaticMachineMechanics";
import ArmBotMechanics from "./mechanics/ArmBotMechanics";
const SideBarRight: React.FC = () => {
const { activeModule } = useModuleStore();
const { toggleUI } = useToggleStore();
const { selectedActionSphere } = useSelectedActionSphere();
const { subModule, setSubModule } = useSubModuleStore();
const { selectedFloorItem } = useSelectedFloorItem();
// Reset activeList whenever activeModule changes
@@ -112,46 +106,9 @@ const SideBarRight: React.FC = () => {
{toggleUI && activeModule === "simulation" && (
<>
{subModule === "mechanics" &&
selectedActionSphere &&
selectedActionSphere.path.type === "Conveyor" && (
{subModule === "mechanics" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<ConveyorMechanics />
</div>
</div>
)}
{subModule === "mechanics" &&
selectedActionSphere &&
selectedActionSphere.path.type === "Vehicle" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<VehicleMechanics />
</div>
</div>
)}
{subModule === "mechanics" &&
selectedActionSphere &&
selectedActionSphere.path.type === "StaticMachine" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<StaticMachineMechanics />
</div>
</div>
)}
{subModule === "mechanics" &&
selectedActionSphere &&
selectedActionSphere.path.type === "ArmBot" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<ArmBotMechanics />
</div>
</div>
)}
{subModule === "mechanics" && !selectedActionSphere && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<ConveyorMechanics /> {/* default */}
</div>
</div>
)}

View File

@@ -1,411 +0,0 @@
import React, { useRef, useMemo, useCallback, useState } from "react";
import { InfoIcon, AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store";
import * as SimulationTypes from '../../../../types/simulationTypes';
import LabledDropdown from "../../../ui/inputs/LabledDropdown";
import { handleResize } from "../../../../functions/handleResizePannel";
interface ConnectedModel {
modelUUID: string;
modelName: string;
points: {
uuid: string;
position: [number, number, number];
index?: number;
}[];
triggers?: {
uuid: string;
name: string;
type: string;
isUsed: boolean;
}[];
}
const ArmBotMechanics: React.FC = () => {
const { selectedActionSphere } = useSelectedActionSphere();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { socket } = useSocketStore();
const [selectedProcessIndex, setSelectedProcessIndex] = useState<number | null>(null);
const actionsContainerRef = useRef<HTMLDivElement>(null);
// Get connected models and their triggers
const connectedModels = useMemo<ConnectedModel[]>(() => {
if (!selectedActionSphere?.points?.uuid) return [];
const armBotPaths = simulationStates.filter(
(path): path is SimulationTypes.ArmBotEventsSchema => path.type === "ArmBot"
);
const currentPoint = armBotPaths.find(
(path) => path.points.uuid === selectedActionSphere.points.uuid
)?.points;
if (!currentPoint?.connections?.targets) return [];
return currentPoint.connections.targets.reduce<ConnectedModel[]>((acc, target) => {
const connectedModel = simulationStates.find(
(model) => model.modeluuid === target.modelUUID
);
if (!connectedModel) return acc;
let triggers: { uuid: string; name: string; type: string; isUsed: boolean }[] = [];
let points: { uuid: string; position: [number, number, number] }[] = [];
if (connectedModel.type === "Conveyor") {
const conveyor = connectedModel as SimulationTypes.ConveyorEventsSchema;
const connectedPointUUIDs = currentPoint?.connections?.targets
.filter(t => t.modelUUID === connectedModel.modeluuid)
.map(t => t.pointUUID) || [];
points = conveyor.points
.map((point, idx) => ({
uuid: point.uuid,
position: point.position,
index: idx
}))
.filter(point => connectedPointUUIDs.includes(point.uuid));
triggers = conveyor.points.flatMap(p => p.triggers?.filter(t => t.isUsed) || []);
}
else if (connectedModel.type === "StaticMachine") {
const staticMachine = connectedModel as SimulationTypes.StaticMachineEventsSchema;
points = [{
uuid: staticMachine.points.uuid,
position: staticMachine.points.position
}];
triggers = staticMachine.points.triggers ?
[{
uuid: staticMachine.points.triggers.uuid,
name: staticMachine.points.triggers.name,
type: staticMachine.points.triggers.type,
isUsed: true // StaticMachine triggers are always considered used
}] : [];
}
if (!acc.some(m => m.modelUUID === connectedModel.modeluuid)) {
acc.push({
modelUUID: connectedModel.modeluuid,
modelName: connectedModel.modelName,
points,
triggers
});
}
return acc;
}, []);
}, [selectedActionSphere, simulationStates]);
// Get triggers from connected models
const connectedTriggers = useMemo(() => {
return connectedModels.flatMap(model =>
(model.triggers || []).map(trigger => ({
...trigger,
displayName: `${model.modelName} - ${trigger.name}`,
modelUUID: model.modelUUID
}))
);
}, [connectedModels]);
// Get all points from connected models
const connectedPoints = useMemo(() => {
return connectedModels.flatMap(model =>
model.points.map(point => ({
...point,
displayName: `${model.modelName} - Point${typeof point.index === 'number' ? ` ${point.index}` : ''}`,
modelUUID: model.modelUUID
}))
);
}, [connectedModels]);
const { selectedPoint } = useMemo(() => {
if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null };
const armBotPaths = simulationStates.filter(
(path): path is SimulationTypes.ArmBotEventsSchema => path.type === "ArmBot"
);
const points = armBotPaths.find(
(path) => path.points.uuid === selectedActionSphere.points.uuid
)?.points;
return {
selectedPoint: points || null
};
}, [selectedActionSphere, simulationStates]);
const updateBackend = async (updatedPath: SimulationTypes.ArmBotEventsSchema | undefined) => {
if (!updatedPath) return;
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "";
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: { type: "ArmBot", points: updatedPath.points }
}
socket.emit('v2:model-asset:updateEventData', data);
}
const handleActionUpdate = useCallback((updatedAction: Partial<SimulationTypes.ArmBotEventsSchema['points']['actions']>) => {
if (!selectedActionSphere?.points?.uuid || !selectedPoint) return;
const updatedPaths = simulationStates.map((path) => {
if (path.type === "ArmBot" && path.points.uuid === selectedActionSphere.points.uuid) {
return {
...path,
points: {
...path.points,
actions: {
...path.points.actions,
...updatedAction
}
}
};
}
return path;
});
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ArmBotEventsSchema =>
path.type === "ArmBot" &&
path.points.uuid === selectedActionSphere.points.uuid
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
}, [selectedActionSphere?.points?.uuid, selectedPoint, simulationStates, setSimulationStates]);
const handleSpeedChange = useCallback((speed: number) => {
handleActionUpdate({ speed });
}, [handleActionUpdate]);
const handleProcessChange = useCallback((processes: SimulationTypes.ArmBotEventsSchema['points']['actions']['processes']) => {
handleActionUpdate({ processes });
}, [handleActionUpdate]);
const handleAddProcess = useCallback(() => {
if (!selectedPoint) return;
const newProcess: any = {
triggerId: "",
startPoint: "",
endPoint: ""
};
const updatedProcesses = selectedPoint.actions.processes ? [...selectedPoint.actions.processes, newProcess] : [newProcess];
handleProcessChange(updatedProcesses);
setSelectedProcessIndex(updatedProcesses.length - 1);
}, [selectedPoint, handleProcessChange]);
const handleDeleteProcess = useCallback((index: number) => {
if (!selectedPoint?.actions.processes) return;
const updatedProcesses = [...selectedPoint.actions.processes];
updatedProcesses.splice(index, 1);
handleProcessChange(updatedProcesses);
// Reset selection if deleting the currently selected process
if (selectedProcessIndex === index) {
setSelectedProcessIndex(null);
} else if (selectedProcessIndex !== null && selectedProcessIndex > index) {
// Adjust selection index if needed
setSelectedProcessIndex(selectedProcessIndex - 1);
}
}, [selectedPoint, selectedProcessIndex, handleProcessChange]);
const handleTriggerSelect = useCallback((displayName: string, index: number) => {
const availableOptions = getFilteredTriggerOptions(index);
const selectedDisplayIndex = availableOptions.indexOf(displayName);
const filteredTriggers = connectedTriggers.filter(trigger =>
!selectedPoint?.actions.processes
?.filter((_, i) => i !== index)
.map(p => p.triggerId)
.includes(trigger.uuid)
);
const selected = filteredTriggers[selectedDisplayIndex];
if (!selected || !selectedPoint?.actions.processes) return;
const oldProcess = selectedPoint.actions.processes[index];
const updatedProcesses = [...selectedPoint.actions.processes];
updatedProcesses[index] = {
...oldProcess,
triggerId: selected.uuid,
startPoint: oldProcess.startPoint || "",
endPoint: oldProcess.endPoint || ""
};
handleProcessChange(updatedProcesses);
}, [connectedTriggers, selectedPoint, handleProcessChange]);
const handleStartPointSelect = useCallback((displayName: string, index: number) => {
if (!selectedPoint?.actions.processes) return;
const point = connectedPoints.find(p => p.displayName === displayName);
if (!point) return;
const updatedProcesses = [...selectedPoint.actions.processes];
updatedProcesses[index] = {
...updatedProcesses[index],
startPoint: point.uuid
};
handleProcessChange(updatedProcesses);
}, [selectedPoint, connectedPoints, handleProcessChange]);
const handleEndPointSelect = useCallback((displayName: string, index: number) => {
if (!selectedPoint?.actions.processes) return;
const point = connectedPoints.find(p => p.displayName === displayName);
if (!point) return;
const updatedProcesses = [...selectedPoint.actions.processes];
updatedProcesses[index] = {
...updatedProcesses[index],
endPoint: point.uuid
};
handleProcessChange(updatedProcesses);
}, [selectedPoint, connectedPoints, handleProcessChange]);
const getProcessByIndex = useCallback((index: number) => {
if (!selectedPoint?.actions.processes || index >= selectedPoint.actions.processes.length) return null;
return selectedPoint.actions.processes[index];
}, [selectedPoint]);
const getFilteredTriggerOptions = (currentIndex: number) => {
const usedTriggerUUIDs = selectedPoint?.actions.processes?.filter((_, i) => i !== currentIndex).map(p => p.triggerId).filter(Boolean) ?? [];
return connectedTriggers.filter(trigger => !usedTriggerUUIDs.includes(trigger.uuid)).map(trigger => trigger.displayName);
};
return (
<div className="machine-mechanics-container" key={selectedPoint?.uuid}>
<div className="machine-mechanics-header">
{selectedActionSphere?.path?.modelName || "ArmBot point not found"}
</div>
<div className="machine-mechanics-content-container">
<div className="selected-properties-container">
<div className="properties-header">ArmBot Properties</div>
{selectedPoint && (
<>
<InputWithDropDown
key={`speed-${selectedPoint.uuid}`}
label="ArmBot Speed"
min={0.1}
step={0.1}
value={selectedPoint.actions.speed.toString()}
onChange={(value) => handleSpeedChange(parseFloat(value))}
/>
<div className="actions">
<div className="header">
<div className="header-value">Processes</div>
<div className="add-button" onClick={handleAddProcess}>
<AddIcon /> Add
</div>
</div>
<div
className="lists-main-container"
ref={actionsContainerRef}
style={{ height: "120px" }}
>
<div className="list-container">
{selectedPoint.actions.processes?.map((process, index) => (
<div
key={`process-${index}`}
className={`list-item ${selectedProcessIndex === index ? "active" : ""}`}
>
<div
className="value"
onClick={() => setSelectedProcessIndex(index)}
>
Process {index + 1}
</div>
<div
className="remove-button"
onClick={() => handleDeleteProcess(index)}
>
<RemoveIcon />
</div>
</div>
))}
</div>
<div
className="resize-icon"
id="action-resize"
onMouseDown={(e) => handleResize(e, actionsContainerRef)}
>
<ResizeHeightIcon />
</div>
</div>
</div>
{selectedProcessIndex !== null && (
<div className="process-configuration">
<LabledDropdown
key={`trigger-select-${selectedProcessIndex}`}
label="Select Trigger"
defaultOption={
connectedTriggers.find(t =>
t.uuid === getProcessByIndex(selectedProcessIndex)?.triggerId
)?.displayName || 'Select a trigger'
}
onSelect={(value) => handleTriggerSelect(value, selectedProcessIndex)}
options={getFilteredTriggerOptions(selectedProcessIndex)}
/>
<LabledDropdown
key={`start-point-${selectedProcessIndex}`}
label="Start Point"
defaultOption={
connectedPoints.find(p =>
p.uuid === getProcessByIndex(selectedProcessIndex)?.startPoint
)?.displayName || 'Select start point'
}
onSelect={(value) => handleStartPointSelect(value, selectedProcessIndex)}
options={connectedPoints.map(point => point.displayName)}
/>
<LabledDropdown
key={`end-point-${selectedProcessIndex}`}
label="End Point"
defaultOption={
connectedPoints.find(p =>
p.uuid === getProcessByIndex(selectedProcessIndex)?.endPoint
)?.displayName || 'Select end point'
}
onSelect={(value) => handleEndPointSelect(value, selectedProcessIndex)}
options={connectedPoints.map(point => point.displayName)}
/>
</div>
)}
</>
)}
</div>
<div className="footer">
<InfoIcon />
Configure ArmBot properties and trigger-based processes.
</div>
</div>
</div>
);
};
export default React.memo(ArmBotMechanics);

View File

@@ -1,879 +0,0 @@
import React, { useRef, useState, useMemo, useEffect } from "react";
import {
AddIcon,
InfoIcon,
RemoveIcon,
ResizeHeightIcon,
} from "../../../icons/ExportCommonIcons";
import RenameInput from "../../../ui/inputs/RenameInput";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import LabledDropdown from "../../../ui/inputs/LabledDropdown";
import { handleResize } from "../../../../functions/handleResizePannel";
import {
useFloorItems,
useSelectedActionSphere,
useSelectedPath,
useSimulationStates,
useSocketStore,
} from "../../../../store/store";
import * as THREE from "three";
import * as SimulationTypes from "../../../../types/simulationTypes";
import InputToggle from "../../../ui/inputs/InputToggle";
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt";
const ConveyorMechanics: React.FC = () => {
const { selectedActionSphere } = useSelectedActionSphere();
const { selectedPath, setSelectedPath } = useSelectedPath();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const actionsContainerRef = useRef<HTMLDivElement>(null);
const triggersContainerRef = useRef<HTMLDivElement>(null);
const selectedPoint = useMemo(() => {
if (!selectedActionSphere) return null;
return simulationStates
.filter(
(path): path is SimulationTypes.ConveyorEventsSchema => path.type === "Conveyor"
)
.flatMap((path) => path.points)
.find((point) => point.uuid === selectedActionSphere.points.uuid);
}, [selectedActionSphere, simulationStates]);
const updateBackend = async (updatedPath: SimulationTypes.ConveyorEventsSchema | undefined) => {
if (!updatedPath) return;
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "";
// await setEventApi(
// organization,
// updatedPath.modeluuid,
// { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed }
// );
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed }
}
socket.emit('v2:model-asset:updateEventData', data);
}
const handleAddAction = () => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) => {
if (path.type === "Conveyor") {
return {
...path,
points: path.points.map((point) => {
if (point.uuid === selectedActionSphere.points.uuid) {
const actionIndex = point.actions.length;
const newAction = {
uuid: THREE.MathUtils.generateUUID(),
name: `Action ${actionIndex + 1}`,
type: "Inherit",
material: "Inherit",
delay: "Inherit",
spawnInterval: "Inherit",
isUsed: false,
};
return { ...point, actions: [...point.actions, newAction] };
}
return point;
}),
};
}
return path;
});
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
};
const handleDeleteAction = (uuid: string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
actions: point.actions.filter(
(action) => action.uuid !== uuid
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
};
const handleActionSelect = (uuid: string, actionType: string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
actions: point.actions.map((action) =>
action.uuid === uuid
? {
...action,
type: actionType,
material:
actionType === "Spawn" || actionType === "Swap"
? "Inherit"
: action.material,
delay:
actionType === "Delay" ? "Inherit" : action.delay,
spawnInterval:
actionType === "Spawn"
? "Inherit"
: action.spawnInterval,
}
: action
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
// Update the selected item to reflect changes
if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) {
const updatedAction = updatedPaths
.filter(
(path): path is SimulationTypes.ConveyorEventsSchema => path.type === "Conveyor"
)
.flatMap((path) => path.points)
.find((p) => p.uuid === selectedActionSphere.points.uuid)
?.actions.find((a) => a.uuid === uuid);
if (updatedAction) {
setSelectedItem({
type: "action",
item: updatedAction,
});
}
}
};
// Modified handleMaterialSelect to ensure it only applies to relevant action types
const handleMaterialSelect = (uuid: string, material: string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
actions: point.actions.map((action) =>
action.uuid === uuid &&
(action.type === "Spawn" || action.type === "Swap")
? { ...action, material }
: action
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
// Update selected item if it's the current action
if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) {
setSelectedItem({
...selectedItem,
item: {
...selectedItem.item,
material,
},
});
}
};
const handleDelayChange = (uuid: string, delay: number | string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
actions: point.actions.map((action) =>
action.uuid === uuid ? { ...action, delay } : action
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
};
const handleSpawnIntervalChange = (
uuid: string,
spawnInterval: number | string
) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
actions: point.actions.map((action) =>
action.uuid === uuid
? { ...action, spawnInterval }
: action
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
};
const handleSpeedChange = (speed: number | string) => {
if (!selectedPath) return;
const updatedPaths = simulationStates.map((path) =>
path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.modeluuid === selectedPath.path.modeluuid
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } });
};
const handleAddTrigger = () => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) => {
if (point.uuid === selectedActionSphere.points.uuid) {
const triggerIndex = point.triggers.length;
const newTrigger = {
uuid: THREE.MathUtils.generateUUID(),
name: `Trigger ${triggerIndex + 1}`,
type: "",
bufferTime: 0,
isUsed: false,
};
return { ...point, triggers: [...point.triggers, newTrigger] };
}
return point;
}),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
};
const handleDeleteTrigger = (uuid: string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
triggers: point.triggers.filter(
(trigger) => trigger.uuid !== uuid
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
};
const handleTriggerSelect = (uuid: string, triggerType: string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
triggers: point.triggers.map((trigger) =>
trigger.uuid === uuid
? { ...trigger, type: triggerType }
: trigger
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
// Ensure the selectedItem is updated immediately
const updatedTrigger = updatedPaths
.flatMap((path) => (path.type === "Conveyor" ? path.points : []))
.flatMap((point) => point.triggers)
.find((trigger) => trigger.uuid === uuid);
if (updatedTrigger) {
setSelectedItem({ type: "trigger", item: updatedTrigger });
}
};
// Update the toggle handlers to immediately update the selected item
const handleActionToggle = (uuid: string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
actions: point.actions.map((action) => ({
...action,
isUsed: action.uuid === uuid ? !action.isUsed : false,
})),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
// Immediately update the selected item if it's the one being toggled
if (selectedItem?.type === "action" && selectedItem.item.uuid === uuid) {
setSelectedItem({
...selectedItem,
item: {
...selectedItem.item,
isUsed: !selectedItem.item.isUsed,
},
});
}
};
// Do the same for trigger toggle
const handleTriggerToggle = (uuid: string) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
triggers: point.triggers.map((trigger) => ({
...trigger,
isUsed: trigger.uuid === uuid ? !trigger.isUsed : false,
})),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
// Immediately update the selected item if it's the one being toggled
if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) {
setSelectedItem({
...selectedItem,
item: {
...selectedItem.item,
isUsed: !selectedItem.item.isUsed,
},
});
}
};
const handleTriggerBufferTimeChange = (uuid: string, bufferTime: number) => {
if (!selectedActionSphere) return;
const updatedPaths = simulationStates.map((path) =>
path.type === "Conveyor"
? {
...path,
points: path.points.map((point) =>
point.uuid === selectedActionSphere.points.uuid
? {
...point,
triggers: point.triggers.map((trigger) =>
trigger.uuid === uuid
? { ...trigger, bufferTime }
: trigger
),
}
: point
),
}
: path
);
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.ConveyorEventsSchema =>
path.type === "Conveyor" &&
path.points.some(
(point) => point.uuid === selectedActionSphere.points.uuid
)
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
// Immediately update selectedItem if it's the currently selected trigger
if (selectedItem?.type === "trigger" && selectedItem.item.uuid === uuid) {
setSelectedItem({
...selectedItem,
item: {
...selectedItem.item,
bufferTime,
},
});
}
};
const [selectedItem, setSelectedItem] = useState<{
type: "action" | "trigger";
item: any;
} | null>(null);
useEffect(() => {
setSelectedItem(null);
}, [selectedActionSphere]);
return (
<div className="machine-mechanics-container">
{!selectedPath && (
<div className="machine-mechanics-header">
{selectedActionSphere?.path?.modelName || "point name not found"}
</div>
)}
{selectedPath && (
<div className="machine-mechanics-header">
{selectedPath.path.modelName || "path name not found"}
</div>
)}
<div className="machine-mechanics-content-container">
{!selectedPath && (
<>
<div className="actions">
<div className="header">
<div className="header-value">Actions</div>
<div className="add-button" onClick={handleAddAction}>
<AddIcon /> Add
</div>
</div>
<div
className="lists-main-container"
ref={actionsContainerRef}
style={{ height: "120px" }}
>
<div className="list-container">
{selectedPoint?.actions.map((action) => (
<div
key={action.uuid}
className={`list-item ${selectedItem?.type === "action" &&
selectedItem.item?.uuid === action.uuid
? "active"
: ""
}`}
>
<div
className="value"
onClick={() =>
setSelectedItem({ type: "action", item: action })
}
>
<input type="radio" name="action" id="action" checked={action.isUsed} readOnly />
<RenameInput value={action.name} />
</div>
<div
className="remove-button"
onClick={() => handleDeleteAction(action.uuid)}
>
<RemoveIcon />
</div>
</div>
))}
</div>
<div
className="resize-icon"
id="action-resize"
onMouseDown={(e) => handleResize(e, actionsContainerRef)}
>
<ResizeHeightIcon />
</div>
</div>
</div>
<div className="triggers">
<div className="header">
<div className="header-value">Triggers</div>
<div className="add-button" onClick={handleAddTrigger}>
<AddIcon /> Add
</div>
</div>
<div
className="lists-main-container"
ref={triggersContainerRef}
style={{ height: "120px" }}
>
<div className="list-container">
{selectedPoint?.triggers.map((trigger) => (
<div
key={trigger.uuid}
className={`list-item ${selectedItem?.type === "trigger" &&
selectedItem.item?.uuid === trigger.uuid
? "active"
: ""
}`}
>
<div
className="value"
onClick={() =>
setSelectedItem({ type: "trigger", item: trigger })
}
>
<input type="radio" name="trigger" id="trigger" checked={trigger.isUsed} readOnly />
<RenameInput value={trigger.name} />
</div>
<div
className="remove-button"
onClick={() => handleDeleteTrigger(trigger.uuid)}
>
<RemoveIcon />
</div>
</div>
))}
</div>
<div
className="resize-icon"
id="trigger-resize"
onMouseDown={(e) => handleResize(e, triggersContainerRef)}
>
<ResizeHeightIcon />
</div>
</div>
</div>
</>
)}
<div className="selected-properties-container">
{selectedItem && (
<>
<div className="properties-header">{selectedItem.item.name}</div>
{selectedItem.type === "action" && (
<>
<InputToggle
inputKey="enableAction"
label="Enable Action"
value={selectedItem.item.isUsed}
onClick={() => handleActionToggle(selectedItem.item.uuid)}
/>
<LabledDropdown
defaultOption={selectedItem.item.type}
options={["Inherit", "Spawn", "Swap", "Despawn", "Delay"]}
onSelect={(option) =>
handleActionSelect(selectedItem.item.uuid, option)
}
/>
{/* Only show material dropdown for Spawn/Swap actions */}
{(selectedItem.item.type === "Spawn" ||
selectedItem.item.type === "Swap") && (
<LabledDropdown
label={
selectedItem.item.type === "Spawn"
? "Spawn Material"
: "Swap Material"
}
defaultOption={selectedItem.item.material}
options={["Inherit", "Crate", "Box"]}
onSelect={(option) =>
handleMaterialSelect(selectedItem.item.uuid, option)
}
/>
)}
{/* Only show delay input for Delay actions */}
{selectedItem.item.type === "Delay" && (
<InputWithDropDown
label="Delay Time"
value={
selectedItem.item.delay === "Inherit"
? undefined
: selectedItem.item.delay
}
onChange={(value) => {
const numValue = parseInt(value);
handleDelayChange(
selectedItem.item.uuid,
!value ? "Inherit" : numValue
);
}}
/>
)}
{/* Only show spawn interval for Spawn actions */}
{selectedItem.item.type === "Spawn" && (
<InputWithDropDown
label="Spawn Interval"
min={0}
defaultValue={
selectedItem.item.spawnInterval === "Inherit"
? ""
: selectedItem.item.spawnInterval.toString()
}
value={
selectedItem.item.spawnInterval === "Inherit"
? ""
: selectedItem.item.spawnInterval.toString()
}
onChange={(value) => {
handleSpawnIntervalChange(
selectedItem.item.uuid,
value === "" ? "Inherit" : parseInt(value)
);
}}
/>
)}
</>
)}
{selectedItem.type === "trigger" && (
<>
<InputToggle
inputKey="enableTrigger"
label="Enable Trigger"
value={selectedItem.item.isUsed}
onClick={() => handleTriggerToggle(selectedItem.item.uuid)}
/>
<LabledDropdown
defaultOption={
selectedItem.item.type || "Select Trigger Type"
}
options={["On-Hit", "Buffer"]}
onSelect={(option) =>
handleTriggerSelect(selectedItem.item.uuid, option)
}
/>
{selectedItem.item.type === "Buffer" && (
<InputWithDropDown
label="Buffer Time"
value={selectedItem.item.bufferTime.toString()}
onChange={(value) => {
handleTriggerBufferTimeChange(
selectedItem.item.uuid,
parseInt(value)
);
}}
/>
)}
</>
)}
</>
)}
{selectedPath && !selectedItem && (
<div
key={selectedPath?.path.modeluuid || "none"}
className="speed-control"
>
<InputWithDropDown
label="Conveyor Speed"
min={0}
value={
selectedPath.path.speed === "Inherit"
? ""
: selectedPath.path.speed.toString()
}
onChange={(value) =>
handleSpeedChange(value === "" ? "Inherit" : parseInt(value))
}
/>
</div>
)}
</div>
{!selectedPath && (
<div className="footer">
<InfoIcon />
Configure the point's action and trigger properties.
</div>
)}
{selectedPath && (
<div className="footer">
<InfoIcon />
Configure the path properties.
</div>
)}
</div>
</div>
);
};
export default ConveyorMechanics;

View File

@@ -1,188 +0,0 @@
import React, { useRef, useMemo, useCallback } from "react";
import { InfoIcon } from "../../../icons/ExportCommonIcons";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store";
import * as SimulationTypes from '../../../../types/simulationTypes';
import LabledDropdown from "../../../ui/inputs/LabledDropdown";
import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt";
const StaticMachineMechanics: React.FC = () => {
const { selectedActionSphere } = useSelectedActionSphere();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { socket } = useSocketStore();
const propertiesContainerRef = useRef<HTMLDivElement>(null);
const { selectedPoint, connectedPointUuids } = useMemo(() => {
if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] };
const staticMachinePaths = simulationStates.filter(
(path): path is SimulationTypes.StaticMachineEventsSchema => path.type === "StaticMachine"
);
const points = staticMachinePaths.find(
(path) => path.points.uuid === selectedActionSphere.points.uuid
)?.points;
if (!points) return { selectedPoint: null, connectedPointUuids: [] };
const connectedUuids: string[] = [];
if (points.connections?.targets) {
points.connections.targets.forEach(target => {
connectedUuids.push(target.pointUUID);
});
}
return {
selectedPoint: points,
connectedPointUuids: connectedUuids
};
}, [selectedActionSphere, simulationStates]);
const updateBackend = async (updatedPath: SimulationTypes.StaticMachineEventsSchema | undefined) => {
if (!updatedPath) return;
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "";
// await setEventApi(
// organization,
// updatedPath.modeluuid,
// { type: "Vehicle", points: updatedPath.points }
// );
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: { type: "StaticMachine", points: updatedPath.points }
}
socket.emit('v2:model-asset:updateEventData', data);
}
const handleActionUpdate = useCallback((updatedAction: Partial<SimulationTypes.StaticMachineEventsSchema['points']['actions']>) => {
if (!selectedActionSphere?.points?.uuid) return;
const updatedPaths = simulationStates.map((path) => {
if (path.type === "StaticMachine" && path.points.uuid === selectedActionSphere.points.uuid) {
return {
...path,
points: {
...path.points,
actions: {
...path.points.actions,
...updatedAction
}
}
};
}
return path;
});
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.StaticMachineEventsSchema =>
path.type === "StaticMachine" &&
path.points.uuid === selectedActionSphere.points.uuid
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
}, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]);
const handleBufferChange = useCallback((buffer: number) => {
handleActionUpdate({ buffer });
}, [handleActionUpdate]);
const handleMaterialChange = useCallback((material: string) => {
handleActionUpdate({ material });
}, [handleActionUpdate]);
const handleTriggerChange = useCallback((updatedTrigger: Partial<SimulationTypes.StaticMachineEventsSchema['points']['triggers']>) => {
if (!selectedActionSphere?.points?.uuid) return;
const updatedPaths = simulationStates.map((path) => {
if (path.type === "StaticMachine" && path.points.uuid === selectedActionSphere.points.uuid) {
return {
...path,
points: {
...path.points,
triggers: {
...path.points.triggers,
...updatedTrigger
}
}
};
}
return path;
});
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.StaticMachineEventsSchema =>
path.type === "StaticMachine" &&
path.points.uuid === selectedActionSphere.points.uuid
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
}, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]);
const handleTriggerTypeChange = useCallback((type: string) => {
handleTriggerChange({ type });
}, [handleTriggerChange]);
return (
<div className="machine-mechanics-container" key={selectedPoint?.uuid}>
<div className="machine-mechanics-header">
{selectedActionSphere?.path?.modelName || "Machine point not found"}
</div>
<div className="machine-mechanics-content-container">
<div className="selected-properties-container" ref={propertiesContainerRef}>
<div className="properties-header">Machine Properties</div>
{selectedPoint && (
<>
<InputWithDropDown
key={`buffer-${selectedPoint.uuid}`}
label="Buffer Time"
value={selectedPoint.actions.buffer.toString()}
onChange={(value) => handleBufferChange(parseInt(value))}
/>
<LabledDropdown
key={`material-${selectedPoint.uuid}`}
label="Material"
defaultOption={selectedPoint.actions.material}
onSelect={(value) => handleMaterialChange(value)}
options={["Inherit", "Crate", "Box"]}
/>
<LabledDropdown
key={`trigger-type-${selectedPoint.uuid}`}
label="Trigger Type"
defaultOption={selectedPoint.triggers.type}
onSelect={(value) => handleTriggerTypeChange(value)}
options={["OnComplete", "OnStart"]}
/>
{/* <LabeledButton
label="Reset"
value="Reset Settings"
onClick={() => {
// Implement reset functionality if needed
}}
/> */}
</>
)}
</div>
<div className="footer">
<InfoIcon />
Configure machine interaction properties and triggers.
</div>
</div>
</div>
);
};
export default React.memo(StaticMachineMechanics);

View File

@@ -1,265 +0,0 @@
import React, { useRef, useMemo } from "react";
import { InfoIcon } from "../../../icons/ExportCommonIcons";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { useEditingPoint, useEyeDropMode, usePreviewPosition, useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store";
import * as SimulationTypes from '../../../../types/simulationTypes';
import PositionInput from "../customInput/PositionInputs";
import { setEventApi } from "../../../../services/factoryBuilder/assest/floorAsset/setEventsApt";
import LabeledButton from "../../../ui/inputs/LabledButton";
const VehicleMechanics: React.FC = () => {
const { selectedActionSphere } = useSelectedActionSphere();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { eyeDropMode, setEyeDropMode } = useEyeDropMode();
const { editingPoint, setEditingPoint } = useEditingPoint();
const { previewPosition, setPreviewPosition } = usePreviewPosition();
const { socket } = useSocketStore();
const propertiesContainerRef = useRef<HTMLDivElement>(null);
const { selectedPoint, connectedPointUuids } = useMemo(() => {
if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null, connectedPointUuids: [] };
const vehiclePaths = simulationStates.filter(
(path): path is SimulationTypes.VehicleEventsSchema => path.type === "Vehicle"
);
const points = vehiclePaths.find(
(path) => path.points.uuid === selectedActionSphere.points.uuid
)?.points;
if (!points) return { selectedPoint: null, connectedPointUuids: [] };
const connectedUuids: string[] = [];
if (points.connections?.targets) {
points.connections.targets.forEach(target => {
connectedUuids.push(target.pointUUID);
});
}
return {
selectedPoint: points,
connectedPointUuids: connectedUuids
};
}, [selectedActionSphere, simulationStates]);
const updateBackend = async (updatedPath: SimulationTypes.VehicleEventsSchema | undefined) => {
if (!updatedPath) return;
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "";
// await setEventApi(
// organization,
// updatedPath.modeluuid,
// { type: "Vehicle", points: updatedPath.points }
// );
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: { type: "Vehicle", points: updatedPath.points }
}
socket.emit('v2:model-asset:updateEventData', data);
}
const handleActionUpdate = React.useCallback((updatedAction: Partial<SimulationTypes.VehicleEventsSchema['points']['actions']>) => {
if (!selectedActionSphere?.points?.uuid) return;
const updatedPaths = simulationStates.map((path) => {
if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) {
return {
...path,
points: {
...path.points,
actions: {
...path.points.actions,
...updatedAction
}
}
};
}
return path;
});
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.VehicleEventsSchema =>
path.type === "Vehicle" &&
path.points.uuid === selectedActionSphere.points.uuid
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
}, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]);
const handleHitCountChange = React.useCallback((hitCount: number) => {
handleActionUpdate({ hitCount });
}, [handleActionUpdate]);
const handleBufferChange = React.useCallback((buffer: number) => {
handleActionUpdate({ buffer });
}, [handleActionUpdate]);
const handleSpeedChange = React.useCallback((speed: number) => {
if (!selectedActionSphere?.points?.uuid) return;
const updatedPaths = simulationStates.map((path) => {
if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) {
return {
...path,
points: {
...path.points,
speed: speed
}
};
}
return path;
});
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.VehicleEventsSchema =>
path.type === "Vehicle" &&
path.points.uuid === selectedActionSphere.points.uuid
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
}, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]);
const ResetVehicleState = React.useCallback(() => {
if (!selectedActionSphere?.points?.uuid) return;
const updatedPaths = simulationStates.map((state) => {
if (state.type === "Vehicle" && state.points.uuid === selectedActionSphere.points.uuid) {
return {
...state,
points: {
...state.points,
actions: { ...state.points.actions, start: {}, end: {} }
}
};
}
return state;
});
const updatedPath = updatedPaths.find(
(path): path is SimulationTypes.VehicleEventsSchema =>
path.type === "Vehicle" &&
path.points.uuid === selectedActionSphere.points.uuid
);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
}, [selectedActionSphere?.points?.uuid, simulationStates, setSimulationStates]);
const handleStartEyeDropClick = () => {
setEditingPoint('start');
setEyeDropMode(true);
};
const handleEndEyeDropClick = () => {
setEditingPoint('end');
setEyeDropMode(true);
};
return (
<div className="machine-mechanics-container" key={selectedPoint?.uuid}>
<div className="machine-mechanics-header">
{selectedActionSphere?.path?.modelName || "Vehicle point not found"}
</div>
<div className="machine-mechanics-content-container">
<div className="selected-properties-container" ref={propertiesContainerRef}>
<div className="properties-header">Vehicle Properties</div>
{selectedPoint && (
<>
<PositionInput
label="Start Point"
onChange={() => { }}
disabled={true}
value1={
editingPoint === 'start' && previewPosition
? parseFloat(previewPosition.x.toFixed(4))
: selectedPoint.actions.start && 'x' in selectedPoint.actions.start
? parseFloat(selectedPoint.actions.start.x.toFixed(4))
: 0
}
value2={
editingPoint === 'start' && previewPosition
? parseFloat(previewPosition.y.toFixed(4))
: selectedPoint.actions.start && 'y' in selectedPoint.actions.start
? parseFloat(selectedPoint.actions.start.y.toFixed(4))
: 0
}
isEyedrop={true}
handleEyeDropClick={handleStartEyeDropClick}
/>
<PositionInput
label="End Point"
onChange={() => { }}
disabled={true}
value1={
editingPoint === 'end' && previewPosition
? parseFloat(previewPosition.x.toFixed(4))
: selectedPoint.actions.end && 'x' in selectedPoint.actions.end
? parseFloat(selectedPoint.actions.end.x.toFixed(4))
: 0
}
value2={
editingPoint === 'end' && previewPosition
? parseFloat(previewPosition.y.toFixed(4))
: selectedPoint.actions.end && 'y' in selectedPoint.actions.end
? parseFloat(selectedPoint.actions.end.y.toFixed(4))
: 0
}
isEyedrop={true}
handleEyeDropClick={handleEndEyeDropClick}
/>
<LabeledButton
label="Reset"
value="Clear Points"
onClick={() => {
ResetVehicleState();
}}
/>
<InputWithDropDown
key={`hitcount-${selectedPoint.uuid}`}
label="Hit Count"
value={selectedPoint.actions.hitCount.toString()}
onChange={(value) => handleHitCountChange(parseInt(value))}
/>
<InputWithDropDown
key={`buffer-${selectedPoint.uuid}`}
label="Buffer Time"
value={selectedPoint.actions.buffer.toString()}
onChange={(value) => handleBufferChange(parseInt(value))}
/>
<InputWithDropDown
key={`speed-${selectedPoint.uuid}`}
label="Vehicle Speed"
value={selectedPoint.speed.toString()}
onChange={(value) => handleSpeedChange(parseFloat(value))}
/>
</>
)}
</div>
<div className="footer">
<InfoIcon />
Configure vehicle's movement and interaction properties.
</div>
</div>
</div>
);
};
export default React.memo(VehicleMechanics);

View File

@@ -3,7 +3,7 @@ import RenameInput from "../../../ui/inputs/RenameInput";
import Vector3Input from "../customInput/Vector3Input";
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
import { useEditPosition, usezonePosition, useZones, usezoneTarget } from "../../../../store/store";
import { zoneCameraUpdate } from "../../../../services/realTimeVisulization/zoneData/zoneCameraUpdation";
import { zoneCameraUpdate } from "../../../../services/visulization/zone/zoneCameraUpdation";
const ZoneProperties: React.FC = () => {
const { Edit, setEdit } = useEditPosition();

View File

@@ -1,9 +1,9 @@
import { useState, useEffect, useRef } from "react";
import { useWidgetStore } from "../../../../../store/useWidgetStore";
import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent";
import ChartComponent from "../../../sidebarLeft//visualization/widgets/ChartComponent";
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
import { WalletIcon } from "../../../../icons/3dChartIcons";
import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard";
import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard";
interface Widget {
id: string;

View File

@@ -15,7 +15,7 @@ import {
} from "../icons/ExportToolsIcons";
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
import { handleSaveTemplate } from "../../modules/visualization/functions/handleSaveTemplate";
import { handleSaveTemplate } from "../../modules//visualization/functions/handleSaveTemplate";
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
import useTemplateStore from "../../store/useTemplateStore";
import { useSelectedZoneStore } from "../../store/useZoneStore";

View File

@@ -4,7 +4,6 @@ import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons";
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
import { useFloorItems, useZones } from "../../../store/store";
import { useSelectedZoneStore } from "../../../store/useZoneStore";
import { getZone2dData } from "../../../services/realTimeVisulization/zoneData/getZoneData";
interface DropDownListProps {
value?: string; // Value to display in the DropDownList

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import RenameInput from "../inputs/RenameInput";
import { useSelectedZoneStore } from "../../../store/useZoneStore";
import { getZoneData } from "../../../services/realTimeVisulization/zoneData/getZones";
import { getZoneData } from "../../../services/visulization/zone/getZones";
import useModuleStore, {
useSubModuleStore,
} from "../../../store/useModuleStore";
@@ -14,7 +14,7 @@ import {
} from "../../icons/ExportCommonIcons";
import { useThree } from "@react-three/fiber";
import { useFloorItems, useZoneAssetId, useZones } from "../../../store/store";
import { zoneCameraUpdate } from "../../../services/realTimeVisulization/zoneData/zoneCameraUpdation";
import { zoneCameraUpdate } from "../../../services/visulization/zone/zoneCameraUpdation";
import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
interface Asset {

View File

@@ -5,7 +5,6 @@ import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
import { toast } from 'react-toastify';
import * as Types from "../../../types/world/worldTypes";
import * as SimulationTypes from "../../../types/simulationTypes";
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
@@ -13,7 +12,6 @@ import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAss
async function loadInitialFloorItems(
itemsGroup: Types.RefGroup,
setFloorItems: Types.setFloorItemSetState,
setSimulationStates: (paths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => void
): Promise<void> {
if (!itemsGroup.current) return;
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
@@ -27,7 +25,7 @@ async function loadInitialFloorItems(
if (items.message === "floorItems not found") return;
if (items) {
const storedFloorItems: SimulationTypes.EventData[] = items;
const storedFloorItems: Types.FloorItems = items;
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
@@ -72,7 +70,7 @@ async function loadInitialFloorItems(
const cachedModel = THREE.Cache.get(item.modelfileID!);
if (cachedModel) {
// console.log(`[Cache] Fetching ${item.modelname}`);
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
return;
@@ -87,7 +85,7 @@ async function loadInitialFloorItems(
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(item.modelfileID!, gltf);
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
},
@@ -108,7 +106,7 @@ async function loadInitialFloorItems(
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
await storeGLTF(item.modelfileID!, modelBlob);
THREE.Cache.add(item.modelfileID!, gltf);
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
},
@@ -134,10 +132,6 @@ async function loadInitialFloorItems(
},
]);
if (item.eventData) {
processEventData(item, setSimulationStates);
}
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
}
@@ -151,10 +145,9 @@ async function loadInitialFloorItems(
function processLoadedModel(
gltf: any,
item: SimulationTypes.EventData,
item: Types.FloorItemType,
itemsGroup: Types.RefGroup,
setFloorItems: Types.setFloorItemSetState,
setSimulationStates: (paths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => void
) {
const model = gltf;
model.uuid = item.modeluuid;
@@ -189,71 +182,10 @@ function processLoadedModel(
},
]);
if (item.eventData) {
processEventData(item, setSimulationStates);
}
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
}
function processEventData(item: SimulationTypes.EventData, setSimulationStates: any) {
if (item.eventData?.type === 'Conveyor') {
const data: any = item.eventData;
data.modeluuid = item.modeluuid;
data.modelName = item.modelname;
data.position = item.position;
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
data as SimulationTypes.ConveyorEventsSchema
]);
} else if (item.eventData?.type === 'Vehicle') {
const data: any = item.eventData;
data.modeluuid = item.modeluuid;
data.modelName = item.modelname;
data.position = item.position;
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
data as SimulationTypes.VehicleEventsSchema
]);
} else if (item.eventData?.type === 'StaticMachine') {
const data: any = item.eventData;
data.modeluuid = item.modeluuid;
data.modelName = item.modelname;
data.position = item.position;
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
data as SimulationTypes.StaticMachineEventsSchema
]);
} else if (item.eventData?.type === 'ArmBot') {
const data: any = item.eventData;
data.modeluuid = item.modeluuid;
data.modelName = item.modelname;
data.position = item.position;
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
data as SimulationTypes.ArmBotEventsSchema
]);
}
}
function checkLoadingCompletion(
modelsLoaded: number,
modelsToLoad: number,

View File

@@ -1,109 +0,0 @@
import { useEffect, useRef, useState } from "react";
import { Line } from "@react-three/drei";
import {
useNavMesh,
usePlayAgv,
useSimulationStates,
} from "../../../store/store";
import PathNavigator from "./pathNavigator";
import { useAnimationPlaySpeed, usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore";
type PathPoints = {
modelUuid: string;
modelSpeed: number;
bufferTime: number;
points: { x: number; y: number; z: number }[];
hitCount: number;
};
interface ProcessContainerProps {
processes: any[];
agvRef: any;
MaterialRef: any;
}
const Agv: React.FC<ProcessContainerProps> = ({
processes,
agvRef,
MaterialRef,
}) => {
const [pathPoints, setPathPoints] = useState<PathPoints[]>([]);
const { simulationStates } = useSimulationStates();
const { navMesh } = useNavMesh();
const { isPlaying } = usePlayButtonStore();
const { isReset, setReset } = useResetButtonStore();
const { speed } = useAnimationPlaySpeed();
const globalSpeed = useRef(1);
useEffect(() => { globalSpeed.current = speed }, [speed])
useEffect(() => {
if (!isPlaying || isReset) {
agvRef.current = [];
}
}, [isPlaying, isReset])
useEffect(() => {
if (simulationStates.length > 0) {
const agvModels = simulationStates.filter(
(val) => val.modelName === "agv" && val.type === "Vehicle"
);
const newPathPoints = agvModels
.filter(
(model: any) =>
model.points &&
model.points.actions &&
typeof model.points.actions.start === "object" &&
typeof model.points.actions.end === "object" &&
"x" in model.points.actions.start &&
"y" in model.points.actions.start &&
"x" in model.points.actions.end &&
"y" in model.points.actions.end
)
.map((model: any) => ({
modelUuid: model.modeluuid,
modelSpeed: model.points.speed,
bufferTime: model.points.actions.buffer,
hitCount: model.points.actions.hitCount,
points: [
{ x: model.position[0], y: model.position[1], z: model.position[2], },
{ x: model.points.actions.start.x, y: 0, z: model.points.actions.start.y, },
{ x: model.points.actions.end.x, y: 0, z: model.points.actions.end.y, },
],
}));
setPathPoints(newPathPoints);
}
}, [simulationStates]);
return (
<>
{pathPoints.map((pair, i) => (
<group key={i}>
<PathNavigator
key={i}
navMesh={navMesh}
pathPoints={pair.points}
id={pair.modelUuid}
speed={pair.modelSpeed}
globalSpeed={globalSpeed.current}
bufferTime={pair.bufferTime}
hitCount={pair.hitCount}
processes={processes}
agvRef={agvRef}
MaterialRef={MaterialRef}
/>
{pair.points.slice(1).map((point, idx) => (
<mesh position={[point.x, point.y, point.z]} key={idx} visible={!isPlaying}>
<sphereGeometry args={[0.3, 15, 15]} />
<meshStandardMaterial color="red" />
</mesh>
))}
</group>
))}
</>
);
};
export default Agv;

View File

@@ -1,31 +0,0 @@
import { useRef } from "react";
import { useNavMesh } from "../../../store/store";
import PolygonGenerator from "./polygonGenerator";
import NavMeshDetails from "./navMeshDetails";
import * as CONSTANTS from "../../../types/world/worldConstants";
import * as Types from "../../../types/world/worldTypes";
type NavMeshCreatorProps = {
lines: Types.RefLines
};
function NavMeshCreator({ lines }: NavMeshCreatorProps) {
let groupRef = useRef() as Types.RefGroup;
const { setNavMesh } = useNavMesh();
return (
<>
<PolygonGenerator groupRef={groupRef} lines={lines} />
<NavMeshDetails lines={lines} setNavMesh={setNavMesh} groupRef={groupRef} />
<group ref={groupRef} visible={false} name="Meshes">
<mesh rotation-x={CONSTANTS.planeConfig.rotation} position={CONSTANTS.planeConfig.position3D} receiveShadow>
<planeGeometry args={[300, 300]} />
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
</mesh>
</group>
</>
)
}
export default NavMeshCreator

View File

@@ -1,64 +0,0 @@
import React, { useEffect, useState } from "react";
import { init as initRecastNavigation } from "@recast-navigation/core";
import { generateSoloNavMesh } from "@recast-navigation/generators";
import { DebugDrawer, getPositionsAndIndices } from "@recast-navigation/three";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import * as Types from "../../../types/world/worldTypes";
interface NavMeshDetailsProps {
setNavMesh: (navMesh: any) => void;
groupRef: React.MutableRefObject<THREE.Group | null>;
lines: Types.RefLines;
}
export default function NavMeshDetails({
lines,
setNavMesh,
groupRef,
}: NavMeshDetailsProps) {
const { scene } = useThree();
useEffect(() => {
const initializeNavigation = async () => {
try {
await initRecastNavigation();
if (!groupRef.current || groupRef.current.children.length === 0) {
return;
}
const meshes = groupRef?.current?.children as THREE.Mesh[];
const [positions, indices] = getPositionsAndIndices(meshes);
const cellSize = 0.2;
const cellHeight = 0.7;
const walkableRadius = 0.5;
const { success, navMesh } = generateSoloNavMesh(positions, indices, {
cs: cellSize,
ch: cellHeight,
walkableRadius: Math.round(walkableRadius / cellHeight),
});
if (!success || !navMesh) {
return;
}
setNavMesh(navMesh);
scene.children
.filter((child) => child instanceof DebugDrawer)
.forEach((child) => scene.remove(child));
const debugDrawer = new DebugDrawer();
debugDrawer.drawNavMesh(navMesh);
// scene.add(debugDrawer);
} catch (error) { }
};
initializeNavigation();
}, [scene, groupRef, lines.current]);
return null;
}

View File

@@ -1,480 +0,0 @@
import React, { useEffect, useState, useRef, useMemo } from "react";
import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { NavMeshQuery } from "@recast-navigation/core";
import { Line } from "@react-three/drei";
import {
useAnimationPlaySpeed,
usePlayButtonStore,
} from "../../../store/usePlayButtonStore";
import { usePlayAgv } from "../../../store/store";
interface PathNavigatorProps {
navMesh: any;
pathPoints: any;
id: string;
speed: number;
globalSpeed: number;
bufferTime: number;
hitCount: number;
processes: any[];
agvRef: any;
MaterialRef: any;
}
interface AGVData {
processId: string;
vehicleId: string;
hitCount: number;
totalHits: number;
}
type Phase = "initial" | "toDrop" | "toPickup";
type MaterialType = "Box" | "Crate";
export default function PathNavigator({
navMesh,
pathPoints,
id,
speed,
globalSpeed,
bufferTime,
hitCount,
processes,
agvRef,
MaterialRef,
}: PathNavigatorProps) {
const [currentPhase, setCurrentPhase] = useState<Phase>("initial");
const [path, setPath] = useState<[number, number, number][]>([]);
const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>(
[]
);
const [pickupDropPath, setPickupDropPath] = useState<
[number, number, number][]
>([]);
const [dropPickupPath, setDropPickupPath] = useState<
[number, number, number][]
>([]);
const [initialPosition, setInitialPosition] = useState<THREE.Vector3 | null>(
null
);
const [initialRotation, setInitialRotation] = useState<THREE.Euler | null>(
null
);
const [boxVisible, setBoxVisible] = useState(false);
const distancesRef = useRef<number[]>([]);
const totalDistanceRef = useRef(0);
const progressRef = useRef(0);
const isWaiting = useRef(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const hasStarted = useRef(false);
const hasReachedPickup = useRef(false);
const { scene } = useThree();
const { isPlaying } = usePlayButtonStore();
const { PlayAgv, setPlayAgv } = usePlayAgv();
const boxRef = useRef<THREE.Mesh | null>(null);
const baseMaterials = useMemo(
() => ({
Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
Default: new THREE.MeshStandardMaterial({ color: 0xcccccc }),
}),
[]
);
useEffect(() => {
const object = scene.getObjectByProperty("uuid", id);
if (object) {
setInitialPosition(object.position.clone());
setInitialRotation(object.rotation.clone());
}
}, [scene, id]);
const computePath = (start: any, end: any) => {
try {
const navMeshQuery = new NavMeshQuery(navMesh);
const { path: segmentPath } = navMeshQuery.computePath(start, end);
return (
segmentPath?.map(
({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]
) || []
);
} catch {
return [];
}
};
const resetState = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
setPath([]);
setCurrentPhase("initial");
setToPickupPath([]);
setPickupDropPath([]);
setDropPickupPath([]);
setBoxVisible(false);
distancesRef.current = [];
totalDistanceRef.current = 0;
progressRef.current = 0;
isWaiting.current = false;
hasStarted.current = false;
hasReachedPickup.current = false;
if (initialPosition && initialRotation) {
const object = scene.getObjectByProperty("uuid", id);
if (object) {
object.position.copy(initialPosition);
object.rotation.copy(initialRotation);
}
}
};
useEffect(() => {
if (!isPlaying) {
resetState();
}
if (!navMesh || pathPoints.length < 2) return;
const [pickup, drop] = pathPoints.slice(-2);
const object = scene.getObjectByProperty("uuid", id);
if (!object) return;
const currentPosition = object.position;
const toPickupPath = computePath(currentPosition, pickup);
const pickupToDropPath = computePath(pickup, drop);
const dropToPickupPath = computePath(drop, pickup);
if (
toPickupPath.length &&
pickupToDropPath.length &&
dropToPickupPath.length
) {
setPickupDropPath(pickupToDropPath);
setDropPickupPath(dropToPickupPath);
setToPickupPath(toPickupPath);
setPath(toPickupPath);
setCurrentPhase("initial");
}
}, [navMesh, pathPoints, hitCount, isPlaying, PlayAgv]);
useEffect(() => {
if (path.length < 2) return;
let total = 0;
const segmentDistances = path.slice(0, -1).map((point, i) => {
const dist = new THREE.Vector3(...point).distanceTo(
new THREE.Vector3(...path[i + 1])
);
total += dist;
return dist;
});
distancesRef.current = segmentDistances;
totalDistanceRef.current = total;
progressRef.current = 0;
isWaiting.current = false;
}, [path]);
function logAgvStatus(id: string, status: string) {
// console.log(
// `AGV ${id}: ${status}`
// );
}
function findProcessByTargetModelUUID(processes: any, targetModelUUID: any) {
for (const process of processes) {
for (const path of process.paths) {
for (const point of path.points) {
if (
point.connections?.targets?.some(
(target: any) => target.modelUUID === targetModelUUID
)
) {
return process.id;
}
}
}
}
return null;
}
useEffect(() => {
if (!scene || !boxRef || !processes || !MaterialRef.current) return;
const existingObject = scene.getObjectByProperty("uuid", id);
if (!existingObject) return;
if (boxRef.current?.parent) {
boxRef.current.parent.remove(boxRef.current);
boxRef.current = null;
}
if (boxVisible) {
const matchedProcess = findProcessByTargetModelUUID(processes, id);
let materialType: "Box" | "Crate" | "Default" = "Default";
if (matchedProcess) {
const materialEntry = MaterialRef.current.find((item: any) =>
item.objects.some((obj: any) => obj.processId === matchedProcess)
);
if (materialEntry) {
materialType = materialEntry.material;
}
}
const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const boxMesh = new THREE.Mesh(boxGeometry, baseMaterials[materialType]);
boxMesh.position.y = 1;
boxMesh.name = `box-${id}`;
existingObject.add(boxMesh);
boxRef.current = boxMesh;
}
return () => {
if (boxRef.current?.parent) {
boxRef.current.parent.remove(boxRef.current);
}
};
}, [processes, MaterialRef, boxVisible, scene, id, baseMaterials]);
useFrame((_, delta) => {
const currentAgv = (agvRef.current || []).find(
(agv: AGVData) => agv.vehicleId === id
);
if (!scene || !id || !isPlaying) return;
const object = scene.getObjectByProperty("uuid", id);
if (!object) return;
if (isPlaying && !hasStarted.current) {
hasStarted.current = false;
progressRef.current = 0;
isWaiting.current = false;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}
const isAgvReady = () => {
if (!agvRef.current || agvRef.current.length === 0) return false;
if (!currentAgv) return false;
return currentAgv.isActive && hitCount >= currentAgv.maxHitCount;
};
if (isPlaying && !hasStarted.current && toPickupPath.length > 0) {
setBoxVisible(false);
const startPoint = new THREE.Vector3(...toPickupPath[0]);
object.position.copy(startPoint);
if (toPickupPath.length > 1) {
const nextPoint = new THREE.Vector3(...toPickupPath[1]);
const direction = nextPoint.clone().sub(startPoint).normalize();
object.rotation.y = Math.atan2(direction.x, direction.z);
}
hasStarted.current = true;
progressRef.current = 0;
hasReachedPickup.current = false;
setToPickupPath(toPickupPath.slice(-1));
logAgvStatus(id, "Started from station, heading to pickup");
return;
}
if (isPlaying && currentPhase === "initial" && !hasReachedPickup.current) {
const reached = moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
if (reached) {
hasReachedPickup.current = true;
if (currentAgv) {
currentAgv.status = "picking";
}
logAgvStatus(id, "Reached pickup point, Waiting for material");
}
return;
}
if (isPlaying && currentAgv?.isActive && currentPhase === "initial") {
if (!isAgvReady()) return;
setTimeout(() => {
setBoxVisible(true);
setPath([...pickupDropPath]);
setCurrentPhase("toDrop");
progressRef.current = 0;
logAgvStatus(id, "Started from pickup point, heading to drop point");
if (currentAgv) {
currentAgv.status = "toDrop";
}
}, 0);
return;
}
if (isPlaying && currentPhase === "toDrop") {
const reached = moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
if (reached && !isWaiting.current) {
isWaiting.current = true;
logAgvStatus(id, "Reached drop point");
if (currentAgv) {
currentAgv.status = "droping";
currentAgv.hitCount = currentAgv.hitCount--;
}
timeoutRef.current = setTimeout(() => {
setPath([...dropPickupPath]);
setCurrentPhase("toPickup");
progressRef.current = 0;
isWaiting.current = false;
setBoxVisible(false);
if (currentAgv) {
currentAgv.status = "toPickup";
}
logAgvStatus(
id,
"Started from droping point, heading to pickup point"
);
}, bufferTime * 1000);
}
return;
}
if (isPlaying && currentPhase === "toPickup") {
const reached = moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
if (reached) {
if (currentAgv) {
currentAgv.isActive = false;
}
setCurrentPhase("initial");
if (currentAgv) {
currentAgv.status = "picking";
}
logAgvStatus(id, "Reached pickup point again, cycle complete");
}
return;
}
moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
});
function moveAlongPath(
object: THREE.Object3D,
path: [number, number, number][],
distances: number[],
speed: number,
delta: number,
progressRef: React.MutableRefObject<number>
): boolean {
if (path.length < 2) return false;
progressRef.current += delta * (speed * globalSpeed);
let covered = progressRef.current;
let accumulated = 0;
let index = 0;
for (; index < distances.length; index++) {
const dist = distances[index];
if (accumulated + dist >= covered) break;
accumulated += dist;
}
if (index >= path.length - 1) {
if (path.length > 1) {
const lastDirection = new THREE.Vector3(...path[path.length - 1])
.sub(new THREE.Vector3(...path[path.length - 2]))
.normalize();
object.rotation.y = Math.atan2(lastDirection.x, lastDirection.z);
}
return true;
}
const start = new THREE.Vector3(...path[index]);
const end = new THREE.Vector3(...path[index + 1]);
const dist = distances[index];
const t = THREE.MathUtils.clamp((covered - accumulated) / dist, 0, 1);
object.position.copy(start.clone().lerp(end, t));
if (dist > 0.1) {
const targetDirection = end.clone().sub(start).normalize();
const targetRotationY = Math.atan2(targetDirection.x, targetDirection.z);
const rotationSpeed = Math.min(5 * delta, 1);
object.rotation.y = THREE.MathUtils.lerp(
object.rotation.y,
targetRotationY,
rotationSpeed
);
}
return false;
}
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return (
<group name="path-navigator-lines" visible={!isPlaying} >
{toPickupPath.length > 0 && (
<Line
points={toPickupPath}
color="blue"
lineWidth={3}
dashed
dashSize={0.75}
dashScale={2}
/>
)}
{pickupDropPath.length > 0 && (
<Line points={pickupDropPath} color="red" lineWidth={3} />
)}
{dropPickupPath.length > 0 && (
<Line points={dropPickupPath} color="red" lineWidth={3} />
)}
</group>
);
}

View File

@@ -1,118 +0,0 @@
import * as THREE from "three";
import { useEffect, useState } from "react";
import * as turf from "@turf/turf";
import * as Types from "../../../types/world/worldTypes";
import arrayLinesToObject from "../geomentries/lines/lineConvertions/arrayLinesToObject";
interface PolygonGeneratorProps {
groupRef: React.MutableRefObject<THREE.Group | null>;
lines: Types.RefLines;
}
export default function PolygonGenerator({
groupRef,
lines,
}: PolygonGeneratorProps) {
useEffect(() => {
let allLines = arrayLinesToObject(lines.current);
const wallLines = allLines?.filter((line) => line?.type === "WallLine");
const aisleLines = allLines?.filter((line) => line?.type === "AisleLine");
const wallPoints = wallLines
.map((pair) => pair?.line.map((vals) => vals.position))
.filter((wall): wall is THREE.Vector3[] => !!wall);
const result = aisleLines.map((pair) =>
pair?.line.map((point) => ({
position: [point.position.x, point.position.z],
uuid: point.uuid,
}))
);
if (!result || result.some((line) => !line)) {
return;
}
const lineFeatures = result?.map((line: any) =>
turf.lineString(line.map((p: any) => p?.position))
);
const polygons = turf.polygonize(turf.featureCollection(lineFeatures));
renderWallGeometry(wallPoints);
if (polygons.features.length > 1) {
polygons.features.forEach((feature) => {
if (feature.geometry.type === "Polygon") {
const shape = new THREE.Shape();
const coords = feature.geometry.coordinates[0];
shape.moveTo(coords[0][0], coords[0][1]);
for (let i = 1; i < coords.length; i++) {
shape.lineTo(coords[i][0], coords[i][1]);
}
shape.lineTo(coords[0][0], coords[0][1]);
const extrudeSettings = {
depth: 5,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshBasicMaterial({ color: "blue", transparent: true, opacity: 0.5 });
const mesh = new THREE.Mesh(geometry, material);
mesh.rotateX(Math.PI / 2);
mesh.name = "agv-collider";
mesh.position.y = 5;
mesh.receiveShadow = true;
groupRef.current?.add(mesh);
}
});
}
}, [lines.current]);
const renderWallGeometry = (walls: THREE.Vector3[][]) => {
walls.forEach((wall) => {
if (wall.length < 2) return;
for (let i = 0; i < wall.length - 1; i++) {
const start = new THREE.Vector3(wall[i].x, wall[i].y, wall[i].z);
const end = new THREE.Vector3(
wall[i + 1].x,
wall[i + 1].y,
wall[i + 1].z
);
const wallHeight = 10;
const direction = new THREE.Vector3().subVectors(end, start);
const length = direction.length();
direction.normalize();
const wallGeometry = new THREE.BoxGeometry(length, wallHeight);
const wallMaterial = new THREE.MeshBasicMaterial({
color: "#aaa",
transparent: true,
opacity: 0.5,
});
const wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
const midPoint = new THREE.Vector3()
.addVectors(start, end)
.multiplyScalar(0.5);
wallMesh.position.set(midPoint.x, wallHeight / 2, midPoint.z);
const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), direction);
wallMesh.quaternion.copy(quaternion);
groupRef.current?.add(wallMesh);
}
});
};
return null;
}

View File

@@ -6,14 +6,14 @@ import { useThree, useFrame } from "@react-three/fiber";
////////// Component Imports //////////
import DistanceText from "../../builder/geomentries/lines/distanceText/distanceText";
import ReferenceDistanceText from "../../builder/geomentries/lines/distanceText/referenceDistanceText";
import DistanceText from "./geomentries/lines/distanceText/distanceText";
import ReferenceDistanceText from "./geomentries/lines/distanceText/referenceDistanceText";
////////// Assests Imports //////////
import arch from "../../../assets/gltf-glb/arch.glb";
import door from "../../../assets/gltf-glb/door.glb";
import Window from "../../../assets/gltf-glb/window.glb";
import arch from "../../assets/gltf-glb/arch.glb";
import door from "../../assets/gltf-glb/door.glb";
import Window from "../../assets/gltf-glb/window.glb";
////////// Zustand State Imports //////////
@@ -32,31 +32,31 @@ import {
useRefTextUpdate,
useRenderDistance,
useLimitDistance,
} from "../../../store/store";
} from "../../store/store";
////////// 3D Function Imports //////////
import loadWalls from "../../builder/geomentries/walls/loadWalls";
import loadWalls from "./geomentries/walls/loadWalls";
import * as Types from "../../../types/world/worldTypes";
import * as Types from "../../types/world/worldTypes";
import SocketResponses from "../../collaboration/socketResponses.dev";
import FloorItemsGroup from "../../builder/groups/floorItemsGroup";
import FloorPlanGroup from "../../builder/groups/floorPlanGroup";
import FloorGroup from "../../builder/groups/floorGroup";
import FloorGroupAilse from "../../builder/groups/floorGroupAisle";
import Draw from "../../builder/functions/draw";
import WallsAndWallItems from "../../builder/groups/wallsAndWallItems";
import Ground from "../environment/ground";
import SocketResponses from "../collaboration/socketResponses.dev";
import FloorItemsGroup from "./groups/floorItemsGroup";
import FloorPlanGroup from "./groups/floorPlanGroup";
import FloorGroup from "./groups/floorGroup";
import FloorGroupAilse from "./groups/floorGroupAisle";
import Draw from "./functions/draw";
import WallsAndWallItems from "./groups/wallsAndWallItems";
import Ground from "../scene/environment/ground";
// import ZoneGroup from "../groups/zoneGroup1";
import { findEnvironment } from "../../../services/factoryBuilder/environment/findEnvironment";
import Layer2DVisibility from "../../builder/geomentries/layers/layer2DVisibility";
import DrieHtmlTemp from "../mqttTemp/drieHtmlTemp";
import ZoneGroup from "../../builder/groups/zoneGroup";
import useModuleStore from "../../../store/useModuleStore";
import NavMeshCreator from "../../builder/agv/navMeshCreator";
import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment";
import Layer2DVisibility from "./geomentries/layers/layer2DVisibility";
import DrieHtmlTemp from "..//visualization/mqttTemp/drieHtmlTemp";
import ZoneGroup from "./groups/zoneGroup";
import useModuleStore from "../../store/useModuleStore";
import MeasurementTool from "../scene/tools/measurementTool";
export default function World() {
export default function Builder() {
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
const csg = useRef(); // Reference for CSG object, used for 3D modeling.
const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
@@ -365,10 +365,9 @@ export default function World() {
anglesnappedPoint={anglesnappedPoint}
/>
<MeasurementTool />
{/* <DrieHtmlTemp itemsGroup={itemsGroup} /> */}
<NavMeshCreator lines={lines} />
</>
);
}

View File

@@ -5,12 +5,10 @@ import { toast } from 'react-toastify';
import TempLoader from './tempLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import * as Types from "../../../../types/world/worldTypes";
import * as SimulationTypes from "../../../../types/simulationTypes";
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { Socket } from 'socket.io-client';
import * as CONSTANTS from '../../../../types/world/worldConstants';
import { getAssetEventType } from '../../../../services/simulation/getAssetEventType';
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
async function addAssetModel(
@@ -25,7 +23,6 @@ async function addAssetModel(
socket: Socket<any>,
selectedItem: any,
setSelectedItem: any,
setSimulationStates: any,
plane: Types.RefMesh,
): Promise<void> {
@@ -66,7 +63,7 @@ async function addAssetModel(
const cachedModel = THREE.Cache.get(selectedItem.id);
if (cachedModel) {
// console.log(`[Cache] Fetching ${selectedItem.name}`);
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
return;
} else {
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
@@ -79,7 +76,7 @@ async function addAssetModel(
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(selectedItem.id, gltf);
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
},
() => {
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
@@ -91,7 +88,7 @@ async function addAssetModel(
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
await storeGLTF(selectedItem.id, modelBlob);
THREE.Cache.add(selectedItem.id, gltf);
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
},
() => {
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
@@ -114,7 +111,6 @@ async function handleModelLoad(
tempLoader: Types.RefMesh,
isTempLoader: Types.RefBoolean,
setFloorItems: Types.setFloorItemSetState,
setSimulationStates: any,
socket: Socket<any>
) {
const model = gltf.scene.clone();
@@ -137,7 +133,7 @@ async function handleModelLoad(
tempLoader.current = undefined;
}
const newFloorItem: SimulationTypes.EventData = {
const newFloorItem: Types.FloorItemType = {
modeluuid: model.uuid,
modelname: selectedItem.name,
modelfileID: selectedItem.id,
@@ -150,276 +146,6 @@ async function handleModelLoad(
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "";
getAssetEventType(selectedItem.id, organization).then(async (res) => {
if (res.type === "Conveyor") {
const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID());
const backendEventData: Extract<SimulationTypes.EventData['eventData'], { type: 'Conveyor' }> = {
type: 'Conveyor',
points: res.points.map((point: any, index: number) => ({
uuid: pointUUIDs[index],
position: point.position as [number, number, number],
rotation: point.rotation as [number, number, number],
actions: [{
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
type: 'Inherit',
material: 'Inherit',
delay: 'Inherit',
spawnInterval: 'Inherit',
isUsed: true
}],
triggers: [],
connections: {
source: { modelUUID: model.uuid, pointUUID: pointUUIDs[index] },
targets: []
}
})),
speed: 'Inherit'
};
// API
// await setFloorItemApi(
// organization,
// newFloorItem.modeluuid,
// newFloorItem.modelname,
// newFloorItem.modelfileID,
// newFloorItem.position,
// { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false,
isVisible: true,
eventData: backendEventData,
socketId: socket.id
};
setFloorItems((prevItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const eventData: any = backendEventData;
eventData.modeluuid = newFloorItem.modeluuid;
eventData.modelName = newFloorItem.modelname;
eventData.position = newFloorItem.position;
eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
eventData as SimulationTypes.ConveyorEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (res.type === "Vehicle") {
const pointUUID = THREE.MathUtils.generateUUID();
const backendEventData: Extract<SimulationTypes.EventData['eventData'], { type: 'Vehicle' }> = {
type: "Vehicle",
points: {
uuid: pointUUID,
position: res.points.position as [number, number, number],
rotation: res.points.rotation as [number, number, number],
actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: '', start: {}, hitCount: 1, end: {}, buffer: 0 },
connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] },
speed: 2,
}
}
// API
// await setFloorItemApi(
// organization,
// newFloorItem.modeluuid,
// newFloorItem.modelname,
// newFloorItem.modelfileID,
// newFloorItem.position,
// { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id
};
const eventData: any = backendEventData;
eventData.modeluuid = newFloorItem.modeluuid;
eventData.modelName = newFloorItem.modelname;
eventData.position = newFloorItem.position;
setFloorItems((prevItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
eventData as SimulationTypes.VehicleEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (res.type === "StaticMachine") {
const pointUUID = THREE.MathUtils.generateUUID();
const backendEventData: Extract<SimulationTypes.EventData['eventData'], { type: 'StaticMachine' }> = {
type: "StaticMachine",
points: {
uuid: pointUUID,
position: res.points.position as [number, number, number],
rotation: res.points.rotation as [number, number, number],
actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', buffer: 0, material: 'Inherit' },
triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' },
connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] },
}
}
// API
// await setFloorItemApi(
// organization,
// newFloorItem.modeluuid,
// newFloorItem.modelname,
// newFloorItem.modelfileID,
// newFloorItem.position,
// { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id
};
const eventData: any = backendEventData;
eventData.modeluuid = newFloorItem.modeluuid;
eventData.modelName = newFloorItem.modelname;
eventData.position = newFloorItem.position;
eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];
setFloorItems((prevItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
eventData as SimulationTypes.StaticMachineEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (res.type === "ArmBot") {
const pointUUID = THREE.MathUtils.generateUUID();
const backendEventData: Extract<SimulationTypes.EventData['eventData'], { type: 'ArmBot' }> = {
type: "ArmBot",
points: {
uuid: pointUUID,
position: res.points.position as [number, number, number],
rotation: res.points.rotation as [number, number, number],
actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', speed: 0.2, processes: [] },
triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' },
connections: { source: { modelUUID: model.uuid, pointUUID: pointUUID }, targets: [] },
}
}
// API
// await setFloorItemApi(
// organization,
// newFloorItem.modeluuid,
// newFloorItem.modelname,
// newFloorItem.modelfileID,
// newFloorItem.position,
// { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id
};
const eventData: any = backendEventData;
eventData.modeluuid = newFloorItem.modeluuid;
eventData.modelName = newFloorItem.modelname;
eventData.position = newFloorItem.position;
eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];
setFloorItems((prevItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
eventData as SimulationTypes.ArmBotEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else {
// API
// await setFloorItemApi(
@@ -455,11 +181,9 @@ async function handleModelLoad(
});
socket.emit("v2:model-asset:add", data);
}
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
});
}
export default addAssetModel;

View File

@@ -2,7 +2,6 @@ import { toast } from 'react-toastify';
import * as THREE from 'three';
import * as Types from "../../../../types/world/worldTypes";
import * as SimulationTypes from "../../../../types/simulationTypes";
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
import { Socket } from 'socket.io-client';
import { getFloorAssets } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
@@ -11,7 +10,6 @@ async function DeleteFloorItems(
itemsGroup: Types.RefGroup,
hoveredDeletableFloorItem: Types.RefMesh,
setFloorItems: Types.setFloorItemSetState,
setSimulationStates: any,
socket: Socket<any>
): Promise<void> {
@@ -77,11 +75,6 @@ async function DeleteFloorItems(
}
setFloorItems(updatedItems);
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== removedItem.modeluuid);
return updatedEvents;
});
toast.success("Model Removed!");
}
}

View File

@@ -1,5 +1,5 @@
import { useFrame, useThree } from "@react-three/fiber";
import { useActiveTool, useAsset3dWidget, useCamMode, useDeletableFloorItem, useDeleteTool, useFloorItems, useLoadingProgress, useRenderDistance, useSelectedFloorItem, useSelectedItem, useSimulationStates, useSocketStore, useToggleView, useTransformMode, } from "../../../store/store";
import { useActiveTool, useAsset3dWidget, useCamMode, useDeletableFloorItem, useDeleteTool, useFloorItems, useLoadingProgress, useRenderDistance, useSelectedFloorItem, useSelectedItem, useSocketStore, useToggleView, useTransformMode, } from "../../../store/store";
import assetVisibility from "../geomentries/assets/assetVisibility";
import { useEffect } from "react";
import * as THREE from "three";
@@ -11,7 +11,7 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import DeletableHoveredFloorItems from "../geomentries/assets/deletableHoveredFloorItems";
import DeleteFloorItems from "../geomentries/assets/deleteFloorItems";
import loadInitialFloorItems from "../../scene/IntialLoad/loadInitialFloorItems";
import loadInitialFloorItems from "../IntialLoad/loadInitialFloorItems";
import addAssetModel from "../geomentries/assets/addAssetModel";
import { getFloorAssets } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi";
import useModuleStore from "../../../store/useModuleStore";
@@ -32,7 +32,6 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
const { setSelectedFloorItem } = useSelectedFloorItem();
const { activeTool } = useActiveTool();
const { selectedItem, setSelectedItem } = useSelectedItem();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { setLoadingProgress } = useLoadingProgress();
const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
@@ -73,7 +72,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
gltfLoaderWorker.postMessage({ floorItems: data });
} else {
gltfLoaderWorker.postMessage({ floorItems: [] });
loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates);
loadInitialFloorItems(itemsGroup, setFloorItems);
updateLoadingProgress(100);
}
});
@@ -92,7 +91,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
updateLoadingProgress(progress);
if (loadedAssets === totalAssets) {
loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates);
loadInitialFloorItems(itemsGroup, setFloorItems);
updateLoadingProgress(100);
}
});
@@ -192,9 +191,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
if (drag) return;
if (deleteTool) {
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, setSimulationStates, socket);
// Remove EventData if there are any in the asset.
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, socket);
}
const Mode = transformMode;
@@ -278,7 +275,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
if (!event.dataTransfer?.files[0]) return;
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, setSimulationStates, plane);
addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, plane);
}
};

View File

@@ -9,15 +9,14 @@ import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
import DeleteLayer from "../geomentries/layers/deleteLayer";
import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
import loadInitialPoint from "../../scene/IntialLoad/loadInitialPoint";
import loadInitialLine from "../../scene/IntialLoad/loadInitialLine";
import loadInitialPoint from "../IntialLoad/loadInitialPoint";
import loadInitialLine from "../IntialLoad/loadInitialLine";
import deletePoint from "../geomentries/points/deletePoint";
import deleteLine from "../geomentries/lines/deleteLine";
import drawWall from "../geomentries/lines/drawWall";
import drawOnlyFloor from "../geomentries/floors/drawOnlyFloor";
import addDragControl from "../eventDeclaration/dragControlDeclaration";
const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, floorGroup, currentLayerPoint, dragPointControls, hoveredDeletablePoint, hoveredDeletableLine, plane, line, lines, onlyFloorline, onlyFloorlines, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
const state = useThree();
const { scene, camera, gl, raycaster, controls } = state;

View File

@@ -7,7 +7,7 @@ import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
import DeleteWallItems from "../geomentries/walls/deleteWallItems";
import loadInitialWallItems from "../../scene/IntialLoad/loadInitialWallItems";
import loadInitialWallItems from "../IntialLoad/loadInitialWallItems";
import AddWallItems from "../geomentries/walls/addWallItems";
import useModuleStore from "../../../store/useModuleStore";
@@ -37,51 +37,6 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable
////////// Update the Rotation value changes in the selected item //////////
useEffect(() => {
if (objectScale.x && objectScale.y && objectScale.z) {
let ScaledWallItems: Types.wallItems = [];
wallItems.forEach((items: any) => {
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
items.scale = [objectScale.x, objectScale.y, objectScale.z];
}
ScaledWallItems.push(items);
});
setWallItems(ScaledWallItems);
}
}, [objectScale]);
useEffect(() => {
if (objectPosition.x && objectPosition.y && objectPosition.z) {
let ScaledWallItems: Types.wallItems = [];
wallItems.forEach((items: any) => {
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
items.position = [objectPosition.x, objectPosition.y, objectPosition.z];
}
ScaledWallItems.push(items);
});
setWallItems(ScaledWallItems);
}
}, [objectPosition]);
useEffect(() => {
if (objectRotation.x && objectRotation.y && objectRotation.z) {
let ScaledWallItems: Types.wallItems = [];
wallItems.forEach((items: any) => {
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
const radiansX = objectRotation.x * (Math.PI / 180);
const radiansY = objectRotation.y * (Math.PI / 180);
const radiansZ = objectRotation.z * (Math.PI / 180);
const quaternion = new THREE.Quaternion().setFromEuler(
new THREE.Euler(radiansX, radiansY, radiansZ)
);
items.quaternion = [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
}
ScaledWallItems.push(items);
});
setWallItems(ScaledWallItems);
}
}, [objectRotation]);
useEffect(() => {
const canvasElement = state.gl.domElement;
function handlePointerMove(e: any) {

View File

@@ -0,0 +1,231 @@
import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import camModel from "../../assets/gltf-glb/camera face 2.gltf";
import getActiveUsersData from "../../../services/factoryBuilder/collab/getActiveUsers";
import { useActiveUsers, useSocketStore } from "../../../store/store";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { useNavigate } from "react-router-dom";
import { Html } from "@react-three/drei";
import CollabUserIcon from "../../../functions/collabUserIcon";
import { getAvatarColor } from "../../../functions/users/functions/getAvatarColor";
import useModuleStore from "../../../store/useModuleStore";
const CamModelsGroup = () => {
const navigate = useNavigate();
const groupRef = useRef<THREE.Group>(null);
const email = localStorage.getItem("email");
const { setActiveUsers } = useActiveUsers();
const { socket } = useSocketStore();
const { activeModule } = useModuleStore();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
const [cams, setCams] = useState<any[]>([]);
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler }>>({});
const dedupeCams = (cams: any[]) => {
const seen = new Set();
return cams.filter((cam) => {
if (seen.has(cam.uuid)) return false;
seen.add(cam.uuid);
return true;
});
};
const dedupeUsers = (users: any[]) => {
const seen = new Set();
return users.filter((user) => {
if (seen.has(user._id)) return false;
seen.add(user._id);
return true;
});
};
useEffect(() => {
if (!email) navigate("/");
if (!socket) return;
const organization = email!.split("@")[1].split(".")[0];
socket.on("userConnectResponse", (data: any) => {
if (!groupRef.current) return;
if (data.data.userData.email === email) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
const model = groupRef.current.getObjectByProperty(
"uuid",
data.data.userData._id
);
if (model) {
groupRef.current.remove(model);
}
loader.load(camModel, (gltf) => {
const newModel = gltf.scene.clone();
newModel.uuid = data.data.userData._id;
newModel.position.set(
data.data.position.x,
data.data.position.y,
data.data.position.z
);
newModel.rotation.set(
data.data.rotation.x,
data.data.rotation.y,
data.data.rotation.z
);
newModel.userData = data.data.userData;
setCams((prev) => dedupeCams([...prev, newModel]));
setActiveUsers((prev: any) =>
dedupeUsers([...prev, data.data.userData])
);
});
});
socket.on("userDisConnectResponse", (data: any) => {
if (!groupRef.current) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
setCams((prev) =>
prev.filter((cam) => cam.uuid !== data.data.userData._id)
);
setActiveUsers((prev: any) =>
prev.filter((user: any) => user._id !== data.data.userData._id)
);
});
socket.on("cameraUpdateResponse", (data: any) => {
if (
!groupRef.current ||
socket.id === data.socketId ||
organization !== data.organization
)
return;
setModels((prev) => ({
...prev,
[data.data.userId]: {
targetPosition: new THREE.Vector3(
data.data.position.x,
data.data.position.y,
data.data.position.z
),
targetRotation: new THREE.Euler(
data.data.rotation.x,
data.data.rotation.y,
data.data.rotation.z
),
},
}));
});
return () => {
socket.off("userConnectResponse");
socket.off("userDisConnectResponse");
socket.off("cameraUpdateResponse");
};
}, [socket]);
useFrame(() => {
if (!groupRef.current) return;
Object.keys(models).forEach((uuid) => {
const model = groupRef.current!.getObjectByProperty("uuid", uuid);
if (!model) return;
const { targetPosition, targetRotation } = models[uuid];
model.position.lerp(targetPosition, 0.1);
model.rotation.x = THREE.MathUtils.lerp(
model.rotation.x,
targetRotation.x,
0.1
);
model.rotation.y = THREE.MathUtils.lerp(
model.rotation.y,
targetRotation.y,
0.1
);
model.rotation.z = THREE.MathUtils.lerp(
model.rotation.z,
targetRotation.z,
0.1
);
});
});
useEffect(() => {
if (!groupRef.current) return;
const organization = email!.split("@")[1].split(".")[0];
getActiveUsersData(organization).then((data) => {
const filteredData = data.cameraDatas.filter(
(camera: any) => camera.userData.email !== email
);
if (filteredData.length > 0) {
loader.load(camModel, (gltf) => {
const newCams = filteredData.map((cam: any) => {
const newModel = gltf.scene.clone();
newModel.uuid = cam.userData._id;
newModel.position.set(
cam.position.x,
cam.position.y,
cam.position.z
);
newModel.rotation.set(
cam.rotation.x,
cam.rotation.y,
cam.rotation.z
);
newModel.userData = cam.userData;
return newModel;
});
const users = filteredData.map((cam: any) => cam.userData);
setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
setCams((prev) => dedupeCams([...prev, ...newCams]));
});
}
});
}, []);
return (
<group
ref={groupRef}
name="Cam-Model-Group"
visible={activeModule !== "visualization" ? true : false}
>
{cams.map((cam, index) => (
<primitive key={cam.uuid} object={cam}>
<Html
as="div"
center
zIndexRange={[1, 0]}
sprite
style={{
color: "white",
textAlign: "center",
fontFamily: "Arial, sans-serif",
display: `${activeModule !== "visualization" ? "" : "none"}`,
}}
position={[-0.015, 0, 0.7]}
>
<CollabUserIcon
userImage={cam.userData.userImage || ""}
userName={cam.userData.userName}
color={getAvatarColor(index, cam.userData.userName)}
/>
</Html>
</primitive>
))}
</group>
);
};
export default CamModelsGroup;

View File

@@ -1,231 +0,0 @@
import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import camModel from "../../assets/gltf-glb/camera face 2.gltf";
import getActiveUsersData from "../../services/factoryBuilder/collab/getActiveUsers";
import { useActiveUsers, useSocketStore } from "../../store/store";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { useNavigate } from "react-router-dom";
import { Html } from "@react-three/drei";
import CollabUserIcon from "./collabUserIcon";
import { getAvatarColor } from "./users/functions/getAvatarColor";
import useModuleStore from "../../store/useModuleStore";
const CamModelsGroup = () => {
const navigate = useNavigate();
const groupRef = useRef<THREE.Group>(null);
const email = localStorage.getItem("email");
const { setActiveUsers } = useActiveUsers();
const { socket } = useSocketStore();
const { activeModule } = useModuleStore();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
const [cams, setCams] = useState<any[]>([]);
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler }>>({});
const dedupeCams = (cams: any[]) => {
const seen = new Set();
return cams.filter((cam) => {
if (seen.has(cam.uuid)) return false;
seen.add(cam.uuid);
return true;
});
};
const dedupeUsers = (users: any[]) => {
const seen = new Set();
return users.filter((user) => {
if (seen.has(user._id)) return false;
seen.add(user._id);
return true;
});
};
useEffect(() => {
if (!email) navigate("/");
if (!socket) return;
const organization = email!.split("@")[1].split(".")[0];
socket.on("userConnectResponse", (data: any) => {
if (!groupRef.current) return;
if (data.data.userData.email === email) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
const model = groupRef.current.getObjectByProperty(
"uuid",
data.data.userData._id
);
if (model) {
groupRef.current.remove(model);
}
loader.load(camModel, (gltf) => {
const newModel = gltf.scene.clone();
newModel.uuid = data.data.userData._id;
newModel.position.set(
data.data.position.x,
data.data.position.y,
data.data.position.z
);
newModel.rotation.set(
data.data.rotation.x,
data.data.rotation.y,
data.data.rotation.z
);
newModel.userData = data.data.userData;
setCams((prev) => dedupeCams([...prev, newModel]));
setActiveUsers((prev: any) =>
dedupeUsers([...prev, data.data.userData])
);
});
});
socket.on("userDisConnectResponse", (data: any) => {
if (!groupRef.current) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
setCams((prev) =>
prev.filter((cam) => cam.uuid !== data.data.userData._id)
);
setActiveUsers((prev: any) =>
prev.filter((user: any) => user._id !== data.data.userData._id)
);
});
socket.on("cameraUpdateResponse", (data: any) => {
if (
!groupRef.current ||
socket.id === data.socketId ||
organization !== data.organization
)
return;
setModels((prev) => ({
...prev,
[data.data.userId]: {
targetPosition: new THREE.Vector3(
data.data.position.x,
data.data.position.y,
data.data.position.z
),
targetRotation: new THREE.Euler(
data.data.rotation.x,
data.data.rotation.y,
data.data.rotation.z
),
},
}));
});
return () => {
socket.off("userConnectResponse");
socket.off("userDisConnectResponse");
socket.off("cameraUpdateResponse");
};
}, [socket]);
useFrame(() => {
if (!groupRef.current) return;
Object.keys(models).forEach((uuid) => {
const model = groupRef.current!.getObjectByProperty("uuid", uuid);
if (!model) return;
const { targetPosition, targetRotation } = models[uuid];
model.position.lerp(targetPosition, 0.1);
model.rotation.x = THREE.MathUtils.lerp(
model.rotation.x,
targetRotation.x,
0.1
);
model.rotation.y = THREE.MathUtils.lerp(
model.rotation.y,
targetRotation.y,
0.1
);
model.rotation.z = THREE.MathUtils.lerp(
model.rotation.z,
targetRotation.z,
0.1
);
});
});
useEffect(() => {
if (!groupRef.current) return;
const organization = email!.split("@")[1].split(".")[0];
getActiveUsersData(organization).then((data) => {
const filteredData = data.cameraDatas.filter(
(camera: any) => camera.userData.email !== email
);
if (filteredData.length > 0) {
loader.load(camModel, (gltf) => {
const newCams = filteredData.map((cam: any) => {
const newModel = gltf.scene.clone();
newModel.uuid = cam.userData._id;
newModel.position.set(
cam.position.x,
cam.position.y,
cam.position.z
);
newModel.rotation.set(
cam.rotation.x,
cam.rotation.y,
cam.rotation.z
);
newModel.userData = cam.userData;
return newModel;
});
const users = filteredData.map((cam: any) => cam.userData);
setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
setCams((prev) => dedupeCams([...prev, ...newCams]));
});
}
});
}, []);
return (
<group
ref={groupRef}
name="Cam-Model-Group"
visible={activeModule !== "visualization" ? true : false}
>
{cams.map((cam, index) => (
<primitive key={cam.uuid} object={cam}>
<Html
as="div"
center
zIndexRange={[1, 0]}
sprite
style={{
color: "white",
textAlign: "center",
fontFamily: "Arial, sans-serif",
display: `${activeModule !== "visualization" ? "" : "none"}`,
}}
position={[-0.015, 0, 0.7]}
>
<CollabUserIcon
userImage={cam.userData.userImage || ""}
userName={cam.userData.userName}
color={getAvatarColor(index, cam.userData.userName)}
/>
</Html>
</primitive>
))}
</group>
);
};
export default CamModelsGroup;

View File

@@ -0,0 +1,14 @@
import React from 'react'
import CamModelsGroup from './camera/collabCams'
const Collaboration = () => {
return (
<>
<CamModelsGroup />
</>
)
}
export default Collaboration

View File

@@ -9,6 +9,7 @@ import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi"
import updateCamPosition from "../camera/updateCameraPosition";
import CamMode from "../camera/camMode";
import SwitchView from "../camera/switchView";
import SelectionControls from "./selectionControls/selectionControls";
export default function Controls() {
const controlsRef = useRef<CameraControls>(null);
@@ -128,9 +129,15 @@ export default function Controls() {
boundaryEnclosesCamera={true}
dollyToCursor={toggleView}
>
<SwitchView />
<CamMode />
</CameraControls>
<SelectionControls />
</>
);
}

View File

@@ -1,581 +0,0 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import * as SimulationTypes from "../../../../types/simulationTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { simulationStates, setSimulationStates } = useSimulationStates();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore()
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
event.preventDefault();
addPastedObjects();
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+C" && movedObjects.length === 0 && rotatedObjects.length === 0) {
copySelection();
}
if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, floorItems, rotatedObjects]);
useFrame(() => {
if (pastedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
});
const copySelection = () => {
if (selectedAssets.length > 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
clone.position.copy(asset.position);
return clone;
});
setCopiedObjects(newClones);
toast.info("Objects copied!");
}
};
const pasteCopiedObjects = () => {
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
const newClones = copiedObjects.map((obj: THREE.Object3D) => {
const clone = obj.clone();
clone.position.copy(obj.position);
return clone;
});
selectionGroup.current.add(...newClones);
setpastedObjects([...newClones]);
setSelectedAssets([...newClones]);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
};
const addPastedObjects = () => {
if (pastedObjects.length === 0) return;
pastedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
let eventData: SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid);
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
if (eventData) {
if (eventData.type === 'Conveyor' && eventData) {
const createConveyorPoint = (index: number) => {
const pointUUID = THREE.MathUtils.generateUUID();
const hasActions = (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].actions.length > 0;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
type: 'Inherit',
material: 'Inherit',
delay: 'Inherit',
spawnInterval: 'Inherit',
isUsed: true
};
return {
uuid: pointUUID,
position: (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].position,
rotation: (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].rotation,
actions: hasActions
? (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].actions.map(action => ({
...action,
uuid: THREE.MathUtils.generateUUID()
}))
: [defaultAction],
triggers: (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].triggers.map(trigger => ({
...trigger,
uuid: THREE.MathUtils.generateUUID()
})),
connections: {
source: { modelUUID: obj.uuid, pointUUID },
targets: []
}
};
};
const backendEventData = {
type: 'Conveyor',
points: [
createConveyorPoint(0), // point1
createConveyorPoint(1), // middlePoint
createConveyorPoint(2) // point2
],
speed: (eventData as SimulationTypes.ConveyorEventsSchema)?.speed
};
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// backendEventData
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.ConveyorEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'Vehicle' && eventData) {
const createVehiclePoint = () => {
const pointUUID = THREE.MathUtils.generateUUID();
const vehiclePoint = (eventData as SimulationTypes.VehicleEventsSchema)?.points;
const hasActions = vehiclePoint?.actions !== undefined;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
type: 'Inherit',
start: {},
hitCount: 0,
end: {},
buffer: 0
};
return {
uuid: pointUUID,
position: vehiclePoint?.position,
// rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
uuid: THREE.MathUtils.generateUUID()
}
: defaultAction,
connections: {
source: { modelUUID: obj.uuid, pointUUID },
targets: []
},
speed: vehiclePoint?.speed || 1
};
};
const backendEventData = {
type: 'Vehicle',
points: createVehiclePoint(),
};
// API
// setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.VehicleEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'StaticMachine' && eventData) {
const createStaticMachinePoint = () => {
const pointUUID = THREE.MathUtils.generateUUID();
const staticMachinePoint = (eventData as SimulationTypes.StaticMachineEventsSchema)?.points;
const hasActions = staticMachinePoint?.actions !== undefined;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
buffer: 0,
material: 'Inherit',
};
return {
uuid: pointUUID,
position: staticMachinePoint?.position,
rotation: staticMachinePoint?.rotation,
actions: hasActions
? {
...staticMachinePoint.actions,
uuid: THREE.MathUtils.generateUUID()
}
: defaultAction,
triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' },
connections: {
source: { modelUUID: obj.uuid, pointUUID },
targets: []
}
};
};
const backendEventData = {
type: 'StaticMachine',
points: createStaticMachinePoint()
};
// API
// setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.StaticMachineEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'ArmBot' && eventData) {
const createArmBotPoint = () => {
const pointUUID = THREE.MathUtils.generateUUID();
const armBotPoint = (eventData as SimulationTypes.ArmBotEventsSchema)?.points;
const hasActions = armBotPoint?.actions !== undefined;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
buffer: 0,
material: 'Inherit',
};
return {
uuid: pointUUID,
position: armBotPoint?.position,
rotation: armBotPoint?.rotation,
actions: hasActions
? {
...armBotPoint.actions,
uuid: THREE.MathUtils.generateUUID(),
processes: []
}
: defaultAction,
triggers: {
uuid: THREE.MathUtils.generateUUID(),
name: armBotPoint.triggers.name,
type: armBotPoint.triggers.type,
},
connections: {
source: { modelUUID: obj.uuid, pointUUID },
targets: []
}
};
};
const backendEventData = {
type: 'ArmBot',
points: createArmBotPoint()
};
// API
// setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.ArmBotEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else {
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
}
} else {
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
}
obj.userData.modeluuid = newFloorItem.modeluuid;
itemsGroupRef.current.add(obj);
}
});
toast.success("Object added!");
clearSelection();
};
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // No visible output, but the component handles copy-paste functionality
};
export default CopyPasteControls;

View File

@@ -1,560 +0,0 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import * as SimulationTypes from "../../../../types/simulationTypes";
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { simulationStates, setSimulationStates } = useSimulationStates();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
event.preventDefault();
addDuplicatedAssets();
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects]);
useFrame(() => {
if (duplicatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
});
const duplicateSelection = () => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
clone.position.copy(asset.position);
return clone;
});
selectionGroup.current.add(...newClones);
setDuplicatedObjects(newClones);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
}
}
};
const addDuplicatedAssets = () => {
if (duplicatedObjects.length === 0) return;
duplicatedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
let eventData: SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid);
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
if (eventData) {
if (eventData.type === 'Conveyor' && eventData) {
const createConveyorPoint = (index: number) => {
const pointUUID = THREE.MathUtils.generateUUID();
const hasActions = (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].actions.length > 0;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
type: 'Inherit',
material: 'Inherit',
delay: 'Inherit',
spawnInterval: 'Inherit',
isUsed: true
};
return {
uuid: pointUUID,
position: (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].position,
rotation: (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].rotation,
actions: hasActions
? (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].actions.map(action => ({
...action,
uuid: THREE.MathUtils.generateUUID()
}))
: [defaultAction],
triggers: (eventData as SimulationTypes.ConveyorEventsSchema)?.points[index].triggers.map(trigger => ({
...trigger,
uuid: THREE.MathUtils.generateUUID()
})),
connections: {
source: { modelUUID: newFloorItem.modeluuid, pointUUID },
targets: []
}
};
};
const backendEventData = {
type: 'Conveyor',
points: [
createConveyorPoint(0),
createConveyorPoint(1),
createConveyorPoint(2)
],
speed: (eventData as SimulationTypes.ConveyorEventsSchema)?.speed
};
//REST
// setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.ConveyorEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'Vehicle' && eventData) {
const createVehiclePoint = () => {
const pointUUID = THREE.MathUtils.generateUUID();
const vehiclePoint = (eventData as SimulationTypes.VehicleEventsSchema)?.points;
const hasActions = vehiclePoint?.actions !== undefined;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
type: 'Inherit',
start: {},
hitCount: 0,
end: {},
buffer: 0
};
return {
uuid: pointUUID,
position: vehiclePoint?.position,
rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
uuid: THREE.MathUtils.generateUUID()
}
: defaultAction,
connections: {
source: { modelUUID: obj.uuid, pointUUID },
targets: []
},
speed: vehiclePoint?.speed || 2
};
};
const backendEventData = {
type: 'Vehicle',
points: createVehiclePoint()
};
// API
// setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.VehicleEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'StaticMachine' && eventData) {
const createStaticMachinePoint = () => {
const pointUUID = THREE.MathUtils.generateUUID();
const staticMachinePoint = (eventData as SimulationTypes.StaticMachineEventsSchema)?.points;
const hasActions = staticMachinePoint?.actions !== undefined;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
buffer: 0,
material: 'Inherit',
};
return {
uuid: pointUUID,
position: staticMachinePoint?.position,
rotation: staticMachinePoint?.rotation,
actions: hasActions
? {
...staticMachinePoint.actions,
uuid: THREE.MathUtils.generateUUID()
}
: defaultAction,
triggers: { uuid: THREE.MathUtils.generateUUID(), name: 'Trigger 1', type: 'OnComplete' },
connections: {
source: { modelUUID: obj.uuid, pointUUID },
targets: []
}
};
};
const backendEventData = {
type: 'StaticMachine',
points: createStaticMachinePoint()
};
// API
// setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.StaticMachineEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'ArmBot' && eventData) {
const createArmBotPoint = () => {
const pointUUID = THREE.MathUtils.generateUUID();
const armBotPoint = (eventData as SimulationTypes.ArmBotEventsSchema)?.points;
const hasActions = armBotPoint?.actions !== undefined;
const defaultAction = {
uuid: THREE.MathUtils.generateUUID(),
name: 'Action 1',
buffer: 0,
material: 'Inherit',
};
return {
uuid: pointUUID,
position: armBotPoint?.position,
rotation: armBotPoint?.rotation,
actions: hasActions
? {
...armBotPoint.actions,
uuid: THREE.MathUtils.generateUUID(),
processes: []
}
: defaultAction,
triggers: {
uuid: THREE.MathUtils.generateUUID(),
name: armBotPoint.triggers.name,
type: armBotPoint.triggers.type,
},
connections: {
source: { modelUUID: obj.uuid, pointUUID },
targets: []
}
};
};
const backendEventData = {
type: 'ArmBot',
points: createArmBotPoint()
};
// API
// setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
// SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
...(prevEvents || []),
newEventData as SimulationTypes.ArmBotEventsSchema
]);
socket.emit("v2:model-asset:add", data);
} else {
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
}
} else {
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
}
obj.userData.modeluuid = newFloorItem.modeluuid;
itemsGroupRef.current.add(obj);
}
});
toast.success("Object duplicated!");
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // This component does not render any UI
};
export default DuplicationControls;

View File

@@ -1,463 +0,0 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
import * as SimulationTypes from "../../../../types/simulationTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef<Types.FloorItems>([]);
useEffect(() => {
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeMovedAssets();
}
if (!isMoving && movedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
movedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setMovedObjects([]);
itemsData.current = [];
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
if (keyCombination === "G") {
if (selectedAssets.length > 0) {
moveAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
movedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setMovedObjects([]);
itemsData.current = [];
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]);
const gridSize = 0.25;
const moveSpeed = 0.25;
const isGridSnap = false;
useFrame(() => {
if (movedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
let targetX = point.x;
let targetZ = point.z;
if (isGridSnap) {
targetX = Math.round(point.x / gridSize) * gridSize;
targetZ = Math.round(point.z / gridSize) * gridSize;
}
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current.getWorldPosition(position);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (position.x - selectionGroup.current.position.x),
selectionGroup.current.position.y,
targetZ - (position.z - selectionGroup.current.position.z)
),
moveSpeed
);
} else {
const box = new THREE.Box3();
movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (center.x - selectionGroup.current.position.x),
selectionGroup.current.position.y,
targetZ - (center.z - selectionGroup.current.position.z)
),
moveSpeed
);
}
}
}
});
const moveAssets = () => {
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
setFloorItems(updatedItems);
setMovedObjects(selectedAssets);
selectedAssets.forEach((asset: any) => { selectionGroup.current.attach(asset); });
}
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;
movedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
let eventData: SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid);
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
if (eventData) {
if (eventData.type === 'Conveyor' && eventData) {
const backendEventData = {
type: 'Conveyor',
points: eventData.points,
speed: (eventData as SimulationTypes.ConveyorEventsSchema)?.speed
};
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'Vehicle' && eventData) {
const backendEventData = {
type: 'Vehicle',
points: eventData.points
};
// REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'StaticMachine' && eventData) {
const backendEventData = {
type: 'StaticMachine',
points: eventData.points,
};
// REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'ArmBot' && eventData) {
const backendEventData = {
type: 'ArmBot',
points: eventData.points,
};
// REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
}
} else {
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
}
itemsGroupRef.current.add(obj);
}
});
toast.success("Object moved!");
itemsData.current = [];
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // No need to return anything, as this component is used for its side effects
}
export default MoveControls

View File

@@ -1,464 +0,0 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
import * as SimulationTypes from "../../../../types/simulationTypes";
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef<Types.FloorItems>([]);
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
useEffect(() => {
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && rotatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeRotatedAssets();
}
if (!isMoving && rotatedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setRotatedObjects([]);
itemsData.current = [];
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return;
if (event.key.toLowerCase() === "r") {
if (selectedAssets.length > 0) {
rotateAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setRotatedObjects([]);
itemsData.current = [];
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, rotatedObjects, movedObjects]);
useFrame(() => {
if (rotatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point && prevPointerPosition.current) {
const box = new THREE.Box3();
rotatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
const delta = new THREE.Vector3().subVectors(point, center);
const prevPointerPosition3D = new THREE.Vector3(prevPointerPosition.current.x, 0, prevPointerPosition.current.y);
const angle = Math.atan2(delta.z, delta.x) - Math.atan2(prevPointerPosition3D.z - center.z, prevPointerPosition3D.x - center.x);
selectionGroup.current.rotation.y += -angle;
selectionGroup.current.position.sub(center);
selectionGroup.current.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
selectionGroup.current.position.add(center);
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
}
});
const rotateAssets = () => {
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
setFloorItems(updatedItems);
const box = new THREE.Box3();
selectedAssets.forEach((asset: any) => box.expandByObject(asset));
const center = new THREE.Vector3();
box.getCenter(center);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
selectedAssets.forEach((asset: any) => {
selectionGroup.current.attach(asset);
});
setRotatedObjects(selectedAssets);
};
const placeRotatedAssets = () => {
if (rotatedObjects.length === 0) return;
rotatedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
const worldQuaternion = new THREE.Quaternion();
obj.getWorldPosition(worldPosition);
obj.getWorldQuaternion(worldQuaternion);
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
obj.quaternion.copy(worldQuaternion);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
let eventData: SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema | undefined = simulationStates.find((events) => events.modeluuid === obj.userData.modeluuid);
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
if (eventData) {
if (eventData.type === 'Conveyor' && eventData) {
const backendEventData = {
type: 'Conveyor',
points: eventData.points,
speed: (eventData as SimulationTypes.ConveyorEventsSchema)?.speed
};
// REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points, speed: backendEventData.speed };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'Vehicle' && eventData) {
const backendEventData = {
type: 'Vehicle',
points: eventData.points
};
// REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'StaticMachine' && eventData) {
const backendEventData = {
type: 'StaticMachine',
points: eventData.points,
};
// REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
} else if (eventData.type === 'ArmBot' && eventData) {
const backendEventData = {
type: 'ArmBot',
points: eventData.points,
};
// REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// obj.userData.modelId,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// false,
// true,
// { type: backendEventData.type, points: backendEventData.points }
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
eventData: { type: backendEventData.type, points: backendEventData.points },
socketId: socket.id,
};
const newEventData: any = { type: backendEventData.type, points: backendEventData.points };
newEventData.modeluuid = newFloorItem.modeluuid;
newEventData.modelName = newFloorItem.modelname;
newEventData.position = newFloorItem.position;
newEventData.rotation = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).map(event =>
event.modeluuid === newFloorItem.modeluuid
? { ...event, ...newEventData }
: event
);
return updatedEvents;
});
socket.emit("v2:model-asset:add", data);
}
} else {
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
}
itemsGroupRef.current.add(obj);
}
});
toast.success("Object rotated!");
itemsData.current = [];
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // No need to return anything, as this component is used for its side effects
}
export default RotateControls

View File

@@ -0,0 +1,211 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore()
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
event.preventDefault();
addPastedObjects();
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+C" && movedObjects.length === 0 && rotatedObjects.length === 0) {
copySelection();
}
if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, floorItems, rotatedObjects]);
useFrame(() => {
if (pastedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
});
const copySelection = () => {
if (selectedAssets.length > 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
clone.position.copy(asset.position);
return clone;
});
setCopiedObjects(newClones);
toast.info("Objects copied!");
}
};
const pasteCopiedObjects = () => {
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
const newClones = copiedObjects.map((obj: THREE.Object3D) => {
const clone = obj.clone();
clone.position.copy(obj.position);
return clone;
});
selectionGroup.current.add(...newClones);
setpastedObjects([...newClones]);
setSelectedAssets([...newClones]);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
};
const addPastedObjects = () => {
if (pastedObjects.length === 0) return;
pastedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
obj.userData.modeluuid = newFloorItem.modeluuid;
itemsGroupRef.current.add(obj);
}
});
toast.success("Object added!");
clearSelection();
};
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // No visible output, but the component handles copy-paste functionality
};
export default CopyPasteControls;

View File

@@ -0,0 +1,189 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
event.preventDefault();
addDuplicatedAssets();
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects]);
useFrame(() => {
if (duplicatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
});
const duplicateSelection = () => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
clone.position.copy(asset.position);
return clone;
});
selectionGroup.current.add(...newClones);
setDuplicatedObjects(newClones);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
}
}
};
const addDuplicatedAssets = () => {
if (duplicatedObjects.length === 0) return;
duplicatedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
obj.userData.modeluuid = newFloorItem.modeluuid;
itemsGroupRef.current.add(obj);
}
});
toast.success("Object duplicated!");
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // This component does not render any UI
};
export default DuplicationControls;

View File

@@ -0,0 +1,240 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef<Types.FloorItems>([]);
useEffect(() => {
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeMovedAssets();
}
if (!isMoving && movedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
movedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setMovedObjects([]);
itemsData.current = [];
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
if (keyCombination === "G") {
if (selectedAssets.length > 0) {
moveAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
movedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setMovedObjects([]);
itemsData.current = [];
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]);
const gridSize = 0.25;
const moveSpeed = 0.25;
const isGridSnap = false;
useFrame(() => {
if (movedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
let targetX = point.x;
let targetZ = point.z;
if (isGridSnap) {
targetX = Math.round(point.x / gridSize) * gridSize;
targetZ = Math.round(point.z / gridSize) * gridSize;
}
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current.getWorldPosition(position);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (position.x - selectionGroup.current.position.x),
selectionGroup.current.position.y,
targetZ - (position.z - selectionGroup.current.position.z)
),
moveSpeed
);
} else {
const box = new THREE.Box3();
movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (center.x - selectionGroup.current.position.x),
selectionGroup.current.position.y,
targetZ - (center.z - selectionGroup.current.position.z)
),
moveSpeed
);
}
}
}
});
const moveAssets = () => {
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
setFloorItems(updatedItems);
setMovedObjects(selectedAssets);
selectedAssets.forEach((asset: any) => { selectionGroup.current.attach(asset); });
}
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;
movedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
itemsGroupRef.current.add(obj);
}
});
toast.success("Object moved!");
itemsData.current = [];
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // No need to return anything, as this component is used for its side effects
}
export default MoveControls

View File

@@ -0,0 +1,241 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef<Types.FloorItems>([]);
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
useEffect(() => {
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && rotatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeRotatedAssets();
}
if (!isMoving && rotatedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setRotatedObjects([]);
itemsData.current = [];
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return;
if (event.key.toLowerCase() === "r") {
if (selectedAssets.length > 0) {
rotateAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setRotatedObjects([]);
itemsData.current = [];
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, rotatedObjects, movedObjects]);
useFrame(() => {
if (rotatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point && prevPointerPosition.current) {
const box = new THREE.Box3();
rotatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
const delta = new THREE.Vector3().subVectors(point, center);
const prevPointerPosition3D = new THREE.Vector3(prevPointerPosition.current.x, 0, prevPointerPosition.current.y);
const angle = Math.atan2(delta.z, delta.x) - Math.atan2(prevPointerPosition3D.z - center.z, prevPointerPosition3D.x - center.x);
selectionGroup.current.rotation.y += -angle;
selectionGroup.current.position.sub(center);
selectionGroup.current.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
selectionGroup.current.position.add(center);
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
}
});
const rotateAssets = () => {
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
setFloorItems(updatedItems);
const box = new THREE.Box3();
selectedAssets.forEach((asset: any) => box.expandByObject(asset));
const center = new THREE.Vector3();
box.getCenter(center);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
selectedAssets.forEach((asset: any) => {
selectionGroup.current.attach(asset);
});
setRotatedObjects(selectedAssets);
};
const placeRotatedAssets = () => {
if (rotatedObjects.length === 0) return;
rotatedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
const worldQuaternion = new THREE.Quaternion();
obj.getWorldPosition(worldPosition);
obj.getWorldQuaternion(worldQuaternion);
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
obj.quaternion.copy(worldQuaternion);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v2:model-asset:add", data);
itemsGroupRef.current.add(obj);
}
});
toast.success("Object rotated!");
itemsData.current = [];
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return null; // No need to return anything, as this component is used for its side effects
}
export default RotateControls

View File

@@ -3,12 +3,11 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
import { SelectionHelper } from "./selectionHelper";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView, } from "../../../../store/store";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView, } from "../../../../store/store";
import BoundingBox from "./boundingBoxHelper";
import { toast } from "react-toastify";
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import * as SimulationTypes from "../../../../types/simulationTypes";
import DuplicationControls from "./duplicationControls";
import CopyPasteControls from "./copyPasteControls";
@@ -21,7 +20,6 @@ const SelectionControls: React.FC = () => {
const itemsGroupRef = useRef<THREE.Group | undefined>(undefined);
const selectionGroup = useRef() as Types.RefGroup;
const { toggleView } = useToggleView();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
const [rotatedObjects, setRotatedObjects] = useState<THREE.Object3D[]>([]);
@@ -199,203 +197,6 @@ const SelectionControls: React.FC = () => {
setDuplicatedObjects([]);
setSelectedAssets([]);
};
const updateBackend = async (updatedPaths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
if (updatedPaths.length === 0) return;
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "";
updatedPaths.forEach(async (updatedPath) => {
if (updatedPath.type === "Conveyor") {
// await setEventApi(
// organization,
// updatedPath.modeluuid,
// { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed }
// );
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: {
type: "Conveyor",
points: updatedPath.points,
speed: updatedPath.speed,
},
};
socket.emit("v2:model-asset:updateEventData", data);
} else if (updatedPath.type === "Vehicle") {
// await setEventApi(
// organization,
// updatedPath.modeluuid,
// { type: "Vehicle", points: updatedPath.points }
// );
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: { type: "Vehicle", points: updatedPath.points },
};
socket.emit("v2:model-asset:updateEventData", data);
} else if (updatedPath.type === "StaticMachine") {
// await setEventApi(
// organization,
// updatedPath.modeluuid,
// { type: "StaticMachine", points: updatedPath.points }
// );
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: { type: "StaticMachine", points: updatedPath.points },
};
socket.emit("v2:model-asset:updateEventData", data);
} else if (updatedPath.type === "ArmBot") {
// await setEventApi(
// organization,
// updatedPath.modeluuid,
// { type: "ArmBot", points: updatedPath.points }
// );
const data = {
organization: organization,
modeluuid: updatedPath.modeluuid,
eventData: { type: "ArmBot", points: updatedPath.points },
};
socket.emit("v2:model-asset:updateEventData", data);
}
});
};
const removeConnections = (deletedModelUUIDs: string[]) => {
const deletedPointUUIDs = new Set<string>();
simulationStates.forEach(state => {
if (deletedModelUUIDs.includes(state.modeluuid)) {
if (state.type === "Conveyor" && state.points) {
state.points.forEach(point => {
deletedPointUUIDs.add(point.uuid);
});
} else if (state.points && 'uuid' in state.points) {
deletedPointUUIDs.add(state.points.uuid);
}
}
});
const updatedStates = simulationStates.map((state) => {
// Handle Conveyor
if (state.type === "Conveyor") {
const updatedConveyor: SimulationTypes.ConveyorEventsSchema = {
...state,
points: state.points.map((point) => {
return {
...point,
connections: {
...point.connections,
targets: point.connections.targets.filter(
(target) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
};
}),
};
return updatedConveyor;
}
// Handle Vehicle
else if (state.type === "Vehicle") {
const updatedVehicle: SimulationTypes.VehicleEventsSchema = {
...state,
points: {
...state.points,
connections: {
...state.points.connections,
targets: state.points.connections.targets.filter(
(target) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
},
};
return updatedVehicle;
}
// Handle StaticMachine
else if (state.type === "StaticMachine") {
const updatedStaticMachine: SimulationTypes.StaticMachineEventsSchema =
{
...state,
points: {
...state.points,
connections: {
...state.points.connections,
targets: state.points.connections.targets.filter(
(target) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
},
};
return updatedStaticMachine;
}
// Handle ArmBot
else if (state.type === "ArmBot") {
const updatedArmBot: SimulationTypes.ArmBotEventsSchema = {
...state,
points: {
...state.points,
connections: {
...state.points.connections,
targets: state.points.connections.targets.filter(
(target: any) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
actions: {
...state.points.actions,
processes: state.points.actions.processes?.filter((process) => {
// Check if trigger is from deleted model
const matchedStates = simulationStates.filter((s) => deletedModelUUIDs.includes(s.modeluuid));
if (matchedStates.length > 0) {
if (matchedStates[0]?.type === "StaticMachine") {
const trigPoints = matchedStates[0]?.points;
if (process.triggerId === trigPoints?.triggers?.uuid) {
return false;
}
} else if (matchedStates[0]?.type === "Conveyor") {
const trigPoints = matchedStates[0]?.points;
if (Array.isArray(trigPoints)) {
const nonEmptyTriggers = trigPoints.filter((point) => point && point.triggers && point.triggers.length > 0);
const allTriggerUUIDs = nonEmptyTriggers.flatMap((point) => point.triggers).map((trigger) => trigger.uuid);
if (allTriggerUUIDs.includes(process.triggerId)) {
return false;
}
}
}
}
// Check if startPoint or endPoint is from deleted model
if (deletedPointUUIDs.has(process.startPoint) || deletedPointUUIDs.has(process.endPoint)) {
return false;
}
return true;
}),
},
},
};
return updatedArmBot;
}
return state;
});
const filteredStates = updatedStates.filter((state) => !deletedModelUUIDs.includes(state.modeluuid));
updateBackend(filteredStates);
setSimulationStates(filteredStates);
};
const deleteSelection = () => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
@@ -439,17 +240,9 @@ const SelectionControls: React.FC = () => {
}
});
setSimulationStates((prevEvents: (| SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).filter((event) => event.modeluuid !== selectedMesh.uuid);
return updatedEvents;
});
itemsGroupRef.current?.remove(selectedMesh);
});
const allUUIDs = selectedAssets.map((val: any) => val.uuid);
removeConnections(allUUIDs);
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
setFloorItems(updatedItems);
}

View File

@@ -1,120 +0,0 @@
import { TransformControls } from "@react-three/drei";
import * as THREE from "three";
import { useSelectedFloorItem, useObjectPosition, useObjectScale, useObjectRotation, useTransformMode, useFloorItems, useSocketStore, useActiveTool } from "../../../store/store";
import { useThree } from "@react-three/fiber";
import * as Types from '../../../types/world/worldTypes';
import { useEffect } from "react";
export default function TransformControl() {
const state = useThree();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { objectPosition, setObjectPosition } = useObjectPosition();
const { objectScale, setObjectScale } = useObjectScale();
const { objectRotation, setObjectRotation } = useObjectRotation();
const { transformMode, setTransformMode } = useTransformMode();
const { floorItems, setFloorItems } = useFloorItems();
const { activeTool, setActiveTool } = useActiveTool();
const { socket } = useSocketStore();
function handleObjectChange() {
if (selectedFloorItem && transformMode) {
setObjectPosition(selectedFloorItem.position);
setObjectScale(selectedFloorItem.scale);
setObjectRotation({
x: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.x),
y: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.y),
z: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.z),
});
}
}
function handleMouseUp() {
if (selectedFloorItem) {
setObjectPosition(selectedFloorItem.position);
setObjectScale(selectedFloorItem.scale);
setObjectRotation({
x: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.x),
y: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.y),
z: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.z),
});
}
setFloorItems((prevItems: Types.FloorItems) => {
if (!prevItems) {
return
}
let updatedItem: any = null;
const updatedItems = prevItems.map((item) => {
if (item.modeluuid === selectedFloorItem?.uuid) {
updatedItem = {
...item,
position: [selectedFloorItem.position.x, selectedFloorItem.position.y, selectedFloorItem.position.z,] as [number, number, number],
rotation: { x: selectedFloorItem.rotation.x, y: selectedFloorItem.rotation.y, z: selectedFloorItem.rotation.z, },
};
return updatedItem;
}
return item;
});
if (updatedItem && selectedFloorItem) {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setFloorItemApi(
// organization,
// updatedItem.modeluuid,
// updatedItem.modelname,
// [selectedFloorItem.position.x, selectedFloorItem.position.y, selectedFloorItem.position.z,],
// { "x": selectedFloorItem.rotation.x, "y": selectedFloorItem.rotation.y, "z": selectedFloorItem.rotation.z },
// false,
// true,
// );
//SOCKET
const data = {
organization: organization,
modeluuid: updatedItem.modeluuid,
modelname: updatedItem.modelname,
position: [selectedFloorItem.position.x, selectedFloorItem.position.y, selectedFloorItem.position.z],
rotation: { "x": selectedFloorItem.rotation.x, "y": selectedFloorItem.rotation.y, "z": selectedFloorItem.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id
}
socket.emit('v2:model-asset:add', data);
}
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
}
useEffect(() => {
if (activeTool === "Add pillar" || activeTool === "Delete") {
if (state.controls) {
const target = (state.controls as any).getTarget(new THREE.Vector3());
(state.controls as any).setTarget(target.x, 0, target.z, true);
}
setSelectedFloorItem(null);
{
setObjectPosition({ x: undefined, y: undefined, z: undefined });
setObjectScale({ x: undefined, y: undefined, z: undefined });
setObjectRotation({ x: undefined, y: undefined, z: undefined });
}
}
}, [activeTool]);
return (
<>
{(selectedFloorItem && transformMode) &&
<TransformControls
object={selectedFloorItem}
mode={transformMode}
onObjectChange={handleObjectChange}
onMouseUp={handleMouseUp}
/>
}
</>
);
}

View File

@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react";
import * as CONSTANTS from '../../../types/world/worldConstants';
export default function Sun() {
const savedTheme: string | null = localStorage.getItem("theme");
const { elevation, setElevation } = useElevation();
const { sunPosition, setSunPosition } = useSunPosition();
const { azimuth, setAzimuth } = useAzimuth();
@@ -28,7 +29,7 @@ export default function Sun() {
return (
<>
{(azimuth !== undefined && elevation !== undefined) && (
{(azimuth !== undefined && elevation !== undefined && savedTheme !== "dark") && (
<>
<Sky
distance={CONSTANTS.skyConfig.skyDistance}

View File

@@ -3,8 +3,6 @@ import { EffectComposer, N8AO, Outline } from "@react-three/postprocessing";
import { BlendFunction } from "postprocessing";
import {
useDeletableFloorItem,
useSelectedActionSphere,
useSelectedPath,
useSelectedWallItem,
useSelectedFloorItem,
} from "../../../store/store";
@@ -16,8 +14,6 @@ export default function PostProcessing() {
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { selectedActionSphere } = useSelectedActionSphere();
const { selectedPath } = useSelectedPath();
function flattenChildren(children: any[]) {
const allChildren: any[] = [];
@@ -89,36 +85,6 @@ export default function PostProcessing() {
xRay={true}
/>
)}
{selectedActionSphere && (
<Outline
selection={[selectedActionSphere.points]}
selectionLayer={10}
width={1000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={10}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={0x6f42c1}
hiddenEdgeColor={0x6f42c1}
blur={true}
xRay={true}
/>
)}
{selectedPath && (
<Outline
selection={flattenChildren(selectedPath.group.children)}
selectionLayer={10}
width={1000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={10}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={0x6f42c1}
hiddenEdgeColor={0x6f42c1}
blur={true}
xRay={true}
/>
)}
</EffectComposer>
</>
);

View File

@@ -1,62 +1,32 @@
import { useMemo } from "react";
import { Canvas } from "@react-three/fiber";
import { Environment, KeyboardControls, Stars } from "@react-three/drei";
import { KeyboardControls } from "@react-three/drei";
import World from "./world/world";
import Controls from "./controls/controls";
import TransformControl from "./controls/transformControls";
import PostProcessing from "./postProcessing/postProcessing";
import Sun from "./environment/sky";
import CamModelsGroup from "../collaboration/collabCams";
import Shadows from "./environment/shadow";
import MqttEvents from "../../services/factoryBuilder/mqtt/mqttEvents";
import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr";
import SelectionControls from "./controls/selection/selectionControls";
import MeasurementTool from "./tools/measurementTool";
import Builder from "../builder/builder";
import Visualization from "../visualization/visualization";
import Setup from "./setup/setup";
import Simulation from "../simulation/simulation";
// import Simulation from "./simulationtemp/simulation";
import ZoneCentreTarget from "../visualization/functions/zoneCameraTarget";
import Dropped3dWidgets from "../../modules/visualization/widgets/3d/Dropped3dWidget";
import ZoneAssets from "../visualization/zoneAssets";
export default function Scene() {
const map = useMemo(
() => [
const map = useMemo(() => [
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
{ name: "right", keys: ["ArrowRight", "d", "D"] },
],
[]
);
const savedTheme: string | null = localStorage.getItem("theme");
{ name: "right", keys: ["ArrowRight", "d", "D"] },],
[]);
return (
<KeyboardControls map={map}>
<Canvas
eventPrefix="client"
gl={{ powerPreference: "high-performance", antialias: true }}
onContextMenu={(e) => {
e.preventDefault();
}}
>
<Dropped3dWidgets />
<Controls />
<TransformControl />
<SelectionControls />
<MeasurementTool />
<World />
<ZoneCentreTarget />
<ZoneAssets />
<Canvas eventPrefix="client" gl={{ powerPreference: "high-performance", antialias: true }} onContextMenu={(e) => { e.preventDefault(); }}>
<Setup />
<Builder />
<Simulation />
<PostProcessing />
{savedTheme !== "dark" ? <Sun /> : <></>}
<Shadows />
<CamModelsGroup />
<MqttEvents />
<Environment files={background} environmentIntensity={1.5} />
<Visualization />
</Canvas>
</KeyboardControls>
);

View File

@@ -0,0 +1,25 @@
import Sun from '../environment/sky'
import Shadows from '../environment/shadow'
import PostProcessing from '../postProcessing/postProcessing'
import Controls from '../controls/controls';
import { Environment } from '@react-three/drei'
import background from "../../../assets/hdr/mudroadpuresky2k.hdr";
function Setup() {
return (
<>
<Controls />
<Sun />
<Shadows />
<PostProcessing />
<Environment files={background} environmentIntensity={1.5} />
</>
)
}
export default Setup

View File

@@ -1,87 +0,0 @@
import React, { useEffect, useState } from "react";
import { useThree } from "@react-three/fiber";
import useModuleStore from "../../../store/useModuleStore";
import { useSimulationStates } from "../../../store/store";
import * as SimulationTypes from '../../../types/simulationTypes';
import { ArmbotInstances } from "./ArmBotInstances";
import { useResetButtonStore } from "../../../store/usePlayButtonStore";
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
interface StaticMachineState {
uuid: string;
status: string;
actions: { uuid: string; name: string; buffer: number; material: string; };
machineTriggerId: string;
connectedArmBot: string;
}
interface ArmBotProps {
armBots: ArmBotState[];
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
}
const ArmBot = ({ armBots, setArmBots, setStaticMachines }: ArmBotProps) => {
const { activeModule } = useModuleStore();
const { scene } = useThree();
const { simulationStates } = useSimulationStates();
const { isReset } = useResetButtonStore();
useEffect(() => {
const filtered = simulationStates.filter((s): s is SimulationTypes.ArmBotEventsSchema => s.type === "ArmBot");
const initialStates: ArmBotState[] = filtered
.filter(bot => bot.points.connections.targets.length > 0)
.map(bot => ({
uuid: bot.modeluuid,
position: bot.position,
rotation: bot.rotation,
status: "idle",
material: "default",
triggerId: '',
actions: bot.points.actions,
connections: bot.points.connections,
isActive: false
}));
setArmBots(initialStates);
}, [simulationStates, isReset]);
useEffect(() => {
armBots.forEach((bot) => {
const object = scene.getObjectByProperty("uuid", bot.uuid);
if (object) {
object.visible = activeModule !== "simulation";
}
});
}, [scene, activeModule, armBots]);
return (
<>
{activeModule === "simulation" &&
armBots.map((bot, i) => (
<ArmbotInstances
key={i}
index={i}
armBot={bot}
setArmBots={setArmBots}
setStaticMachines={setStaticMachines}
/>
))}
</>
);
};
export default ArmBot;

View File

@@ -1,90 +0,0 @@
import IkInstances from "./IkInstances";
import armModel from "../../../assets/gltf-glb/rigged/ik_arm_4.glb";
import { useEffect, useState } from "react";
import { useThree } from "@react-three/fiber";
import { Vector3 } from "three";
interface Process {
triggerId: string;
startPoint?: Vector3;
endPoint?: Vector3;
speed: number;
}
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
interface StaticMachineState {
uuid: string;
status: string;
actions: { uuid: string; name: string; buffer: number; material: string; };
machineTriggerId: string;
connectedArmBot: string;
}
interface ArmbotInstancesProps {
index: number;
armBot: ArmBotState;
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
}
export const ArmbotInstances: React.FC<ArmbotInstancesProps> = ({ index, armBot, setArmBots, setStaticMachines }) => {
const { scene } = useThree();
const [processes, setProcesses] = useState<Process[]>([]);
useEffect(() => {
if (armBot.actions.processes.length > 0) {
const mappedProcesses = armBot.actions.processes.map((process) => {
return {
triggerId: process.triggerId,
startPoint: scene.getObjectByProperty('uuid', process.startPoint)?.getWorldPosition(new Vector3()),
endPoint: scene.getObjectByProperty('uuid', process.endPoint)?.getWorldPosition(new Vector3()),
speed: armBot.actions.speed
};
});
setProcesses(mappedProcesses);
} else {
setProcesses([]);
}
}, [armBot, scene]);
const updateArmBotStatus = (status: string) => {
setArmBots((prevArmBots) => {
return prevArmBots.map(bot => {
if (bot.uuid === armBot.uuid) {
return { ...bot, status, triggerId: status === 'idle' ? '' : armBot.triggerId };
}
return bot;
});
});
};
return (
<IkInstances
key={index}
uuid={armBot.uuid}
selectedTrigger={armBot.triggerId}
modelUrl={armModel}
position={armBot.position}
rotation={armBot.rotation}
processes={processes}
armBot={armBot}
setArmBots={setArmBots}
setStaticMachines={setStaticMachines}
updateArmBotStatus={updateArmBotStatus}
/>
);
};

View File

@@ -1,379 +0,0 @@
import { useEffect, useMemo, useState, useRef } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore";
import { useSimulationStates } from "../../../store/store";
import MaterialInstances from "./MaterialInstances";
import { Line } from "react-chartjs-2";
import { QuadraticBezierLine } from "@react-three/drei";
interface StaticMachineState {
uuid: string;
status: string;
actions: { uuid: string; name: string; buffer: number; material: string; };
machineTriggerId: string;
connectedArmBot: string;
}
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
type IKAnimationControllerProps = {
ikSolver: any;
processes: {
triggerId: string;
startPoint: THREE.Vector3;
endPoint: THREE.Vector3;
speed: number;
}[];
selectedTrigger: string;
targetBoneName: string;
uuid: string;
logStatus: (status: string) => void;
groupRef: React.RefObject<THREE.Group>;
armBot: ArmBotState;
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
updateArmBotStatus: (status: string) => void;
}
const IKAnimationController = ({
ikSolver,
processes,
selectedTrigger,
targetBoneName,
uuid,
logStatus,
groupRef,
armBot,
setArmBots,
setStaticMachines,
updateArmBotStatus
}: IKAnimationControllerProps) => {
const [progress, setProgress] = useState(0);
const [initialProgress, setInitialProgress] = useState(0);
const [needsInitialMovement, setNeedsInitialMovement] = useState(true);
const [isInitializing, setIsInitializing] = useState(true);
const restSpeed = 0.1;
const restPosition = new THREE.Vector3(0, 2, 1.6);
const { isPlaying } = usePlayButtonStore();;
const statusRef = useRef("idle");
const { simulationStates } = useSimulationStates();
const { isReset } = useResetButtonStore();
const initialCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null);
const initialStartPositionRef = useRef<THREE.Vector3 | null>(null);
useEffect(() => {
setProgress(0);
}, [selectedTrigger]);
useEffect(() => {
setProgress(0);
setNeedsInitialMovement(true);
setInitialProgress(0);
setIsInitializing(true);
}, [isReset]);
useEffect(() => {
if (ikSolver) {
const targetBone = ikSolver.mesh.skeleton.bones.find(
(b: any) => b.name === targetBoneName
);
if (targetBone) {
initialStartPositionRef.current = targetBone.position.clone();
calculateInitialCurve(targetBone.position);
logStatus(`[Arm ${uuid}] Initializing IK system, starting position: ${targetBone.position.toArray()}`);
}
}
}, [ikSolver]);
const calculateInitialCurve = (startPosition: THREE.Vector3) => {
const direction = new THREE.Vector3().subVectors(restPosition, startPosition);
const distance = direction.length();
direction.normalize();
const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
const midHeight = 0.5;
const tiltAmount = 1;
const mid = new THREE.Vector3()
.addVectors(startPosition, restPosition)
.multiplyScalar(0.5)
.add(perpendicular.clone().multiplyScalar(distance * 0.3 * tiltAmount))
.add(new THREE.Vector3(0, midHeight, 0));
initialCurveRef.current = new THREE.CatmullRomCurve3([
startPosition,
new THREE.Vector3().lerpVectors(startPosition, mid, 0.33),
mid,
new THREE.Vector3().lerpVectors(mid, restPosition, 0.66),
restPosition
]);
};
const processedCurves = useMemo(() => {
if (!isPlaying) return [];
return processes.map(process => {
const localStart = groupRef.current?.worldToLocal(process.startPoint.clone());
const localEnd = groupRef.current?.worldToLocal(process.endPoint.clone());
if (!localStart || !localEnd) return null;
const midPoint = new THREE.Vector3(
(localStart.x + localEnd.x) / 2,
Math.max(localStart.y, localEnd.y) + 1,
(localStart.z + localEnd.z) / 2
);
const restToStartCurve = new THREE.CatmullRomCurve3([
restPosition,
new THREE.Vector3().lerpVectors(restPosition, localStart, 0.5),
localStart
]);
const processCurve = new THREE.CatmullRomCurve3([
localStart,
midPoint,
localEnd
]);
const endToRestCurve = new THREE.CatmullRomCurve3([
localEnd,
new THREE.Vector3().lerpVectors(localEnd, restPosition, 0.5),
restPosition
]);
return {
triggerId: process.triggerId,
restToStartCurve,
processCurve,
endToRestCurve,
speed: process.speed,
totalDistance:
restPosition.distanceTo(localStart) +
localStart.distanceTo(localEnd) +
localEnd.distanceTo(restPosition)
};
}).filter(Boolean);
}, [processes, isPlaying]);
const activeProcess = useMemo(() => {
if (!selectedTrigger) return null;
return processedCurves.find(p => p?.triggerId === selectedTrigger);
}, [processedCurves, selectedTrigger]);
// Initial movement to rest position
useFrame((_, delta) => {
if (!ikSolver || !needsInitialMovement || !isInitializing || !initialCurveRef.current) return;
const targetBone = ikSolver.mesh.skeleton.bones.find(
(b: any) => b.name === targetBoneName
);
if (!targetBone) return;
setInitialProgress((prev) => {
const next = prev + delta * 0.5;
if (next >= 1) {
targetBone.position.copy(restPosition);
setNeedsInitialMovement(false);
setIsInitializing(false);
return 1;
}
targetBone.position.copy(initialCurveRef.current!.getPoint(next));
return next;
});
ikSolver.update();
});
// Main animation loop
useFrame((_, delta) => {
if (isInitializing || !isPlaying || !selectedTrigger || !activeProcess || !ikSolver) return;
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBoneName);
if (!bone) return;
const {
restToStartCurve,
processCurve,
endToRestCurve,
speed,
totalDistance
} = activeProcess;
// Calculate current segment and progress
const restToStartDist = restPosition.distanceTo(restToStartCurve.points[2]);
const processDist = processCurve.getLength();
const endToRestDist = endToRestCurve.getLength();
const restToStartEnd = restToStartDist / totalDistance;
const processEnd = (restToStartDist + processDist) / totalDistance;
setProgress(prev => {
let currentStatus = statusRef.current;
let currentPosition: THREE.Vector3;
const newProgress = Math.min(prev + delta * ((currentStatus === 'returning to rest') ? restSpeed : speed), 1);
if (newProgress < restToStartEnd) {
// Moving from rest to start position
currentStatus = "moving to start";
const segmentProgress = newProgress / restToStartEnd;
currentPosition = restToStartCurve.getPoint(segmentProgress);
} else if (newProgress < processEnd) {
// Processing - moving from start to end
currentStatus = "processing";
const segmentProgress = (newProgress - restToStartEnd) / (processEnd - restToStartEnd);
currentPosition = processCurve.getPoint(segmentProgress);
if (statusRef.current !== "processing") {
updateConveyorOrStaticMachineStatusOnStart(selectedTrigger);
}
} else {
// Returning to rest position
currentStatus = "returning to rest";
const segmentProgress = (newProgress - processEnd) / (1 - processEnd);
currentPosition = endToRestCurve.getPoint(segmentProgress);
}
// Update status if changed
if (currentStatus !== statusRef.current) {
statusRef.current = currentStatus;
// updateArmBotStatus(currentStatus);
logStatus(`[Arm ${uuid}] Status: ${currentStatus}`);
}
// Only trigger when the entire animation is complete (newProgress === 1)
if (newProgress === 1 && currentStatus === "returning to rest") {
updateConveyorOrStaticMachineStatusOnEnd(selectedTrigger);
}
bone.position.copy(currentPosition);
ikSolver.update();
return newProgress;
});
});
const updateConveyorOrStaticMachineStatusOnStart = (selectedTrigger: string) => {
const currentProcess = processes.find(p => p.triggerId === selectedTrigger);
if (currentProcess) {
const triggerId = currentProcess.triggerId;
const startPoint = armBot.actions.processes.find((process) => process.triggerId === triggerId)?.startPoint;
const matchedMachine = simulationStates.find((state) => {
if (state.type === "Conveyor") {
return (state).points.some(
(point) => point.uuid === startPoint
);
} else if (state.type === "StaticMachine") {
return state.points.uuid === startPoint;
}
return false;
});
if (matchedMachine) {
if (matchedMachine.type === "Conveyor") {
logStatus(`[Arm ${uuid}] start point which is a conveyor (${matchedMachine.modelName})`);
} else {
logStatus(`[Arm ${uuid}] started form start point which is a static machine (${matchedMachine.modelName})`);
}
setTimeout(() => {
if (matchedMachine.type === "StaticMachine") {
updateArmBotStatus('dropping');
}
if (matchedMachine.type === "Conveyor") {
updateArmBotStatus('picking');
}
}, 0);
}
}
}
const updateConveyorOrStaticMachineStatusOnEnd = (selectedTrigger: string) => {
const currentProcess = processes.find(p => p.triggerId === selectedTrigger);
if (currentProcess) {
const triggerId = currentProcess.triggerId;
const endPoint = armBot.actions.processes.find((process) => process.triggerId === triggerId)?.endPoint;
const matchedMachine = simulationStates.find((state) => {
if (state.type === "Conveyor") {
return (state).points.some(
(point) => point.uuid === endPoint
);
} else if (state.type === "StaticMachine") {
return state.points.uuid === endPoint;
}
return false;
});
if (matchedMachine) {
if (matchedMachine.type === "Conveyor") {
logStatus(`[Arm ${uuid}] Reached end point which is a conveyor (${matchedMachine.modelName})`);
} else {
logStatus(`[Arm ${uuid}] Reached end point which is a static machine (${matchedMachine.modelName})`);
}
setTimeout(() => {
if (matchedMachine.type === "StaticMachine") {
setStaticMachines((machines) => {
return machines.map((machine) => {
if (machine.uuid === matchedMachine.modeluuid) {
return { ...machine, status: "running" };
} else {
return machine;
}
});
});
updateArmBotStatus('idle');
}
if (matchedMachine.type === "Conveyor") {
setArmBots((prev) =>
prev.map((arm) => {
if (arm.uuid === uuid && arm.isActive === true) {
return {
...arm,
isActive: false,
status: "idle",
};
}
else {
return arm;
}
})
);
}
}, 0);
}
}
}
return (
<>
<MaterialInstances
statusRef={statusRef}
ikSolver={ikSolver}
targetBoneName={targetBoneName}
/>
</>
);
};
export default IKAnimationController;

View File

@@ -1,150 +0,0 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useLoader } from "@react-three/fiber";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver";
import IKAnimationController from "./IKAnimationController";
import { TransformControls } from "@react-three/drei";
interface StaticMachineState {
uuid: string;
status: string;
actions: { uuid: string; name: string; buffer: number; material: string; };
machineTriggerId: string;
connectedArmBot: string;
}
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
const IkInstances = ({
uuid,
selectedTrigger,
modelUrl,
processes,
position,
rotation,
armBot,
setArmBots,
setStaticMachines,
updateArmBotStatus
}: {
uuid: string;
selectedTrigger: string;
modelUrl: string;
processes: any;
position: [number, number, number];
rotation: [number, number, number];
armBot: ArmBotState;
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
updateArmBotStatus: (status: string) => void;
}) => {
const [ikSolver, setIkSolver] = useState<any>(null);
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
const draco = new DRACOLoader();
draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/");
loader.setDRACOLoader(draco);
});
const cloned = useMemo(() => clone(gltf.scene), [gltf]);
const groupRef = useRef<any>(null);
const targetBoneName = "Target";
const skinnedMeshName = "link_0";
useEffect(() => {
if (!gltf) return;
const OOI: any = {};
cloned.traverse((n: any) => {
if (n.name === targetBoneName) OOI.Target_Bone = n;
if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n;
});
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
const iks = [
{
target: 7,
effector: 6,
links: [
{
index: 5,
enabled: true,
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0),
},
{
index: 4,
enabled: true,
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
rotationMax: new THREE.Vector3(0, 0, 0),
},
{
index: 3,
enabled: true,
rotationMin: new THREE.Vector3(0, 0, 0),
rotationMax: new THREE.Vector3(2, 0, 0),
},
{ index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) },
{ index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) },
],
},
];
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
setIkSolver(solver);
const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05);
// groupRef.current.add(helper);
}, [gltf]);
const logStatus = (status: string) => {
// console.log(status);
}
return (
<>
<group
ref={groupRef}
position={position}
rotation={rotation}
>
<primitive
object={cloned}
scale={[1, 1, 1]}
name={`arm-bot`}
/>
</group>
<IKAnimationController
ikSolver={ikSolver}
processes={processes}
selectedTrigger={selectedTrigger}
targetBoneName={targetBoneName}
uuid={uuid}
logStatus={logStatus}
groupRef={groupRef}
armBot={armBot}
setArmBots={setArmBots}
setStaticMachines={setStaticMachines}
updateArmBotStatus={updateArmBotStatus}
/>
</>
);
};
export default IkInstances;

View File

@@ -1,31 +0,0 @@
import React from 'react';
import * as THREE from 'three';
import { Box } from '@react-three/drei';
type MaterialInstancesProps = {
statusRef: React.RefObject<string>;
ikSolver: any;
targetBoneName: string;
};
function MaterialInstances({
statusRef,
ikSolver,
targetBoneName
}: MaterialInstancesProps) {
if (!ikSolver) return null;
const targetBone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBoneName);
if (!targetBone) return null;
const worldPos = new THREE.Vector3();
targetBone.getWorldPosition(worldPos);
return (
<Box args={[0.5, 0.5, 0.5]} position={worldPos} visible={statusRef.current === 'processing'}>
<meshStandardMaterial color="orange" />
</Box>
);
}
export default MaterialInstances;

File diff suppressed because it is too large Load Diff

View File

@@ -1,415 +0,0 @@
import * as THREE from "three";
import * as SimulationTypes from "../../../types/simulationTypes";
import { useRef, useState, useEffect, useMemo } from "react";
import { Sphere, TransformControls } from "@react-three/drei";
import {
useEditingPoint,
useEyeDropMode,
useIsConnecting,
usePreviewPosition,
useRenderDistance,
useSelectedActionSphere,
useSelectedPath,
useSimulationStates,
} from "../../../store/store";
import { useFrame, useThree } from "@react-three/fiber";
import { useSubModuleStore } from "../../../store/useModuleStore";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt";
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObject<THREE.Group>; }) {
const { isPlaying } = usePlayButtonStore();
const { renderDistance } = useRenderDistance();
const { setSubModule } = useSubModuleStore();
const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere();
const { eyeDropMode, setEyeDropMode } = useEyeDropMode();
const { editingPoint, setEditingPoint } = useEditingPoint();
const { previewPosition, setPreviewPosition } = usePreviewPosition();
const { raycaster, camera, pointer, gl } = useThree();
const { setSelectedPath } = useSelectedPath();
const { simulationStates, setSimulationStates } = useSimulationStates();
const { isConnecting } = useIsConnecting();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const groupRefs = useRef<{ [key: string]: THREE.Group }>({});
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
const isMovingRef = useRef(false);
const transformRef = useRef<any>(null);
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
useEffect(() => {
setTransformMode(null);
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedActionSphere) return;
if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
}
if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedActionSphere]);
useFrame(() => {
Object.values(groupRefs.current).forEach((group) => {
if (group) {
const distance = new THREE.Vector3(
...group.position.toArray()
).distanceTo(camera.position);
group.visible = ((distance <= renderDistance) && !isPlaying);
}
});
});
useFrame(() => {
if (eyeDropMode) {
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
setPreviewPosition({ x: point.x, y: point.z });
}
} else {
setPreviewPosition(null);
}
});
useEffect(() => {
if (!camera) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
const onPointerDown = () => {
isMovingRef.current = false;
};
const onPointerMove = () => {
isMovingRef.current = true;
};
const onPointerUp = (event: PointerEvent) => {
if (
!isMovingRef.current &&
eyeDropMode &&
event.button === 0 &&
previewPosition
) {
event.preventDefault();
if (editingPoint) {
handlePointUpdate(editingPoint, previewPosition.x, previewPosition.y);
setEditingPoint(null);
setEyeDropMode(false);
}
}
};
if (eyeDropMode) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
};
}, [eyeDropMode, editingPoint, previewPosition]);
const updateBackend = async (updatedPath: SimulationTypes.VehicleEventsSchema | undefined) => {
if (!updatedPath) return;
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "";
await setEventApi(
organization,
updatedPath.modeluuid,
{ type: "Vehicle", points: updatedPath.points }
);
}
const handlePointUpdate = (pointType: "start" | "end", x: number, z: number) => {
if (!selectedActionSphere?.points?.uuid) return;
const updatedPaths = simulationStates.map((path) => {
if (path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid) {
return {
...path,
points: {
...path.points,
actions: {
...path.points.actions,
[pointType]: { ...path.points.actions[pointType], x: x, y: z, },
},
},
};
}
return path;
});
const updatedPath = updatedPaths.find((path): path is SimulationTypes.VehicleEventsSchema => path.type === "Vehicle" && path.points.uuid === selectedActionSphere.points.uuid);
updateBackend(updatedPath);
setSimulationStates(updatedPaths);
};
return (
<group visible={!isPlaying} name="simulation-simulationStates-group" ref={pathsGroupRef}>
{simulationStates.map((path) => {
if (path.type === "Conveyor") {
const points = path.points.map(
(point) => new THREE.Vector3(...point.position)
);
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({
path,
group: groupRefs.current[path.modeluuid],
});
setSelectedActionSphere(null);
setTransformMode(null);
setSubModule("mechanics");
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule("properties");
}}
>
{path.points.map((point, index) => (
<Sphere
key={point.uuid}
uuid={point.uuid}
position={point.position}
args={[0.15, 32, 32]}
name="events-sphere"
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
points: sphereRefs.current[point.uuid],
});
setSubModule("mechanics");
setSelectedPath(null);
}}
userData={{ points, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule("properties");
setSelectedActionSphere(null);
}}
>
<meshStandardMaterial
color={index === 0 ? "orange" : index === path.points.length - 1 ? "blue" : "green"}
/>
</Sphere>
))}
{points.slice(0, -1).map((point, index) => {
const nextPoint = points[index + 1];
const segmentCurve = new THREE.CatmullRomCurve3([point, nextPoint,]);
const tubeGeometry = new THREE.TubeGeometry(segmentCurve, 20, 0.1, 16, false);
return (
<mesh name="event-connection-tube" key={`tube-${index}`} geometry={tubeGeometry}>
<meshStandardMaterial transparent opacity={0.9} color="red" />
</mesh>
);
})}
</group>
);
} else if (path.type === "Vehicle") {
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({
path,
group: groupRefs.current[path.modeluuid],
});
setSelectedActionSphere(null);
setTransformMode(null);
setSubModule("mechanics");
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule("properties");
}}
>
<Sphere
key={path.points.uuid}
uuid={path.points.uuid}
position={path.points.position}
args={[0.15, 32, 32]}
name="events-sphere"
ref={(el) => (sphereRefs.current[path.points.uuid] = el!)}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
points: sphereRefs.current[path.points.uuid],
});
setSubModule("mechanics");
setSelectedPath(null);
}}
userData={{ points: path.points, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule("properties");
setSelectedActionSphere(null);
}}
>
<meshStandardMaterial color="purple" />
</Sphere>
</group>
);
} else if (path.type === "StaticMachine") {
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({
path,
group: groupRefs.current[path.modeluuid],
});
setSelectedActionSphere(null);
setTransformMode(null);
setSubModule("mechanics");
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule("properties");
}}
>
<Sphere
key={path.points.uuid}
uuid={path.points.uuid}
position={path.points.position}
args={[0.15, 32, 32]}
name="events-sphere"
ref={(el) => (sphereRefs.current[path.points.uuid] = el!)}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
points: sphereRefs.current[path.points.uuid],
});
setSubModule("mechanics");
setSelectedPath(null);
}}
userData={{ points: path.points, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule("properties");
setSelectedActionSphere(null);
}}
>
<meshStandardMaterial color="yellow" />
</Sphere>
</group>
);
} else if (path.type === "ArmBot") {
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({
path,
group: groupRefs.current[path.modeluuid],
});
setSelectedActionSphere(null);
setTransformMode(null);
setSubModule("mechanics");
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule("properties");
}}
>
<Sphere
key={path.points.uuid}
uuid={path.points.uuid}
position={path.points.position}
args={[0.15, 32, 32]}
name="events-sphere"
ref={(el) => (sphereRefs.current[path.points.uuid] = el!)}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
points: sphereRefs.current[path.points.uuid],
});
setSubModule("mechanics");
setSelectedPath(null);
}}
userData={{ points: path.points, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule("properties");
setSelectedActionSphere(null);
}}
>
<meshStandardMaterial color="pink" />
</Sphere>
</group>
);
}
return null;
})}
{selectedActionSphere && transformMode && (
<TransformControls
ref={transformRef}
object={selectedActionSphere.points}
mode={transformMode}
/>
)}
</group>
);
}
export default PathCreation;

View File

@@ -1,611 +0,0 @@
import React, { useRef, useEffect, useMemo, useCallback } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three-stdlib";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import crate from "../../../assets/gltf-glb/crate_box.glb";
import { useProcessAnimation } from "./useProcessAnimations";
import ProcessObject from "./processObject";
import { ProcessData } from "./types";
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: {
uuid: string;
name: string;
speed: number;
processes: { triggerId: string; startPoint: string; endPoint: string }[];
};
isActive?: boolean;
}
interface ProcessContainerProps {
processes: ProcessData[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
agvRef: any;
MaterialRef: any;
armBots: ArmBotState[];
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
}
const ProcessAnimator: React.FC<ProcessContainerProps> = ({
processes,
setProcesses,
agvRef,
MaterialRef,
armBots,
setArmBots,
}) => {
const gltf = useLoader(GLTFLoader, crate) as GLTF;
const groupRef = useRef<THREE.Group>(null);
const tempStackedObjectsRef = useRef<Record<string, boolean>>({});
const {
animationStates,
setAnimationStates,
clockRef,
elapsedBeforePauseRef,
speedRef,
debugRef,
findSpawnPoint,
createSpawnedObject,
handlePointActions,
hasNonInheritActions,
getPointDataForAnimationIndex,
processes: processedProcesses,
checkAndCountTriggers,
} = useProcessAnimation(processes, setProcesses, agvRef, armBots, setArmBots);
const baseMaterials = useMemo(
() => ({
Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
Default: new THREE.MeshStandardMaterial(),
}),
[]
);
useEffect(() => {
// Update material references for all spawned objects
Object.entries(animationStates).forEach(([processId, processState]) => {
Object.keys(processState.spawnedObjects).forEach((objectId) => {
const entry = { processId, objectId };
const materialType =
processState.spawnedObjects[objectId]?.currentMaterialType;
if (!materialType) {
return;
}
const matRefArray = MaterialRef.current;
// Find existing material group
const existing = matRefArray.find(
(entryGroup: { material: string; objects: any[] }) =>
entryGroup.material === materialType
);
if (existing) {
// Check if this processId + objectId already exists
const alreadyExists = existing.objects.some(
(o: any) =>
o.processId === entry.processId && o.objectId === entry.objectId
);
if (!alreadyExists) {
existing.objects.push(entry);
}
} else {
// Create new group for this material type
matRefArray.push({
material: materialType,
objects: [entry],
});
}
});
});
}, [animationStates, MaterialRef, agvRef]);
// In processAnimator.tsx - only the relevant spawn logic part that needs fixes
// Add this function to ProcessAnimator component
const isConnectedToActiveArmBot = useCallback(
(processId: any) => {
// Check if any active armbot is connected to this process
return armBots.some((armbot) => {
if (!armbot.isActive) return false;
// Check if this armbot is connected to the process
return armbot.connections?.targets?.some((connection) => {
// Find the process that owns this modelUUID
const connectedProcess = processes.find((p) =>
p.paths?.some((path) => path.modeluuid === connection.modelUUID)
);
return connectedProcess?.id === processId;
});
});
},
[armBots, processes]
);
// First useFrame for spawn logic
useFrame(() => {
// Spawn logic frame
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => {
const newStates = { ...prev };
processedProcesses.forEach((process) => {
const processState = newStates[process.id];
if (!processState) return;
// Check connection status
const isConnected = isConnectedToActiveArmBot(process.id);
if (processState.isProcessDelaying) {
// Existing delay handling logic...
return;
}
if (isConnected) {
newStates[process.id] = {
...processState,
nextSpawnTime: Infinity, // Prevent future spawns
};
return;
}
const spawnPoint = findSpawnPoint(process);
if (!spawnPoint || !spawnPoint.actions) {
// console.log(
// `Process ${process.id} has no valid spawn point or actions`
// );
return;
}
const spawnAction = spawnPoint.actions.find(
(a) => a.isUsed && a.type === "Spawn"
);
if (!spawnAction) {
return;
}
const spawnInterval =
typeof spawnAction.spawnInterval === "number"
? spawnAction.spawnInterval
: parseFloat(spawnAction.spawnInterval || "0") || 0;
// Check if this is a zero interval spawn and we already spawned an object
if (
spawnInterval === 0 &&
processState.hasSpawnedZeroIntervalObject === true
) {
return; // Don't spawn more objects for zero interval
}
const effectiveSpawnInterval = spawnInterval / speedRef.current;
if (currentTime >= processState.nextSpawnTime) {
const objectId = `obj-${process.id}-${processState.objectIdCounter}`;
const newObject = createSpawnedObject(
process,
currentTime,
spawnAction.material || "Default",
spawnPoint,
baseMaterials
);
// Initialize state properly to ensure animation
newObject.state = {
...newObject.state,
isAnimating: true,
isDelaying: false,
delayComplete: false,
progress: 0.005, // Start with tiny progress to ensure animation begins
};
// Update state with the new object and flag for zero interval
newStates[process.id] = {
...processState,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: newObject,
},
objectIdCounter: processState.objectIdCounter + 1,
nextSpawnTime: currentTime + effectiveSpawnInterval,
// Mark that we've spawned an object for zero interval case
hasSpawnedZeroIntervalObject:
spawnInterval === 0
? true
: processState.hasSpawnedZeroIntervalObject,
};
}
});
return newStates;
});
});
// Second useFrame for animation logic
useFrame((_, delta) => {
// Animation logic frame
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => {
const newStates = { ...prev };
processedProcesses.forEach((process) => {
const processState = newStates[process.id];
if (!processState) {
return;
}
// Check connection status with debugging
const isConnected = isConnectedToActiveArmBot(process.id);
// console.log(
// `Process ${process.id} animation - connected:`,
// isConnected
// );
if (isConnected) {
// Stop all animations when connected to active arm bot
newStates[process.id] = {
...processState,
spawnedObjects: Object.entries(processState.spawnedObjects).reduce(
(acc, [id, obj]) => ({
...acc,
[id]: {
...obj,
state: {
...obj.state,
isAnimating: false, // Stop animation
isDelaying: false, // Clear delays
delayComplete: false, // Reset delays
progress: 0, // Reset progress
},
},
}),
{}
),
};
return;
}
// Process delay handling
if (processState.isProcessDelaying) {
const effectiveDelayTime =
processState.processDelayDuration / speedRef.current;
if (
currentTime - processState.processDelayStartTime >=
effectiveDelayTime
) {
// console.log(
// `Process ${process.id} delay completed, resuming animation`
// );
newStates[process.id] = {
...processState,
isProcessDelaying: false,
spawnedObjects: Object.entries(
processState.spawnedObjects
).reduce(
(acc, [id, obj]) => ({
...acc,
[id]: {
...obj,
state: {
...obj.state,
isDelaying: false,
delayComplete: true,
isAnimating: true,
progress:
obj.state.progress === 0 ? 0.005 : obj.state.progress,
},
},
}),
{}
),
};
return;
} else {
return;
}
}
// Ensure we have a valid path to follow
const path =
process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) ||
[];
if (path.length < 2) {
// console.log(
// `Process ${process.id} has insufficient path points: ${path.length}`
// );
return;
}
const updatedObjects = { ...processState.spawnedObjects };
let animationOccurring = false; // Track if any animation is happening
Object.entries(processState.spawnedObjects).forEach(
([objectId, obj]) => {
if (!obj.visible) {
return;
}
const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
if (!currentRef) {
// console.log(
// `No reference for object ${objectId}, skipping animation`
// );
return;
}
// Initialize position for new objects
if (
obj.position &&
obj.state.currentIndex === 0 &&
obj.state.progress === 0
) {
currentRef.position.copy(obj.position);
}
const stateRef = obj.state;
// Ensure animation state is properly set for objects
if (!stateRef.isAnimating && !stateRef.isDelaying && !isConnected) {
stateRef.isAnimating = true;
stateRef.progress =
stateRef.progress > 0 ? stateRef.progress : 0.005;
}
// Handle delay logic
if (stateRef.isDelaying) {
const effectiveDelayTime =
stateRef.currentDelayDuration / speedRef.current;
if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) {
// console.log(
// `Delay complete for object ${objectId}, resuming animation`
// );
stateRef.isDelaying = false;
stateRef.delayComplete = true;
stateRef.isAnimating = true;
if (stateRef.progress === 0) {
stateRef.progress = 0.005;
}
const nextPointIdx = stateRef.currentIndex + 1;
if (nextPointIdx < path.length) {
const slightProgress = Math.max(stateRef.progress, 0.005);
currentRef.position.lerpVectors(
path[stateRef.currentIndex],
nextPointIdx < path.length
? path[nextPointIdx]
: path[stateRef.currentIndex],
slightProgress
);
}
} else {
updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
return;
}
}
// Skip non-animating objects
if (!stateRef.isAnimating) {
// console.log(
// `Object ${objectId} not animating, skipping animation updates`
// );
return;
}
animationOccurring = true; // Mark that animation is happening
// Handle point actions
const currentPointData = getPointDataForAnimationIndex(
process,
stateRef.currentIndex
);
// Handle point actions when first arriving at point
if (stateRef.progress === 0 && currentPointData?.actions) {
const shouldStop = handlePointActions(
process.id,
objectId,
currentPointData.actions,
currentTime,
processedProcesses,
baseMaterials
);
if (shouldStop) {
updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
return;
}
}
const nextPointIdx = stateRef.currentIndex + 1;
const isLastPoint = nextPointIdx >= path.length;
// Handle objects at the last point
if (isLastPoint) {
const isAgvPicking = agvRef.current.some(
(agv: any) =>
agv.processId === process.id && agv.status === "picking"
);
const shouldHide =
!currentPointData?.actions ||
!hasNonInheritActions(currentPointData.actions);
if (shouldHide) {
if (isAgvPicking) {
// console.log(
// `AGV picking at last point for object ${objectId}, hiding object`
// );
updatedObjects[objectId] = {
...obj,
visible: false,
state: {
...stateRef,
isAnimating: false,
},
};
} else {
tempStackedObjectsRef.current[objectId] = true;
updatedObjects[objectId] = {
...obj,
visible: true,
state: {
...stateRef,
isAnimating: true,
},
};
}
return;
}
}
// Handle stacked objects when AGV picks
if (tempStackedObjectsRef.current[objectId]) {
const isAgvPicking = agvRef.current.some(
(agv: any) =>
agv.processId === process.id && agv.status === "picking"
);
if (isAgvPicking) {
delete tempStackedObjectsRef.current[objectId];
updatedObjects[objectId] = {
...obj,
visible: false,
state: {
...stateRef,
isAnimating: false,
},
};
return;
}
}
// Handle normal animation progress for objects not at last point
if (!isLastPoint) {
const nextPoint = path[nextPointIdx];
const distance =
path[stateRef.currentIndex].distanceTo(nextPoint);
const effectiveSpeed = stateRef.speed * speedRef.current;
const movement = effectiveSpeed * delta;
// Ensure progress is always moving forward
if (stateRef.delayComplete && stateRef.progress < 0.01) {
stateRef.progress = 0.05;
stateRef.delayComplete = false;
// console.log(
// `Boosting progress for object ${objectId} after delay`
// );
} else {
stateRef.progress += movement / distance;
// console.log(
// `Object ${objectId} progress: ${stateRef.progress.toFixed(3)}`
// );
}
// Handle point transition
if (stateRef.progress >= 1) {
stateRef.currentIndex = nextPointIdx;
stateRef.progress = 0;
currentRef.position.copy(nextPoint);
// TRIGGER CHECK - When object arrives at new point
checkAndCountTriggers(
process.id,
objectId,
stateRef.currentIndex, // The new point index
processedProcesses,
currentTime
);
const newPointData = getPointDataForAnimationIndex(
process,
stateRef.currentIndex
);
// No action needed with newPointData here - will be handled in next frame
} else {
// Update position with lerp
currentRef.position.lerpVectors(
path[stateRef.currentIndex],
nextPoint,
stateRef.progress
);
}
}
updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
}
);
// Log if no animation is occurring when it should
if (!animationOccurring && !isConnected) {
// console.log(
// `Warning: No animation occurring for process ${process.id} despite not being connected`
// );
}
newStates[process.id] = {
...processState,
spawnedObjects: updatedObjects,
};
});
return newStates;
});
});
if (!processedProcesses || processedProcesses.length === 0) {
return null;
}
return (
<group ref={groupRef}>
{Object.entries(animationStates).flatMap(([processId, processState]) =>
Object.entries(processState.spawnedObjects)
.filter(([_, obj]) => obj.visible)
.map(([objectId, obj]) => {
const process = processedProcesses.find((p) => p.id === processId);
const renderAs = process?.renderAs || "custom";
return (
<ProcessObject
key={objectId}
objectId={objectId}
obj={obj}
renderAs={renderAs}
gltf={gltf}
/>
);
})
)}
</group>
);
};
export default ProcessAnimator;

View File

@@ -1,52 +0,0 @@
import React, { useState } from "react";
import ProcessCreator from "./processCreator";
import ProcessAnimator from "./processAnimator";
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
interface ProcessContainerProps {
processes: any[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
agvRef: any;
MaterialRef: any;
armBots: ArmBotState[];
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
}
const ProcessContainer: React.FC<ProcessContainerProps> = ({
processes,
setProcesses,
agvRef,
MaterialRef,
armBots,
setArmBots
}) => {
return (
<>
<ProcessCreator onProcessesCreated={setProcesses} />
<ProcessAnimator
processes={processes}
setProcesses={setProcesses}
agvRef={agvRef}
MaterialRef={MaterialRef}
armBots={armBots}
setArmBots={setArmBots}
/>
</>
);
};
export default ProcessContainer;

View File

@@ -1,492 +0,0 @@
import React, {
useEffect,
useMemo,
useState,
useCallback,
useRef,
} from "react";
import { useSimulationStates } from "../../../store/store";
import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import {
ArmBotEventsSchema,
ConveyorEventsSchema,
VehicleEventsSchema,
} from "../../../types/simulationTypes";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
// Type definitions
export interface PointAction {
uuid: string;
name: string;
type: string;
material: string;
delay: number | string;
spawnInterval: string | number;
isUsed: boolean;
}
export interface PointTrigger {
uuid: string;
bufferTime: number;
name: string;
type: string;
isUsed: boolean;
}
// Update the connections type in your interfaces
export interface PathPoint {
uuid: string;
position: [number, number, number];
actions: PointAction[];
triggers: PointTrigger[];
connections: {
targets: Array<{ modelUUID: string; pointUUID?: string }>;
};
}
export interface SimulationPath {
type: string;
modeluuid: string;
points: PathPoint[];
pathPosition: [number, number, number];
speed?: number;
isActive: boolean;
}
export interface ArmBot {
type: string;
modeluuid: string;
points: PathPoint[];
pathPosition: [number, number, number];
speed?: number;
isActive: boolean;
}
export interface Process {
id: string;
paths: SimulationPath[];
animationPath: THREE.Vector3[];
pointActions: PointAction[][];
pointTriggers: PointTrigger[][];
speed: number;
isActive: boolean;
}
interface ProcessCreatorProps {
onProcessesCreated: (processes: Process[]) => void;
}
// Convert event schemas to SimulationPath
function convertToSimulationPath(
path: ConveyorEventsSchema | VehicleEventsSchema | ArmBotEventsSchema
): SimulationPath {
const { modeluuid } = path;
// Normalized action handler
const normalizeAction = (action: any): PointAction => {
return { ...action }; // Return exact copy with no modifications
};
// Normalized trigger handler
const normalizeTrigger = (trigger: any): PointTrigger => {
return { ...trigger }; // Return exact copy with no modifications
};
if (path.type === "Conveyor") {
return {
type: path.type,
modeluuid,
points: path.points.map((point) => ({
uuid: point.uuid,
position: point.position,
actions: Array.isArray(point.actions)
? point.actions.map(normalizeAction)
: point.actions
? [normalizeAction(point.actions)]
: [],
triggers: Array.isArray(point.triggers)
? point.triggers.map(normalizeTrigger)
: point.triggers
? [normalizeTrigger(point.triggers)]
: [],
connections: {
targets: point.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
})),
},
})),
pathPosition: path.position,
speed:
typeof path.speed === "string"
? parseFloat(path.speed) || 1
: path.speed || 1,
isActive: false, // Added missing property
};
} else if (path.type === "ArmBot") {
return {
type: path.type,
modeluuid,
points: [
{
uuid: path.points.uuid,
position: path.points.position,
actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction)
: path.points.actions
? [normalizeAction(path.points.actions)]
: [],
triggers: Array.isArray(path.points.triggers)
? path.points.triggers.map(normalizeTrigger)
: path.points.triggers
? [normalizeTrigger(path.points.triggers)]
: [],
connections: {
targets: path.points.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
pointUUID: target.pointUUID, // Include if available
})),
},
},
],
pathPosition: path.position,
speed: path.points.actions?.speed || 1,
isActive: false,
};
} else {
// For vehicle paths, handle the case where triggers might not exist
return {
type: path.type,
modeluuid,
points: [
{
uuid: path.points.uuid,
position: path.points.position,
actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction)
: path.points.actions
? [normalizeAction(path.points.actions)]
: [],
triggers: [],
connections: {
targets: path.points.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
})),
},
},
],
pathPosition: path.position,
speed: path.points.speed || 1,
isActive: false,
};
}
}
// Helper function to create an empty process
const createEmptyProcess = (): Process => ({
id: `process-${Math.random().toString(36).substring(2, 11)}`,
paths: [],
animationPath: [],
pointActions: [],
pointTriggers: [], // Added point triggers array
speed: 1,
isActive: false,
});
// Enhanced connection checking function
function shouldReverseNextPath(
currentPath: SimulationPath,
nextPath: SimulationPath
): boolean {
if (nextPath.points.length !== 3) return false;
const currentLastPoint = currentPath.points[currentPath.points.length - 1];
const nextFirstPoint = nextPath.points[0];
const nextLastPoint = nextPath.points[nextPath.points.length - 1];
// Check if current last connects to next last (requires reversal)
const connectsToLast = currentLastPoint.connections.targets.some(
(target) =>
target.modelUUID === nextPath.modeluuid &&
nextLastPoint.connections.targets.some(
(t) => t.modelUUID === currentPath.modeluuid
)
);
// Check if current last connects to next first (no reversal needed)
const connectsToFirst = currentLastPoint.connections.targets.some(
(target) =>
target.modelUUID === nextPath.modeluuid &&
nextFirstPoint.connections.targets.some(
(t) => t.modelUUID === currentPath.modeluuid
)
);
// Only reverse if connected to last point and not to first point
return connectsToLast && !connectsToFirst;
}
// Check if a point has a spawn action
function hasSpawnAction(point: PathPoint): boolean {
return point.actions.some((action) => action.type.toLowerCase() === "spawn");
}
// Ensure spawn point is always at the beginning of the path
function ensureSpawnPointIsFirst(path: SimulationPath): SimulationPath {
if (path.points.length !== 3) return path;
// If the third point has spawn action and first doesn't, reverse the array
if (hasSpawnAction(path.points[2]) && !hasSpawnAction(path.points[0])) {
return {
...path,
points: [...path.points].reverse(),
};
}
return path;
}
// Updated path adjustment function
function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] {
if (paths.length < 1) return paths;
const adjustedPaths = [...paths];
// First ensure all paths have spawn points at the beginning
for (let i = 0; i < adjustedPaths.length; i++) {
adjustedPaths[i] = ensureSpawnPointIsFirst(adjustedPaths[i]);
}
// Then handle connections between paths
for (let i = 0; i < adjustedPaths.length - 1; i++) {
const currentPath = adjustedPaths[i];
const nextPath = adjustedPaths[i + 1];
if (shouldReverseNextPath(currentPath, nextPath)) {
const reversedPoints = [
nextPath.points[2],
nextPath.points[1],
nextPath.points[0],
];
adjustedPaths[i + 1] = {
...nextPath,
points: reversedPoints,
};
}
}
return adjustedPaths;
}
// Main hook for process creation
export function useProcessCreation() {
const { scene } = useThree();
const [processes, setProcesses] = useState<Process[]>([]);
const hasSpawnAction = useCallback((path: SimulationPath): boolean => {
if (path.type !== "Conveyor") return false;
return path.points.some((point) =>
point.actions.some((action) => action.type.toLowerCase() === "spawn")
);
}, []);
const createProcess = useCallback(
(paths: SimulationPath[]): Process => {
if (!paths || paths.length === 0) {
return createEmptyProcess();
}
const animationPath: THREE.Vector3[] = [];
const pointActions: PointAction[][] = [];
const pointTriggers: PointTrigger[][] = []; // Added point triggers collection
const processSpeed = paths[0]?.speed || 1;
for (const path of paths) {
for (const point of path.points) {
if (path.type === "Conveyor") {
const obj = scene.getObjectByProperty("uuid", point.uuid);
if (!obj) {
console.warn(`Object with UUID ${point.uuid} not found in scene`);
continue;
}
const position = obj.getWorldPosition(new THREE.Vector3());
animationPath.push(position.clone());
pointActions.push(point.actions);
pointTriggers.push(point.triggers); // Collect triggers for each point
}
}
}
return {
id: `process-${Math.random().toString(36).substring(2, 11)}`,
paths,
animationPath,
pointActions,
pointTriggers,
speed: processSpeed,
isActive: false,
};
},
[scene]
);
const getAllConnectedPaths = useCallback(
(
initialPath: SimulationPath,
allPaths: SimulationPath[],
visited: Set<string> = new Set()
): SimulationPath[] => {
const connectedPaths: SimulationPath[] = [];
const queue: SimulationPath[] = [initialPath];
visited.add(initialPath.modeluuid);
const pathMap = new Map<string, SimulationPath>();
allPaths.forEach((path) => pathMap.set(path.modeluuid, path));
while (queue.length > 0) {
const currentPath = queue.shift()!;
connectedPaths.push(currentPath);
// Process outgoing connections
for (const point of currentPath.points) {
for (const target of point.connections.targets) {
if (!visited.has(target.modelUUID)) {
const targetPath = pathMap.get(target.modelUUID);
if (targetPath) {
visited.add(target.modelUUID);
queue.push(targetPath);
}
}
}
}
// Process incoming connections
for (const [uuid, path] of pathMap) {
if (!visited.has(uuid)) {
const hasConnectionToCurrent = path.points.some((point) =>
point.connections.targets.some(
(t) => t.modelUUID === currentPath.modeluuid
)
);
if (hasConnectionToCurrent) {
visited.add(uuid);
queue.push(path);
}
}
}
}
return connectedPaths;
},
[]
);
const createProcessesFromPaths = useCallback(
(paths: SimulationPath[]): Process[] => {
if (!paths || paths.length === 0) return [];
const visited = new Set<string>();
const processes: Process[] = [];
const pathMap = new Map<string, SimulationPath>();
paths.forEach((path) => pathMap.set(path.modeluuid, path));
for (const path of paths) {
if (!visited.has(path.modeluuid) && hasSpawnAction(path)) {
const connectedPaths = getAllConnectedPaths(path, paths, visited);
const adjustedPaths = adjustPathPointsOrder(connectedPaths);
const process = createProcess(adjustedPaths);
processes.push(process);
}
}
return processes;
},
[createProcess, getAllConnectedPaths, hasSpawnAction]
);
return {
processes,
createProcessesFromPaths,
setProcesses,
};
}
const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
({ onProcessesCreated }) => {
const { simulationStates } = useSimulationStates();
const { createProcessesFromPaths } = useProcessCreation();
const prevPathsRef = useRef<SimulationPath[]>([]);
const prevProcessesRef = useRef<Process[]>([]);
const { isPlaying } = usePlayButtonStore();
const convertedPaths = useMemo((): SimulationPath[] => {
if (!simulationStates) return [];
return simulationStates.map((path) =>
convertToSimulationPath(
path as
| ConveyorEventsSchema
| VehicleEventsSchema
| ArmBotEventsSchema
)
);
}, [simulationStates]);
// Enhanced dependency tracking that includes action and trigger types
const pathsDependency = useMemo(() => {
if (!convertedPaths) return null;
return convertedPaths.map((path) => ({
id: path.modeluuid,
// Track all action types for each point
actionSignature: path.points
.map((point, index) =>
point.actions.map((action) => `${index}-${action.type}`).join("|")
)
.join(","),
// Track all trigger types for each point
triggerSignature: path.points
.map((point, index) =>
point.triggers
.map((trigger) => `${index}-${trigger.type}`)
.join("|")
)
.join(","),
connections: path.points
.flatMap((p: PathPoint) =>
p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID)
)
.join(","),
isActive: false,
}));
}, [convertedPaths]);
// Force process recreation when paths change
useEffect(() => {
if (!convertedPaths || convertedPaths.length === 0) {
if (prevProcessesRef.current.length > 0) {
onProcessesCreated([]);
prevProcessesRef.current = [];
}
return;
}
// Always regenerate processes if the pathsDependency has changed
// This ensures action and trigger type changes will be detected
const newProcesses = createProcessesFromPaths(convertedPaths);
prevPathsRef.current = convertedPaths;
// Always update processes when action or trigger types change
onProcessesCreated(newProcesses);
prevProcessesRef.current = newProcesses;
}, [
pathsDependency, // This now includes action and trigger types
onProcessesCreated,
convertedPaths,
createProcessesFromPaths,
]);
return null;
}
);
export default ProcessCreator;

View File

@@ -1,58 +0,0 @@
import React, { useMemo } from "react";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import { SpawnedObject } from "./types";
interface ProcessObjectProps {
objectId: string;
obj: SpawnedObject;
renderAs?: "box" | "custom";
gltf?: GLTF;
}
const ProcessObject: React.FC<ProcessObjectProps> = ({
objectId,
obj,
renderAs = "custom",
gltf,
}) => {
const renderedObject = useMemo(() => {
if (renderAs === "box") {
return (
<mesh
key={objectId}
ref={obj.ref as React.RefObject<THREE.Mesh>}
material={obj.material}
position={obj.position}
>
<boxGeometry args={[1, 1, 1]} />
</mesh>
);
}
if (gltf?.scene) {
const clonedScene = gltf.scene.clone();
clonedScene.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.material = obj.material;
}
});
return (
<group
key={objectId}
ref={obj.ref as React.RefObject<THREE.Group>}
position={obj.position}
>
<primitive object={clonedScene} />
</group>
);
}
return null;
}, [objectId, obj, renderAs, gltf]);
return renderedObject;
};
export default ProcessObject;

View File

@@ -1,86 +0,0 @@
import * as THREE from "three";
export interface Trigger {
uuid: string;
name: string;
type: string;
bufferTime: number;
isUsed: boolean;
}
export interface PointAction {
uuid: string;
name: string;
type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
objectType: string;
material: string;
delay: string | number;
spawnInterval: string | number;
isUsed: boolean;
hitCount?: number;
}
export interface ProcessPoint {
uuid: string;
position: number[];
rotation: number[];
actions: PointAction[];
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
triggers?: Trigger[];
}
export interface ProcessPath {
modeluuid: string;
modelName: string;
points: ProcessPoint[];
pathPosition: number[];
pathRotation: number[];
speed: number;
type: "Conveyor" | "Vehicle" | "ArmBot";
isActive: boolean
}
export interface ProcessData {
id: string;
paths: ProcessPath[];
animationPath: { x: number; y: number; z: number }[];
pointActions: PointAction[][];
speed: number;
customMaterials?: Record<string, THREE.Material>;
renderAs?: "box" | "custom";
pointTriggers: [];
}
export interface AnimationState {
currentIndex: number;
progress: number;
isAnimating: boolean;
speed: number;
isDelaying: boolean;
delayStartTime: number;
currentDelayDuration: number;
delayComplete: boolean;
currentPathIndex: number;
}
export interface SpawnedObject {
ref: React.RefObject<THREE.Group | THREE.Mesh>;
state: AnimationState;
visible: boolean;
material: THREE.Material;
spawnTime: number;
currentMaterialType: string;
position: THREE.Vector3;
}
export interface ProcessAnimationState {
spawnedObjects: { [objectId: string]: SpawnedObject };
nextSpawnTime: number;
objectIdCounter: number;
isProcessDelaying: boolean;
processDelayStartTime: number;
processDelayDuration: number;
hasSpawnedZeroIntervalObject?: boolean;
}

View File

@@ -1,671 +0,0 @@
import { useCallback, useEffect, useRef, useState } from "react";
import * as THREE from "three";
import {
ProcessData,
ProcessAnimationState,
SpawnedObject,
AnimationState,
ProcessPoint,
PointAction,
Trigger,
} from "./types";
import {
useAnimationPlaySpeed,
usePauseButtonStore,
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import { usePlayAgv, useSimulationStates } from "../../../store/store";
interface ArmBotProcess {
triggerId: string;
startPoint: string;
endPoint: string;
}
// Enhanced ProcessAnimationState with trigger tracking
interface EnhancedProcessAnimationState extends ProcessAnimationState {
triggerCounts: Record<string, number>;
triggerLogs: Array<{
timestamp: number;
pointId: string;
objectId: string;
triggerId: string;
}>;
}
interface ProcessContainerProps {
processes: ProcessData[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
agvRef: any;
}
interface PlayAgvState {
playAgv: Record<string, any>;
setPlayAgv: (data: any) => void;
}
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
export const useProcessAnimation = (
processes: ProcessData[],
setProcesses: React.Dispatch<React.SetStateAction<any[]>>,
agvRef: any,
armBots: ArmBotState[],
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>
) => {
// State and refs initialization
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore();
const debugRef = useRef<boolean>(false);
const clockRef = useRef<THREE.Clock>(new THREE.Clock());
const pauseTimeRef = useRef<number>(0);
const elapsedBeforePauseRef = useRef<number>(0);
const animationStatesRef = useRef<Record<string, EnhancedProcessAnimationState>>({});
const { speed } = useAnimationPlaySpeed();
const prevIsPlaying = useRef<boolean | null>(null);
const [internalResetFlag, setInternalResetFlag] = useState(false);
const [animationStates, setAnimationStates] = useState<Record<string, EnhancedProcessAnimationState>>({});
const speedRef = useRef<number>(speed);
const { PlayAgv, setPlayAgv } = usePlayAgv();
const { simulationStates } = useSimulationStates();
// Effect hooks
useEffect(() => {
speedRef.current = speed;
}, [speed]);
useEffect(() => {
if (prevIsPlaying.current !== null || !isPlaying) {
setAnimationStates({});
}
prevIsPlaying.current = isPlaying;
}, [isPlaying]);
useEffect(() => {
animationStatesRef.current = animationStates;
}, [animationStates]);
// Reset handler
useEffect(() => {
if (isReset) {
setInternalResetFlag(true);
setIsPlaying(false);
setIsPaused(false);
setAnimationStates({});
animationStatesRef.current = {};
clockRef.current = new THREE.Clock();
elapsedBeforePauseRef.current = 0;
pauseTimeRef.current = 0;
setReset(false);
setTimeout(() => {
setInternalResetFlag(false);
setIsPlaying(true);
}, 0);
}
}, [isReset, setReset, setIsPlaying, setIsPaused]);
// Pause handler
useEffect(() => {
if (isPaused) {
pauseTimeRef.current = clockRef.current.getElapsedTime();
} else if (pauseTimeRef.current > 0) {
const pausedDuration = clockRef.current.getElapsedTime() - pauseTimeRef.current;
elapsedBeforePauseRef.current += pausedDuration;
}
}, [isPaused]);
// Initialize animation states with trigger tracking
useEffect(() => {
if (isPlaying && !internalResetFlag) {
const newStates: Record<string, EnhancedProcessAnimationState> = {};
processes.forEach((process) => {
const triggerCounts: Record<string, number> = {};
// Initialize trigger counts for all On-Hit triggers
process.paths?.forEach((path) => {
path.points?.forEach((point) => {
point.triggers?.forEach((trigger: Trigger) => {
if (trigger.type === "On-Hit" && trigger.isUsed) {
triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0;
}
});
});
});
newStates[process.id] = {
spawnedObjects: {},
nextSpawnTime: 0,
objectIdCounter: 0,
isProcessDelaying: false,
processDelayStartTime: 0,
processDelayDuration: 0,
triggerCounts,
triggerLogs: [],
};
});
setAnimationStates(newStates);
animationStatesRef.current = newStates;
clockRef.current.start();
}
}, [isPlaying, processes, internalResetFlag]);
useEffect(() => {
if (isPlaying && !internalResetFlag) {
const newStates: Record<string, EnhancedProcessAnimationState> = {};
// Initialize AGVs for each process first
processes.forEach((process) => {
// Find all vehicle paths for this process
const vehiclePaths = process.paths?.filter(
(path) => path.type === "Vehicle"
) || [];
// Initialize AGVs for each vehicle path
vehiclePaths.forEach((vehiclePath) => {
if (vehiclePath.points?.length > 0) {
const vehiclePoint = vehiclePath.points[0];
const action = vehiclePoint.actions?.[0];
const maxHitCount = action?.hitCount;
const vehicleId = vehiclePath.modeluuid;
const processId = process.id;
// Check if this AGV already exists
const existingAgv = agvRef.current.find(
(v: any) => v.vehicleId === vehicleId && v.processId === processId
);
if (!existingAgv) {
// Initialize the AGV in a stationed state
agvRef.current.push({
processId,
vehicleId,
maxHitCount: maxHitCount || 0,
isActive: false,
hitCount: 0,
status: 'stationed',
lastUpdated: 0
});
}
}
});
// Then initialize trigger counts as before
const triggerCounts: Record<string, number> = {};
process.paths?.forEach((path) => {
path.points?.forEach((point) => {
point.triggers?.forEach((trigger: Trigger) => {
if (trigger.type === "On-Hit" && trigger.isUsed) {
triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0;
}
});
});
});
newStates[process.id] = {
spawnedObjects: {},
nextSpawnTime: 0,
objectIdCounter: 0,
isProcessDelaying: false,
processDelayStartTime: 0,
processDelayDuration: 0,
triggerCounts,
triggerLogs: [],
};
});
setAnimationStates(newStates);
animationStatesRef.current = newStates;
clockRef.current.start();
}
}, [isPlaying, processes, internalResetFlag]);
// Helper functions
const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
for (const path of process.paths || []) {
for (const point of path.points || []) {
const spawnAction = point.actions?.find(
(a) => a.isUsed && a.type === "Spawn"
);
if (spawnAction) {
return point;
}
}
}
return null;
};
const findAnimationPathPoint = (
process: ProcessData,
spawnPoint: ProcessPoint
): THREE.Vector3 => {
if (process.animationPath && process.animationPath.length > 0) {
let pointIndex = 0;
for (const path of process.paths || []) {
for (let i = 0; i < (path.points?.length || 0); i++) {
const point = path.points?.[i];
if (point && point.uuid === spawnPoint.uuid) {
if (process.animationPath[pointIndex]) {
const p = process.animationPath[pointIndex];
return new THREE.Vector3(p.x, p.y, p.z);
}
}
pointIndex++;
}
}
}
return new THREE.Vector3(
spawnPoint.position[0],
spawnPoint.position[1],
spawnPoint.position[2]
);
};
// Optimized object creation
const createSpawnedObject = useCallback(
(
process: ProcessData,
currentTime: number,
materialType: string,
spawnPoint: ProcessPoint,
baseMaterials: Record<string, THREE.Material>
): SpawnedObject => {
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const spawnPosition = findAnimationPathPoint(process, spawnPoint);
const material =
processMaterials[materialType as keyof typeof processMaterials] ||
baseMaterials.Default;
return {
ref: { current: null },
state: {
currentIndex: 0,
progress: 0,
isAnimating: true,
speed: process.speed || 1,
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
},
visible: true,
material: material,
currentMaterialType: materialType,
spawnTime: currentTime,
position: spawnPosition,
};
},
[]
);
// Material handling
const handleMaterialSwap = useCallback(
(
processId: string,
objectId: string,
materialType: string,
processes: ProcessData[],
baseMaterials: Record<string, THREE.Material>
) => {
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || !processState.spawnedObjects[objectId])
return prev;
const process = processes.find((p) => p.id === processId);
if (!process) return prev;
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const newMaterial =
processMaterials[materialType as keyof typeof processMaterials];
if (!newMaterial) return prev;
return {
...prev,
[processId]: {
...processState,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
material: newMaterial,
currentMaterialType: materialType,
},
},
},
};
});
},
[]
);
// Point action handler with trigger counting
const handlePointActions = useCallback(
(
processId: string,
objectId: string,
actions: PointAction[] = [],
currentTime: number,
processes: ProcessData[],
baseMaterials: Record<string, THREE.Material>
): boolean => {
let shouldStopAnimation = false;
actions.forEach((action) => {
if (!action.isUsed) return;
switch (action.type) {
case "Delay":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || processState.isProcessDelaying) return prev;
const delayDuration =
typeof action.delay === "number"
? action.delay
: parseFloat(action.delay as string) || 0;
if (delayDuration > 0) {
return {
...prev,
[processId]: {
...processState,
isProcessDelaying: true,
processDelayStartTime: currentTime,
processDelayDuration: delayDuration,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
state: {
...processState.spawnedObjects[objectId].state,
isAnimating: false,
isDelaying: true,
delayStartTime: currentTime,
currentDelayDuration: delayDuration,
delayComplete: false,
},
},
},
},
};
}
return prev;
});
shouldStopAnimation = true;
break;
case "Despawn":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState) return prev;
const newSpawnedObjects = { ...processState.spawnedObjects };
delete newSpawnedObjects[objectId];
return {
...prev,
[processId]: {
...processState,
spawnedObjects: newSpawnedObjects,
},
};
});
shouldStopAnimation = true;
break;
case "Swap":
if (action.material) {
handleMaterialSwap(
processId,
objectId,
action.material,
processes,
baseMaterials
);
}
break;
default:
break;
}
});
return shouldStopAnimation;
},
[handleMaterialSwap]
);
const deferredArmBotUpdates = useRef<{ uuid: string; triggerId: string }[]>([]);
// Trigger counting system
const checkAndCountTriggers = useCallback(
(
processId: string,
objectId: string,
currentPointIndex: number,
processes: ProcessData[],
currentTime: number
) => {
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState) return prev;
const process = processes.find((p) => p.id === processId);
if (!process) return prev;
const point = getPointDataForAnimationIndex(process, currentPointIndex);
if (!point?.triggers) return prev;
const onHitTriggers = point.triggers.filter((t: Trigger) => t.type === "On-Hit" && t.isUsed);
if (onHitTriggers.length === 0) return prev;
let newTriggerCounts = { ...processState.triggerCounts };
const newTriggerLogs = [...processState.triggerLogs];
let shouldLog = false;
const vehiclePaths = process.paths.filter((path) => path.type === "Vehicle");
const armBotPaths = process.paths.filter((path) => path.type === "ArmBot");
const activeVehicles = vehiclePaths.filter((path) => {
const vehicleId = path.modeluuid;
const vehicleEntry = agvRef.current.find((v: any) => v.vehicleId === vehicleId && v.processId === processId);
return vehicleEntry?.isActive;
});
// Check if any ArmBot is active for this process
// const activeArmBots = armBotPaths.filter((path) => {
// const armBotId = path.modeluuid;
// const armBotEntry = armBots.find((a: any) => a.uuid === armBotId);
// return armBotEntry;
// });
// Only count triggers if no vehicles and no ArmBots are active for this process
if (activeVehicles.length === 0) {
onHitTriggers.forEach((trigger: Trigger) => {
const triggerKey = `${point.uuid}-${trigger.uuid}`;
newTriggerCounts[triggerKey] = (newTriggerCounts[triggerKey] || 0) + 1;
newTriggerLogs.push({ timestamp: currentTime, pointId: point.uuid, objectId, triggerId: trigger.uuid, });
const connections = point.connections?.targets || [];
connections.forEach((connection) => {
const connectedModelUUID = connection.modelUUID;
const isConveyor = simulationStates.find((state) => state.modeluuid === connectedModelUUID && state.type === "Conveyor");
if (!isConveyor) {
const matchingArmPath = armBotPaths.find((path) => path.modeluuid === connectedModelUUID);
if (matchingArmPath) {
deferredArmBotUpdates.current.push({
uuid: connectedModelUUID,
triggerId: trigger.uuid,
});
} else {
shouldLog = true;
}
}
});
});
}
let processTotalHits = Object.values(newTriggerCounts).reduce((a, b) => a + b, 0);
// Handle logic for vehicles when a trigger is hit
if (shouldLog) {
vehiclePaths.forEach((vehiclePath) => {
if (vehiclePath.points?.length > 0) {
const vehiclePoint = vehiclePath.points[0];
const action = vehiclePoint.actions?.[0];
const maxHitCount = action?.hitCount;
if (maxHitCount !== undefined) {
const vehicleId = vehiclePath.modeluuid;
let vehicleEntry = agvRef.current.find(
(v: any) =>
v.vehicleId === vehicleId && v.processId === processId
);
if (!vehicleEntry) {
vehicleEntry = {
processId,
vehicleId,
maxHitCount: maxHitCount,
isActive: false,
hitCount: 0,
status: "stationed",
};
agvRef.current.push(vehicleEntry);
}
if (!vehicleEntry.isActive) {
vehicleEntry.hitCount++;
vehicleEntry.lastUpdated = currentTime;
if (vehicleEntry.hitCount >= vehicleEntry.maxHitCount) {
vehicleEntry.isActive = true;
newTriggerCounts = {};
processTotalHits = 0;
}
}
}
}
});
}
return {
...prev,
[processId]: {
...processState,
triggerCounts: newTriggerCounts,
triggerLogs: newTriggerLogs,
totalHits: processTotalHits,
},
};
});
}, []);
useEffect(() => {
// console.log('deferredArmBotUpdates: ', deferredArmBotUpdates);
if (deferredArmBotUpdates.current.length > 0) {
const updates = [...deferredArmBotUpdates.current];
deferredArmBotUpdates.current = [];
setArmBots((prev) =>
prev.map((bot) => {
const update = updates.find((u) => u.uuid === bot.uuid);
return update
? { ...bot, triggerId: update.triggerId, isActive: true }
: bot;
})
);
}
}, [animationStates]);
// Utility functions
const hasNonInheritActions = useCallback(
(actions: PointAction[] = []): boolean => {
return actions.some(
(action) => action.isUsed && action.type !== "Inherit"
);
}, []);
const getPointDataForAnimationIndex = useCallback(
(process: ProcessData, index: number): ProcessPoint | null => {
if (!process.paths) return null;
let cumulativePoints = 0;
for (const path of process.paths) {
const pointCount = path.points?.length || 0;
if (index < cumulativePoints + pointCount) {
const pointIndex = index - cumulativePoints;
return path.points?.[pointIndex] || null;
}
cumulativePoints += pointCount;
}
return null;
},
[]
);
const getTriggerCounts = useCallback((processId: string) => {
return animationStatesRef.current[processId]?.triggerCounts || {};
}, []);
const getTriggerLogs = useCallback((processId: string) => {
return animationStatesRef.current[processId]?.triggerLogs || [];
}, []);
return {
animationStates,
setAnimationStates,
clockRef,
elapsedBeforePauseRef,
speedRef,
debugRef,
findSpawnPoint,
createSpawnedObject,
handlePointActions,
hasNonInheritActions,
getPointDataForAnimationIndex,
checkAndCountTriggers,
getTriggerCounts,
getTriggerLogs,
processes,
};
};

View File

@@ -1,74 +1,10 @@
import { useState, useRef } from "react";
import * as THREE from "three";
import PathCreation from "./path/pathCreation";
import PathConnector from "./path/pathConnector";
import useModuleStore from "../../store/useModuleStore";
import ProcessContainer from "./process/processContainer";
import Agv from "../builder/agv/agv";
import ArmBot from "./armbot/ArmBot";
import StaticMachine from "./staticMachine/staticMachine";
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
interface StaticMachineState {
uuid: string;
status: string;
actions: { uuid: string; name: string; buffer: number; material: string; };
machineTriggerId: string;
connectedArmBot: string;
}
import React from 'react'
function Simulation() {
const { activeModule } = useModuleStore();
const pathsGroupRef = useRef() as React.MutableRefObject<THREE.Group>;
const [armBots, setArmBots] = useState<ArmBotState[]>([]);
const [staticMachines, setStaticMachines] = useState<StaticMachineState[]>([]);
const [processes, setProcesses] = useState<any[]>([]);
const agvRef = useRef([]);
const MaterialRef = useRef([]);
return (
<>
{activeModule === "simulation" && (
<>
<PathCreation pathsGroupRef={pathsGroupRef} />
<PathConnector pathsGroupRef={pathsGroupRef} />
<ProcessContainer
processes={processes}
setProcesses={setProcesses}
agvRef={agvRef}
MaterialRef={MaterialRef}
armBots={armBots}
setArmBots={setArmBots}
/>
<Agv
processes={processes}
agvRef={agvRef}
MaterialRef={MaterialRef}
/>
</>
)}
<StaticMachine setArmBots={setArmBots} staticMachines={staticMachines} setStaticMachines={setStaticMachines} />
<ArmBot armBots={armBots} setArmBots={setArmBots} setStaticMachines={setStaticMachines} />
</>
);
)
}
export default Simulation;
export default Simulation

View File

@@ -1,409 +0,0 @@
// import { useMemo, useState } from 'react';
// import { useSelectedActionSphere, useToggleView, useSimulationStates, useSelectedPath, useStartSimulation, useDrawMaterialPath } from '../../store/store';
// import * as THREE from 'three';
// import useModuleStore from '../../store/useModuleStore';
// function SimulationUI() {
// const { ToggleView } = useToggleView();
// const { activeModule } = useModuleStore();
// const { startSimulation, setStartSimulation } = useStartSimulation();
// const { selectedActionSphere } = useSelectedActionSphere();
// const { selectedPath, setSelectedPath } = useSelectedPath();
// const { simulationStates, setSimulationStates } = useSimulationStates();
// const { drawMaterialPath, setDrawMaterialPath } = useDrawMaterialPath();
// const [activeButton, setActiveButton] = useState<string | null>(null);
// const handleAddAction = () => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) => {
// if (point.uuid === selectedActionSphere.points.uuid) {
// const actionIndex = point.actions.length;
// const newAction = {
// uuid: THREE.MathUtils.generateUUID(),
// name: `Action ${actionIndex + 1}`, // Assign action name based on index
// type: 'Inherit',
// material: 'Inherit',
// delay: 'Inherit',
// spawnInterval: 'Inherit',
// isUsed: false
// };
// return { ...point, actions: [...point.actions, newAction] };
// }
// return point;
// }),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleDeleteAction = (uuid: string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? { ...point, actions: point.actions.filter(action => action.uuid !== uuid) }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleActionSelect = (uuid: string, actionType: string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? {
// ...point,
// actions: point.actions.map((action) =>
// action.uuid === uuid ? { ...action, type: actionType } : action
// ),
// }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleMaterialSelect = (uuid: string, material: string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? {
// ...point,
// actions: point.actions.map((action) =>
// action.uuid === uuid ? { ...action, material } : action
// ),
// }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleDelayChange = (uuid: string, delay: number | string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? {
// ...point,
// actions: point.actions.map((action) =>
// action.uuid === uuid ? { ...action, delay } : action
// ),
// }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleSpawnIntervalChange = (uuid: string, spawnInterval: number | string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? {
// ...point,
// actions: point.actions.map((action) =>
// action.uuid === uuid ? { ...action, spawnInterval } : action
// ),
// }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleSpeedChange = (speed: number) => {
// if (!selectedPath) return;
// const updatedPaths = simulationStates.map((path) =>
// path.modeluuid === selectedPath.path.modeluuid ? { ...path, speed } : path
// );
// setSimulationStates(updatedPaths);
// setSelectedPath({ ...selectedPath, path: { ...selectedPath.path, speed } });
// };
// const handleAddTrigger = () => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) => {
// if (point.uuid === selectedActionSphere.points.uuid) {
// const triggerIndex = point.triggers.length;
// const newTrigger = {
// uuid: THREE.MathUtils.generateUUID(),
// name: `Trigger ${triggerIndex + 1}`, // Assign name based on index
// type: '',
// isUsed: false
// };
// return { ...point, triggers: [...point.triggers, newTrigger] };
// }
// return point;
// }),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleDeleteTrigger = (uuid: string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? { ...point, triggers: point.triggers.filter(trigger => trigger.uuid !== uuid) }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleTriggerSelect = (uuid: string, triggerType: string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? {
// ...point,
// triggers: point.triggers.map((trigger) =>
// trigger.uuid === uuid ? { ...trigger, type: triggerType } : trigger
// ),
// }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleResetPath = () => {
// if (!selectedPath) return;
// };
// const handleActionToggle = (uuid: string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? {
// ...point,
// actions: point.actions.map((action) => ({
// ...action,
// isUsed: action.uuid === uuid ? !action.isUsed : false,
// })),
// }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const handleTriggerToggle = (uuid: string) => {
// if (!selectedActionSphere) return;
// const updatedPaths = simulationStates.map((path) => ({
// ...path,
// points: path.points.map((point) =>
// point.uuid === selectedActionSphere.points.uuid
// ? {
// ...point,
// triggers: point.triggers.map((trigger) => ({
// ...trigger,
// isUsed: trigger.uuid === uuid ? !trigger.isUsed : false,
// })),
// }
// : point
// ),
// }));
// setSimulationStates(updatedPaths);
// };
// const selectedPoint = useMemo(() => {
// if (!selectedActionSphere) return null;
// return simulationStates.flatMap((path) => path.points).find((point) => point.uuid === selectedActionSphere.points.uuid);
// }, [selectedActionSphere, simulationStates]);
// const createPath = () => {
// setActiveButton(activeButton !== 'addMaterialPath' ? 'addMaterialPath' : null);
// setDrawMaterialPath(!drawMaterialPath);
// }
// return (
// <>
// {activeModule === "simulation" && (
// <div style={{ zIndex: 10, position: "fixed", width: '260px' }}>
// {!ToggleView && (
// <>
// <button
// onClick={() => setStartSimulation(!startSimulation)}
// style={{
// marginTop: "10px",
// background: startSimulation ? '#ff320e' : '',
// padding: "10px",
// borderRadius: "5px"
// }}
// >
// {startSimulation ? 'Stop Simulation' : 'Start Simulation'}
// </button>
// <div style={{ zIndex: "10", position: "relative" }}>
// {!ToggleView && <button onClick={createPath} style={{ marginTop: "10px", background: activeButton === 'addMaterialPath' ? '#ff320e' : '' }}> Add Material Path</button>}
// </div>
// {selectedPath && (
// <div style={{ marginTop: "10px" }}>
// <label>Path Speed:</label>
// <input
// style={{ width: '50px' }}
// type="number"
// value={selectedPath.path.speed}
// min="0.1"
// step="0.1"
// onChange={(e) => handleSpeedChange(parseFloat(e.target.value))}
// />
// </div>
// )}
// {selectedActionSphere && (
// <div style={{ marginTop: "10px" }}>
// <button onClick={handleAddAction}>Add Action</button>
// <button onClick={handleAddTrigger}>Add Trigger</button>
// {selectedPoint?.actions.map((action) => (
// <div key={action.uuid} style={{ marginTop: "10px" }}>
// <select value={action.type} onChange={(e) => handleActionSelect(action.uuid, e.target.value)}>
// <option value="Inherit">Inherit</option>
// <option value="Spawn">Spawn Point</option>
// <option value="Swap">Swap Material</option>
// <option value="Despawn">Despawn Point</option>
// <option value="Delay">Delay</option>
// </select>
// <button onClick={() => handleDeleteAction(action.uuid)}>Delete Action</button>
// <label>
// <input
// type="checkbox"
// checked={action.isUsed}
// onChange={() => handleActionToggle(action.uuid)}
// />
// </label>
// {(action.type === 'Spawn' || action.type === 'Swap') && (
// <div style={{ marginTop: "10px" }}>
// <select value={action.material} onChange={(e) => handleMaterialSelect(action.uuid, e.target.value)}>
// <option value="Inherit">Inherit</option>
// <option value="Crate">Crate</option>
// <option value="Box">Box</option>
// </select>
// </div>
// )}
// {action.type === 'Delay' && (
// <div style={{ marginTop: "10px" }}>
// <label>Delay Time:</label>
// <input
// style={{ width: '50px' }}
// type="text"
// value={isNaN(Number(action.delay)) || action.delay === "Inherit" ? "Inherit" : action.delay}
// min="1"
// onChange={(e) => handleDelayChange(action.uuid, parseInt(e.target.value) || 'Inherit')}
// />
// </div>
// )}
// {action.type === 'Spawn' && (
// <div style={{ marginTop: "10px" }}>
// <label>Spawn Interval:</label>
// <input
// style={{ width: '50px' }}
// type="text"
// value={isNaN(Number(action.spawnInterval)) || action.spawnInterval === "Inherit" ? "Inherit" : action.spawnInterval}
// min="1"
// onChange={(e) => handleSpawnIntervalChange(action.uuid, parseInt(e.target.value) || 'Inherit')}
// />
// </div>
// )}
// <hr style={{ margin: "10px 0", borderColor: "#ccc" }} />
// </div>
// ))}
// <hr style={{ margin: "10px 0", border: "1px solid black" }} />
// {selectedPoint?.triggers.map((trigger) => (
// <div key={trigger.uuid} style={{ marginTop: "10px" }}>
// <select value={trigger.type} onChange={(e) => handleTriggerSelect(trigger.uuid, e.target.value)}>
// <option value="">Select Trigger Type</option>
// <option value="On-Hit">On Hit</option>
// <option value="Buffer">Buffer</option>
// </select>
// <button onClick={() => handleDeleteTrigger(trigger.uuid)}>Delete Trigger</button>
// <label>
// <input
// type="checkbox"
// checked={trigger.isUsed}
// onChange={() => handleTriggerToggle(trigger.uuid)}
// />
// </label>
// <hr style={{ margin: "10px 0", borderColor: "#ccc" }} />
// </div>
// ))}
// </div>
// )}
// {selectedPath && (
// <div style={{ marginTop: "10px" }}>
// <button
// onClick={handleResetPath}
// style={{ padding: "10px", borderRadius: "5px", background: "#ff0000", color: "#fff" }}
// >
// Reset Path
// </button>
// </div>
// )}
// </>
// )}
// </div>
// )}
// </>
// );
// }
// export default SimulationUI;

View File

@@ -1,9 +0,0 @@
import React from 'react'
function ColliderCreator() {
return (
<></>
)
}
export default ColliderCreator

View File

@@ -1,407 +0,0 @@
import { useEffect, useState } from 'react';
import * as THREE from 'three';
import { useThree, useFrame } from '@react-three/fiber';
import { Line, TransformControls } from '@react-three/drei';
import { useDrawMaterialPath } from '../../../../store/store';
type PathPoint = {
position: THREE.Vector3;
rotation: THREE.Quaternion;
uuid: string;
};
type PathCreatorProps = {
simulationStates: PathPoint[][];
setSimulationStates: React.Dispatch<React.SetStateAction<PathPoint[][]>>;
connections: { start: PathPoint; end: PathPoint }[];
setConnections: React.Dispatch<React.SetStateAction<{ start: PathPoint; end: PathPoint }[]>>
};
const PathCreator = ({ simulationStates, setSimulationStates, connections, setConnections }: PathCreatorProps) => {
const { camera, scene, raycaster, pointer, gl } = useThree();
const { drawMaterialPath } = useDrawMaterialPath();
const [currentPath, setCurrentPath] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }[]>([]);
const [temporaryPoint, setTemporaryPoint] = useState<THREE.Vector3 | null>(null);
const [selectedPoint, setSelectedPoint] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string } | null>(null);
const [selectedConnectionPoint, setSelectedConnectionPoint] = useState<{ point: PathPoint; pathIndex: number } | null>(null);
const [previewConnection, setPreviewConnection] = useState<{ start: PathPoint; end?: THREE.Vector3 } | null>(null);
const [transformMode, setTransformMode] = useState<'translate' | 'rotate'>('translate');
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (selectedPoint) {
if (event.key === 'g') {
setTransformMode('translate');
} else if (event.key === 'r') {
setTransformMode('rotate');
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [selectedPoint]);
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let MouseDown = false;
const onMouseDown = () => {
MouseDown = true;
drag = false;
};
const onMouseUp = () => {
MouseDown = false;
};
const onMouseMove = () => {
if (MouseDown) {
drag = true;
}
};
const onContextMenu = (e: any) => {
e.preventDefault();
if (drag || e.button === 0) return;
if (currentPath.length > 1) {
setSimulationStates((prevPaths) => [...prevPaths, currentPath]);
}
setCurrentPath([]);
setTemporaryPoint(null);
setPreviewConnection(null);
setSelectedConnectionPoint(null);
};
const onMouseClick = (evt: any) => {
if (drag || evt.button !== 0) return;
evt.preventDefault();
raycaster.setFromCamera(pointer, camera);
let intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.some((intersect) => intersect.object.name.includes("path-point"))) {
intersects = [];
} else {
intersects = intersects.filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper")
);
}
if (intersects.length > 0 && selectedPoint === null) {
let point = intersects[0].point;
if (point.y < 0.05) {
point = new THREE.Vector3(point.x, 0.05, point.z);
}
const newPoint = {
position: point,
rotation: new THREE.Quaternion(),
uuid: THREE.MathUtils.generateUUID(),
};
setCurrentPath((prevPath) => [...prevPath, newPoint]);
setTemporaryPoint(null);
} else {
setSelectedPoint(null);
}
};
if (drawMaterialPath) {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("click", onMouseClick);
canvasElement.addEventListener("contextmenu", onContextMenu);
} else {
if (currentPath.length > 1) {
setSimulationStates((prevPaths) => [...prevPaths, currentPath]);
}
setCurrentPath([]);
setTemporaryPoint(null);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [camera, scene, raycaster, currentPath, drawMaterialPath, selectedPoint]);
useFrame(() => {
if (drawMaterialPath && currentPath.length > 0) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true).filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper")
);
if (intersects.length > 0) {
let point = intersects[0].point;
if (point.y < 0.05) {
point = new THREE.Vector3(point.x, 0.05, point.z);
}
setTemporaryPoint(point);
} else {
setTemporaryPoint(null);
}
} else {
setTemporaryPoint(null);
}
});
const handlePointClick = (point: { position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }) => {
if (currentPath.length === 0 && drawMaterialPath) {
setSelectedPoint(point);
} else {
setSelectedPoint(null);
}
};
const handleTransform = (e: any) => {
if (selectedPoint) {
const updatedPosition = e.target.object.position.clone();
const updatedRotation = e.target.object.quaternion.clone();
const updatedPaths = simulationStates.map((path) =>
path.map((p) =>
p.uuid === selectedPoint.uuid ? { ...p, position: updatedPosition, rotation: updatedRotation } : p
)
);
setSimulationStates(updatedPaths);
}
};
const meshContext = (uuid: string) => {
const pathIndex = simulationStates.findIndex(path => path.some(point => point.uuid === uuid));
if (pathIndex === -1) return;
const clickedPoint = simulationStates[pathIndex].find(point => point.uuid === uuid);
if (!clickedPoint) return;
const isStart = simulationStates[pathIndex][0].uuid === uuid;
const isEnd = simulationStates[pathIndex][simulationStates[pathIndex].length - 1].uuid === uuid;
if (pathIndex === 0 && isStart) {
console.log("The first-ever point is not connectable.");
setSelectedConnectionPoint(null);
setPreviewConnection(null);
return;
}
if (!isStart && !isEnd) {
console.log("Selected point is not a valid connection point (not start or end)");
setSelectedConnectionPoint(null);
setPreviewConnection(null);
return;
}
if (connections.some(conn => conn.start.uuid === uuid || conn.end.uuid === uuid)) {
console.log("The selected point is already connected.");
setSelectedConnectionPoint(null);
setPreviewConnection(null);
return;
}
if (!selectedConnectionPoint) {
setSelectedConnectionPoint({ point: clickedPoint, pathIndex });
setPreviewConnection({ start: clickedPoint });
console.log("First point selected for connection:", clickedPoint);
return;
}
if (selectedConnectionPoint.pathIndex === pathIndex) {
console.log("Cannot connect points within the same path.");
setSelectedConnectionPoint(null);
setPreviewConnection(null);
return;
}
if (connections.some(conn => conn.start.uuid === clickedPoint.uuid || conn.end.uuid === clickedPoint.uuid)) {
console.log("The target point is already connected.");
setSelectedConnectionPoint(null);
setPreviewConnection(null);
return;
}
setConnections(prevConnections => [
...prevConnections,
{ start: selectedConnectionPoint.point, end: clickedPoint },
]);
setSelectedConnectionPoint(null);
setPreviewConnection(null);
};
useEffect(() => {
if (!selectedConnectionPoint) {
setPreviewConnection(null);
}
}, [selectedConnectionPoint, connections]);
useFrame(() => {
if (selectedConnectionPoint) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true).filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper")
);
if (intersects.length > 0) {
let point = intersects[0].point;
if (point.y < 0.05) {
point = new THREE.Vector3(point.x, 0.05, point.z);
}
setPreviewConnection({ start: selectedConnectionPoint.point, end: point });
} else {
setPreviewConnection(null);
}
}
});
return (
<>
<group name='pathObjects'>
{/* Render finalized simulationStates */}
{simulationStates.map((path, pathIndex) => (
<group key={`path-line-${pathIndex}`}>
<Line
name={`path-line-${pathIndex}`}
points={path.map((point) => point.position)}
color="yellow"
lineWidth={5}
userData={{ isPathObject: true }}
/>
</group>
))}
{/* Render finalized points */}
{simulationStates.map((path) =>
path.map((point) => (
<mesh
key={`path-point-${point.uuid}`}
name={`path-point-${point.uuid}`}
uuid={`${point.uuid}`}
position={point.position}
userData={{ isPathObject: true }}
onClick={() => handlePointClick(point)}
onPointerMissed={() => { setSelectedPoint(null) }}
onContextMenu={() => { meshContext(point.uuid); }}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="blue" wireframe />
</mesh>
))
)}
{connections.map((conn, index) => (
<Line
key={`connection-${index}`}
points={[conn.start.position, conn.end.position]}
color="white"
dashed
lineWidth={4}
dashSize={1}
dashScale={15}
userData={{ isPathObject: true }}
/>
))}
</group>
{/* Render current path */}
{currentPath.length > 1 && (
<group>
<Line
points={currentPath.map((point) => point.position)}
color="red"
lineWidth={5}
userData={{ isPathObject: true }}
/>
</group>
)}
{/* Render current path points */}
{currentPath.map((point) => (
<mesh
key={`current-point-${point.uuid}`}
position={point.position}
userData={{ isPathObject: true }}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="red" />
</mesh>
))}
{/* Render temporary indicator line */}
{temporaryPoint && currentPath.length > 0 && (
<group>
<Line
points={[currentPath[currentPath.length - 1].position, temporaryPoint]}
color="white"
lineWidth={2}
userData={{ isPathObject: true }}
/>
</group>
)}
{/* Render dashed preview connection */}
{previewConnection && previewConnection.end && (
<Line
points={[previewConnection.start.position, previewConnection.end]}
color="white"
dashed
lineWidth={4}
dashSize={1}
dashScale={15}
userData={{ isPathObject: true }}
/>
)}
{/* Render temporary point */}
{temporaryPoint && (
<mesh
position={temporaryPoint}
userData={{ isPathObject: true }}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="white" />
</mesh>
)}
{/* Attach TransformControls to the selected point */}
{selectedPoint && (
<TransformControls
object={scene.getObjectByProperty('uuid', selectedPoint.uuid)}
mode={transformMode}
onObjectChange={handleTransform}
/>
)}
</>
);
};
export default PathCreator;

View File

@@ -1,164 +0,0 @@
import * as THREE from 'three';
import { useState, useEffect, useRef, useMemo } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three-stdlib";
import crate from "../../../../assets/gltf-glb/crate_box.glb";
import { useOrganization } from '../../../../store/store';
import { useControls } from 'leva';
type PathPoint = {
position: THREE.Vector3;
rotation: THREE.Quaternion;
uuid: string;
};
type PathFlowProps = {
path: PathPoint[];
connections: { start: PathPoint; end: PathPoint }[];
};
export default function PathFlow({ path, connections }: PathFlowProps) {
const { organization } = useOrganization();
const [isPaused, setIsPaused] = useState(false);
const [isStopped, setIsStopped] = useState(false);
const { spawnInterval, speed, pauseResume, startStop } = useControls({
spawnInterval: { value: 1000, min: 500, max: 5000, step: 100 },
speed: { value: 2, min: 1, max: 20, step: 0.5 },
pauseResume: { value: false, label: "Pause/Resume" },
startStop: { value: false, label: "Start/Stop" },
});
const [meshes, setMeshes] = useState<{ id: number }[]>([]);
const gltf = useLoader(GLTFLoader, crate);
const meshIdRef = useRef(0);
const lastSpawnTime = useRef(performance.now());
const totalPausedTime = useRef(0);
const pauseStartTime = useRef<number | null>(null);
useEffect(() => {
setIsPaused(pauseResume);
setIsStopped(startStop);
}, [pauseResume, startStop]);
const removeMesh = (id: number) => {
setMeshes((prev) => prev.filter((m) => m.id !== id));
};
useFrame(() => {
if (isStopped || !path) return;
const now = performance.now();
if (isPaused) {
if (pauseStartTime.current === null) {
pauseStartTime.current = now;
}
return;
}
if (pauseStartTime.current !== null) {
totalPausedTime.current += now - pauseStartTime.current;
pauseStartTime.current = null;
}
const adjustedTime = now - totalPausedTime.current;
if (adjustedTime - lastSpawnTime.current >= spawnInterval) {
setMeshes((prev) => [...prev, { id: meshIdRef.current++ }]);
lastSpawnTime.current = adjustedTime;
}
});
return (
<>
{meshes.map((mesh) => (
<MovingMesh
key={mesh.id}
meshId={mesh.id}
points={path}
speed={speed}
gltf={gltf}
removeMesh={removeMesh}
isPaused={isPaused}
/>
))}
</>
);
}
function MovingMesh({ meshId, points, speed, gltf, removeMesh, isPaused }: any) {
const meshRef = useRef<any>();
const startTime = useRef<number | null>(null); // Initialize as null
const pausedTime = useRef(0);
const pauseStartTime = useRef<number | null>(null);
const distances = useMemo(() => {
if (!points || points.length < 2) return [];
return points.slice(1).map((point: any, i: number) => points[i].position.distanceTo(point.position));
}, [points]);
useFrame(() => {
if (!points || points.length < 2) return;
if (startTime.current === null && points.length > 0) {
startTime.current = performance.now();
}
if (!meshRef.current) return;
if (isPaused) {
if (pauseStartTime.current === null) {
pauseStartTime.current = performance.now();
}
return;
}
if (pauseStartTime.current !== null) {
pausedTime.current += performance.now() - pauseStartTime.current;
pauseStartTime.current = null;
}
if (startTime.current === null) return;
const elapsed = performance.now() - startTime.current - pausedTime.current;
const distanceTraveled = elapsed / 1000 * speed;
let remainingDistance = distanceTraveled;
let currentSegmentIndex = 0;
while (currentSegmentIndex < distances.length && remainingDistance > distances[currentSegmentIndex]) {
remainingDistance -= distances[currentSegmentIndex];
currentSegmentIndex++;
}
if (currentSegmentIndex >= distances.length) {
removeMesh(meshId);
return;
}
const progress = remainingDistance / distances[currentSegmentIndex];
const start = points[currentSegmentIndex].position;
const end = points[currentSegmentIndex + 1].position;
meshRef.current.position.lerpVectors(start, end, Math.min(progress, 1));
const startRotation = points[currentSegmentIndex].rotation;
const endRotation = points[currentSegmentIndex + 1].rotation;
const interpolatedRotation = new THREE.Quaternion().slerpQuaternions(startRotation, endRotation, Math.min(progress, 1));
meshRef.current.quaternion.copy(interpolatedRotation);
});
return (
<>
{points && points.length > 0 &&
<mesh ref={meshRef}>
<primitive object={gltf.scene.clone()} />
</mesh>
}
</>
);
}

View File

@@ -1,9 +0,0 @@
import React from 'react'
function ProcessCreator() {
return (
<></>
)
}
export default ProcessCreator

View File

@@ -1,26 +0,0 @@
import React, { useState } from 'react';
import * as THREE from 'three';
import PathCreator from './path/pathCreator';
import PathFlow from './path/pathFlow';
type PathPoint = {
position: THREE.Vector3;
rotation: THREE.Quaternion;
uuid: string;
};
function Simulation() {
const [simulationStates, setSimulationStates] = useState<{ position: THREE.Vector3; rotation: THREE.Quaternion; uuid: string }[][]>([]);
const [connections, setConnections] = useState<{ start: PathPoint; end: PathPoint }[]>([]);
return (
<>
<PathCreator simulationStates={simulationStates} setSimulationStates={setSimulationStates} connections={connections} setConnections={setConnections} />
{simulationStates.map((path, index) => (
<PathFlow key={index} path={path} connections={connections} />
))}
</>
);
}
export default Simulation;

View File

@@ -1,83 +0,0 @@
import React, { useEffect } from 'react'
import * as SimulationTypes from '../../../types/simulationTypes';
import { useSimulationStates } from '../../../store/store';
import StaticMachineInstances from './staticMachineInstances';
import { useResetButtonStore } from '../../../store/usePlayButtonStore';
interface ArmBotState {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
status: string;
material: string;
triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
}
interface StaticMachineState {
uuid: string;
status: string;
actions: { uuid: string; name: string; buffer: number; material: string; };
machineTriggerId: string;
connectedArmBot: string;
}
type StaticMachineProps = {
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
staticMachines: StaticMachineState[];
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
}
function StaticMachine({ setArmBots, staticMachines, setStaticMachines }: StaticMachineProps) {
const { simulationStates } = useSimulationStates();
const { isReset } = useResetButtonStore();
useEffect(() => {
const filtered = simulationStates.filter((s): s is SimulationTypes.StaticMachineEventsSchema => s.type === "StaticMachine");
const initialStates: StaticMachineState[] = filtered
.filter(machine => machine.points.connections.targets.length > 0)
.map(machine => ({
uuid: machine.modeluuid,
status: "idle",
actions: machine.points.actions,
machineTriggerId: machine.points.triggers.uuid,
connectedArmBot: machine.points.connections.targets[0].modelUUID
}));
setStaticMachines(initialStates);
}, [simulationStates, isReset]);
const updateArmBotTriggerAndMachineStatus = (armBotUuid: string, triggerId: string, machineId: string) => {
setArmBots((prevArmBots) => {
return prevArmBots.map(bot => {
if (bot.uuid === armBotUuid) {
return { ...bot, triggerId: triggerId };
}
return bot;
});
});
setStaticMachines((prevStaticMachines) => {
return prevStaticMachines.map(machine => {
if (machine.uuid === machineId) {
return { ...machine, status: "idle" };
} else {
return machine;
}
});
});
}
return (
<>
{staticMachines.map((machine, index) => (
<StaticMachineInstances key={index} machine={machine} updateArmBotTriggerAndMachineStatus={updateArmBotTriggerAndMachineStatus} />
))}
</>
)
}
export default StaticMachine;

View File

@@ -1,33 +0,0 @@
import React, { useEffect } from 'react'
import { useAnimationPlaySpeed } from '../../../store/usePlayButtonStore';
interface StaticMachineState {
uuid: string;
status: string;
actions: { uuid: string; name: string; buffer: number; material: string; };
machineTriggerId: string;
connectedArmBot: string;
}
type StaticMachineInstancesProps = {
machine: StaticMachineState,
updateArmBotTriggerAndMachineStatus: (armBotUuid: string, triggerId: string, machineId: string) => void;
}
function StaticMachineInstances({ machine, updateArmBotTriggerAndMachineStatus }: StaticMachineInstancesProps) {
const { speed } = useAnimationPlaySpeed();
useEffect(() => {
if (machine.status === 'running') {
setTimeout(() => {
updateArmBotTriggerAndMachineStatus(machine.connectedArmBot, machine.machineTriggerId, machine.uuid);
}, machine.actions.buffer * 1000 * speed);
}
}, [machine])
return (
<></>
)
}
export default StaticMachineInstances

View File

@@ -3,7 +3,7 @@ import { usePlayButtonStore } from "../../store/usePlayButtonStore";
import Panel from "./widgets/panel/Panel";
import AddButtons from "./widgets/panel/AddButtons";
import { useSelectedZoneStore } from "../../store/useZoneStore";
import DisplayZone from "./DisplayZone";
import DisplayZone from "./zone/DisplayZone";
import Scene from "../scene/scene";
import useModuleStore from "../../store/useModuleStore";
@@ -17,10 +17,10 @@ import {
useWidgetSubOption,
useZones,
} from "../../store/store";
import { getZone2dData } from "../../services/realTimeVisulization/zoneData/getZoneData";
import { getZone2dData } from "../../services/visulization/zone/getZoneData";
import { generateUniqueId } from "../../functions/generateUniqueId";
import { determinePosition } from "./functions/determinePosition";
import { addingFloatingWidgets } from "../../services/realTimeVisulization/zoneData/addFloatingWidgets";
import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets";
import SocketRealTimeViz from "./socket/realTimeVizSocket.dev";
import RenderOverlay from "../../components/templates/Overlay";
import ConfirmationPopup from "../../components/layout/confirmationPopup/ConfirmationPopup";
@@ -31,7 +31,6 @@ import {
useRightClickSelected,
useRightSelected,
} from "../../store/useZone3DWidgetStore";
import Dropped3dWidgets from "./widgets/3d/Dropped3dWidget";
import OuterClick from "../../utils/outerClick";
import { useWidgetStore } from "../../store/useWidgetStore";
import { getActiveProperties } from "./functions/getActiveProperties";

View File

@@ -2,7 +2,7 @@ import { useEffect } from "react";
import useTemplateStore from "../../../store/useTemplateStore";
import { useSelectedZoneStore } from "../../../store/useZoneStore";
import { useSocketStore } from "../../../store/store";
import { getTemplateData } from "../../../services/realTimeVisulization/zoneData/getTemplate";
import { getTemplateData } from "../../../services/visulization/zone/getTemplate";
import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
import RenameInput from "../../../components/ui/inputs/RenameInput";

View File

@@ -0,0 +1,22 @@
import React from 'react'
import Dropped3dWidgets from './widgets/3d/Dropped3dWidget'
import ZoneCentreTarget from './zone/zoneCameraTarget'
import ZoneAssets from './zone/zoneAssets'
// import MqttEvents from '../../services/factoryBuilder/mqtt/mqttEvents'
const Visualization = () => {
return (
<>
<Dropped3dWidgets />
{/* <ZoneCentreTarget />
<ZoneAssets />
<MqttEvents /> */}
</>
)
}
export default Visualization;

View File

@@ -13,8 +13,6 @@ import {
KebabIcon,
} from "../../../../components/icons/ExportCommonIcons";
import { useEffect, useRef, useState } from "react";
import { duplicateWidgetApi } from "../../../../services/realTimeVisulization/zoneData/duplicateWidget";
import { deleteWidgetApi } from "../../../../services/realTimeVisulization/zoneData/deleteWidgetApi";
import { useClickOutside } from "../../functions/handleWidgetsOuterClick";
import { useSocketStore } from "../../../../store/store";
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";

View File

@@ -7,7 +7,7 @@ import { ThreeState } from "../../../../types/world/worldTypes";
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
import { useEditWidgetOptionsStore, useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore } from "../../../../store/useZone3DWidgetStore";
import { use3DWidget } from "../../../../store/useDroppedObjectsStore";
import { get3dWidgetZoneData } from "../../../../services/realTimeVisulization/zoneData/get3dWidgetData";
import { get3dWidgetZoneData } from "../../../../services/visulization/zone/get3dWidgetData";
import { generateUniqueId } from "../../../../functions/generateUniqueId";
import ProductionCapacity from "./cards/ProductionCapacity";
import ReturnOfInvestment from "./cards/ReturnOfInvestment";
@@ -16,10 +16,6 @@ import Throughput from "./cards/Throughput";
import { useWidgetStore } from "../../../../store/useWidgetStore";
import useChartStore from "../../../../store/useChartStore";
type WidgetData = {
id: string;
type: string;

View File

@@ -7,14 +7,14 @@ import {
import useModuleStore from "../../../../store/useModuleStore";
import { determinePosition } from "../../functions/determinePosition";
import { getActiveProperties } from "../../functions/getActiveProperties";
import { addingFloatingWidgets } from "../../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
import { addingFloatingWidgets } from "../../../../services/visulization/zone/addFloatingWidgets";
import {
DublicateIcon,
KebabIcon,
DeleteIcon,
} from "../../../../components/icons/ExportCommonIcons";
import DistanceLines from "./DistanceLines"; // Import the DistanceLines component
import { deleteFloatingWidgetApi } from "../../../../services/realTimeVisulization/zoneData/deleteFloatingWidget";
import { deleteFloatingWidgetApi } from "../../../../services/visulization/zone/deleteFloatingWidget";
import TotalCardComponent from "./cards/TotalCardComponent";
import WarehouseThroughputComponent from "./cards/WarehouseThroughputComponent";

View File

@@ -6,8 +6,8 @@ import {
} from "../../../../components/icons/RealTimeVisulationIcons";
import { AddIcon } from "../../../../components/icons/ExportCommonIcons";
import { useSocketStore } from "../../../../store/store";
import { clearPanel } from "../../../../services/realTimeVisulization/zoneData/clearPanel";
import { lockPanel } from "../../../../services/realTimeVisulization/zoneData/lockPanel";
import { clearPanel } from "../../../../services/visulization/zone/clearPanel";
import { lockPanel } from "../../../../services/visulization/zone/lockPanel";
// Define the type for `Side`
type Side = "top" | "bottom" | "left" | "right";

View File

@@ -1,18 +1,18 @@
import React, { useEffect, useRef, useState, useCallback } from "react";
import { useWidgetStore, Widget } from "../../store/useWidgetStore";
import { useWidgetStore, Widget } from "../../../store/useWidgetStore";
import {
useDroppedObjectsStore,
useFloatingWidget,
} from "../../store/useDroppedObjectsStore";
import { getSelect2dZoneData } from "../../services/realTimeVisulization/zoneData/getSelect2dZoneData";
import { getFloatingZoneData } from "../../services/realTimeVisulization/zoneData/getFloatingData";
import { get3dWidgetZoneData } from "../../services/realTimeVisulization/zoneData/get3dWidgetData";
} from "../../../store/useDroppedObjectsStore";
import { getSelect2dZoneData } from "../../../services/visulization/zone/getSelect2dZoneData";
import { getFloatingZoneData } from "../../../services/visulization/zone/getFloatingData";
import { get3dWidgetZoneData } from "../../../services/visulization/zone/get3dWidgetData";
import {
MoveArrowLeft,
MoveArrowRight,
} from "../../components/icons/SimulationIcons";
import { InfoIcon } from "../../components/icons/ExportCommonIcons";
} from "../../../components/icons/SimulationIcons";
import { InfoIcon } from "../../../components/icons/ExportCommonIcons";
// Define the type for `Side`
type Side = "top" | "bottom" | "left" | "right";

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useRef } from 'react'
import { useSelectedFloorItem, useZoneAssetId } from '../../store/store';
import { useSelectedFloorItem, useZoneAssetId } from '../../../store/store';
import * as THREE from "three";
import { useThree } from '@react-three/fiber';
import * as Types from "../../types/world/worldTypes";
import * as Types from "../../../types/world/worldTypes";
export default function ZoneAssets() {
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { setSelectedFloorItem } = useSelectedFloorItem();
@@ -10,7 +10,6 @@ export default function ZoneAssets() {
useEffect(() => {
// console.log('zoneAssetId: ', zoneAssetId);
if (!zoneAssetId) return
console.log('zoneAssetId: ', zoneAssetId);
let AssetMesh = scene.getObjectByProperty("uuid", zoneAssetId.id);
if (AssetMesh) {
const bbox = new THREE.Box3().setFromObject(AssetMesh);
@@ -30,20 +29,17 @@ export default function ZoneAssets() {
setSelectedFloorItem(AssetMesh);
} else {
console.log('zoneAssetId: ', zoneAssetId)
if (Array.isArray(zoneAssetId.position) && zoneAssetId.position.length >= 3) {
let selectedAssetPosition = [
zoneAssetId.position[0],
10,
zoneAssetId.position[2]
];
console.log('selectedAssetPosition: ', selectedAssetPosition);
let selectedAssetTarget = [
zoneAssetId.position[0],
zoneAssetId.position[1],
zoneAssetId.position[2]
];
console.log('selectedAssetTarget: ', selectedAssetTarget);
const setCam = async () => {
await controls?.setLookAt(...selectedAssetPosition, ...selectedAssetTarget, true);
setTimeout(() => {

View File

@@ -1,32 +0,0 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
export const setEventApi = async (
organization: string,
modeluuid: string,
eventData: any
) => {
try {
const body: any = { organization, modeluuid, eventData };
const response = await fetch(`${url_Backend_dwinzo}/api/v2/eventDataUpdate`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error("Failed to set or Update Event Data");
}
const result = await response.json();
return result;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
} else {
throw new Error("An unknown error occurred");
}
}
};

View File

@@ -8,13 +8,9 @@ export const setFloorItemApi = async (
rotation?: Object,
isLocked?: boolean,
isVisible?: boolean,
eventData?: any
) => {
try {
const body: any = { organization, modeluuid, modelname, position, rotation, modelfileID, isLocked, isVisible };
if (eventData) {
body.eventData = eventData;
}
const response = await fetch(`${url_Backend_dwinzo}/api/v2/setasset`, {
method: "POST",

View File

@@ -1,25 +0,0 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
export const getAssetEventType = async (modelId: string, organization: string) => {
try {
const response = await fetch(`${url_Backend_dwinzo}/api/v2/pointData/${modelId}/${organization}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error("Failed to fetch model event type");
}
const result = await response.json();
return result;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
} else {
throw new Error("An unknown error occurred");
}
}
};

Some files were not shown because too many files have changed in this diff Show More