folder structure change
This commit is contained in:
@@ -5,8 +5,8 @@ import Header from "./Header";
|
|||||||
import useToggleStore from "../../../store/useUIToggleStore";
|
import useToggleStore from "../../../store/useUIToggleStore";
|
||||||
import Assets from "./Assets";
|
import Assets from "./Assets";
|
||||||
import useModuleStore from "../../../store/useModuleStore";
|
import useModuleStore from "../../../store/useModuleStore";
|
||||||
import Widgets from "./visualization/widgets/Widgets";
|
import Widgets from ".//visualization/widgets/Widgets";
|
||||||
import Templates from "../../../modules/visualization/template/Templates";
|
import Templates from "../../../modules//visualization/template/Templates";
|
||||||
import Search from "../../ui/inputs/Search";
|
import Search from "../../ui/inputs/Search";
|
||||||
|
|
||||||
const SideBarLeft: React.FC = () => {
|
const SideBarLeft: React.FC = () => {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import {
|
|||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
WalletIcon,
|
WalletIcon,
|
||||||
} from "../../../../icons/3dChartIcons";
|
} 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 WarehouseThroughput from "../../../../../modules//visualization/widgets/floating/cards/WarehouseThroughput";
|
||||||
import ProductivityDashboard from "../../../../../modules/visualization/widgets/floating/cards/ProductivityDashboard";
|
import ProductivityDashboard from "../../../../../modules//visualization/widgets/floating/cards/ProductivityDashboard";
|
||||||
import FleetEfficiency from "../../../../../modules/visualization/widgets/floating/cards/FleetEfficiency";
|
import FleetEfficiency from "../../../../../modules//visualization/widgets/floating/cards/FleetEfficiency";
|
||||||
|
|
||||||
interface Widget {
|
interface Widget {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { AppDockIcon } from "../../icons/HeaderIcons";
|
import { AppDockIcon } from "../../icons/HeaderIcons";
|
||||||
import orgImg from "../../../assets/orgTemp.png";
|
import orgImg from "../../../assets/orgTemp.png";
|
||||||
import { useActiveUsers } from "../../../store/store";
|
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 { ActiveUser } from "../../../types/users";
|
||||||
import CollaborationPopup from "../../templates/CollaborationPopup";
|
import CollaborationPopup from "../../templates/CollaborationPopup";
|
||||||
|
|
||||||
|
|||||||
@@ -10,25 +10,19 @@ import {
|
|||||||
SimulationIcon,
|
SimulationIcon,
|
||||||
} from "../../icons/SimulationIcons";
|
} from "../../icons/SimulationIcons";
|
||||||
import useToggleStore from "../../../store/useUIToggleStore";
|
import useToggleStore from "../../../store/useUIToggleStore";
|
||||||
import ConveyorMechanics from "./mechanics/ConveyorMechanics";
|
|
||||||
import Visualization from "./visualization/Visualization";
|
import Visualization from "./visualization/Visualization";
|
||||||
import Analysis from "./analysis/Analysis";
|
import Analysis from "./analysis/Analysis";
|
||||||
import Simulations from "./simulation/Simulations";
|
import Simulations from "./simulation/Simulations";
|
||||||
import {
|
import {
|
||||||
useSelectedActionSphere,
|
|
||||||
useSelectedFloorItem,
|
useSelectedFloorItem,
|
||||||
} from "../../../store/store";
|
} from "../../../store/store";
|
||||||
import GlobalProperties from "./properties/GlobalProperties";
|
import GlobalProperties from "./properties/GlobalProperties";
|
||||||
import AsstePropertiies from "./properties/AssetProperties";
|
import AsstePropertiies from "./properties/AssetProperties";
|
||||||
import ZoneProperties from "./properties/ZoneProperties";
|
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 SideBarRight: React.FC = () => {
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { toggleUI } = useToggleStore();
|
const { toggleUI } = useToggleStore();
|
||||||
const { selectedActionSphere } = useSelectedActionSphere();
|
|
||||||
const { subModule, setSubModule } = useSubModuleStore();
|
const { subModule, setSubModule } = useSubModuleStore();
|
||||||
const { selectedFloorItem } = useSelectedFloorItem();
|
const { selectedFloorItem } = useSelectedFloorItem();
|
||||||
// Reset activeList whenever activeModule changes
|
// Reset activeList whenever activeModule changes
|
||||||
@@ -112,46 +106,9 @@ const SideBarRight: React.FC = () => {
|
|||||||
|
|
||||||
{toggleUI && activeModule === "simulation" && (
|
{toggleUI && activeModule === "simulation" && (
|
||||||
<>
|
<>
|
||||||
{subModule === "mechanics" &&
|
{subModule === "mechanics" && (
|
||||||
selectedActionSphere &&
|
|
||||||
selectedActionSphere.path.type === "Conveyor" && (
|
|
||||||
<div className="sidebar-right-container">
|
<div className="sidebar-right-container">
|
||||||
<div className="sidebar-right-content-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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
@@ -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;
|
|
||||||
@@ -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);
|
|
||||||
@@ -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);
|
|
||||||
@@ -3,7 +3,7 @@ import RenameInput from "../../../ui/inputs/RenameInput";
|
|||||||
import Vector3Input from "../customInput/Vector3Input";
|
import Vector3Input from "../customInput/Vector3Input";
|
||||||
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
|
||||||
import { useEditPosition, usezonePosition, useZones, usezoneTarget } from "../../../../store/store";
|
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 ZoneProperties: React.FC = () => {
|
||||||
const { Edit, setEdit } = useEditPosition();
|
const { Edit, setEdit } = useEditPosition();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
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 RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||||
import { WalletIcon } from "../../../../icons/3dChartIcons";
|
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 {
|
interface Widget {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from "../icons/ExportToolsIcons";
|
} from "../icons/ExportToolsIcons";
|
||||||
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
|
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
|
||||||
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
|
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 { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||||
import useTemplateStore from "../../store/useTemplateStore";
|
import useTemplateStore from "../../store/useTemplateStore";
|
||||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons";
|
|||||||
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
|
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
|
||||||
import { useFloorItems, useZones } from "../../../store/store";
|
import { useFloorItems, useZones } from "../../../store/store";
|
||||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||||
import { getZone2dData } from "../../../services/realTimeVisulization/zoneData/getZoneData";
|
|
||||||
|
|
||||||
interface DropDownListProps {
|
interface DropDownListProps {
|
||||||
value?: string; // Value to display in the DropDownList
|
value?: string; // Value to display in the DropDownList
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|||||||
import RenameInput from "../inputs/RenameInput";
|
import RenameInput from "../inputs/RenameInput";
|
||||||
|
|
||||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||||
import { getZoneData } from "../../../services/realTimeVisulization/zoneData/getZones";
|
import { getZoneData } from "../../../services/visulization/zone/getZones";
|
||||||
import useModuleStore, {
|
import useModuleStore, {
|
||||||
useSubModuleStore,
|
useSubModuleStore,
|
||||||
} from "../../../store/useModuleStore";
|
} from "../../../store/useModuleStore";
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from "../../icons/ExportCommonIcons";
|
} from "../../icons/ExportCommonIcons";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import { useFloorItems, useZoneAssetId, useZones } from "../../../store/store";
|
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";
|
import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
|
||||||
|
|
||||||
interface Asset {
|
interface Asset {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import * as THREE from 'three';
|
|||||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import * as Types from "../../../types/world/worldTypes";
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
import * as SimulationTypes from "../../../types/simulationTypes";
|
|
||||||
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||||
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||||
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||||
@@ -13,7 +12,6 @@ import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAss
|
|||||||
async function loadInitialFloorItems(
|
async function loadInitialFloorItems(
|
||||||
itemsGroup: Types.RefGroup,
|
itemsGroup: Types.RefGroup,
|
||||||
setFloorItems: Types.setFloorItemSetState,
|
setFloorItems: Types.setFloorItemSetState,
|
||||||
setSimulationStates: (paths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => void
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!itemsGroup.current) return;
|
if (!itemsGroup.current) return;
|
||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
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.message === "floorItems not found") return;
|
||||||
|
|
||||||
if (items) {
|
if (items) {
|
||||||
const storedFloorItems: SimulationTypes.EventData[] = items;
|
const storedFloorItems: Types.FloorItems = items;
|
||||||
const loader = new GLTFLoader();
|
const loader = new GLTFLoader();
|
||||||
const dracoLoader = new DRACOLoader();
|
const dracoLoader = new DRACOLoader();
|
||||||
|
|
||||||
@@ -72,7 +70,7 @@ async function loadInitialFloorItems(
|
|||||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||||
if (cachedModel) {
|
if (cachedModel) {
|
||||||
// console.log(`[Cache] Fetching ${item.modelname}`);
|
// console.log(`[Cache] Fetching ${item.modelname}`);
|
||||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
|
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems);
|
||||||
modelsLoaded++;
|
modelsLoaded++;
|
||||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||||
return;
|
return;
|
||||||
@@ -87,7 +85,7 @@ async function loadInitialFloorItems(
|
|||||||
URL.revokeObjectURL(blobUrl);
|
URL.revokeObjectURL(blobUrl);
|
||||||
THREE.Cache.remove(blobUrl);
|
THREE.Cache.remove(blobUrl);
|
||||||
THREE.Cache.add(item.modelfileID!, gltf);
|
THREE.Cache.add(item.modelfileID!, gltf);
|
||||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
|
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||||
modelsLoaded++;
|
modelsLoaded++;
|
||||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||||
},
|
},
|
||||||
@@ -108,7 +106,7 @@ async function loadInitialFloorItems(
|
|||||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||||
await storeGLTF(item.modelfileID!, modelBlob);
|
await storeGLTF(item.modelfileID!, modelBlob);
|
||||||
THREE.Cache.add(item.modelfileID!, gltf);
|
THREE.Cache.add(item.modelfileID!, gltf);
|
||||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
|
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||||
modelsLoaded++;
|
modelsLoaded++;
|
||||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||||
},
|
},
|
||||||
@@ -134,10 +132,6 @@ async function loadInitialFloorItems(
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (item.eventData) {
|
|
||||||
processEventData(item, setSimulationStates);
|
|
||||||
}
|
|
||||||
|
|
||||||
modelsLoaded++;
|
modelsLoaded++;
|
||||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
|
||||||
}
|
}
|
||||||
@@ -151,10 +145,9 @@ async function loadInitialFloorItems(
|
|||||||
|
|
||||||
function processLoadedModel(
|
function processLoadedModel(
|
||||||
gltf: any,
|
gltf: any,
|
||||||
item: SimulationTypes.EventData,
|
item: Types.FloorItemType,
|
||||||
itemsGroup: Types.RefGroup,
|
itemsGroup: Types.RefGroup,
|
||||||
setFloorItems: Types.setFloorItemSetState,
|
setFloorItems: Types.setFloorItemSetState,
|
||||||
setSimulationStates: (paths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => void
|
|
||||||
) {
|
) {
|
||||||
const model = gltf;
|
const model = gltf;
|
||||||
model.uuid = item.modeluuid;
|
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.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' });
|
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(
|
function checkLoadingCompletion(
|
||||||
modelsLoaded: number,
|
modelsLoaded: number,
|
||||||
modelsToLoad: number,
|
modelsToLoad: number,
|
||||||
@@ -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;
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -6,14 +6,14 @@ import { useThree, useFrame } from "@react-three/fiber";
|
|||||||
|
|
||||||
////////// Component Imports //////////
|
////////// Component Imports //////////
|
||||||
|
|
||||||
import DistanceText from "../../builder/geomentries/lines/distanceText/distanceText";
|
import DistanceText from "./geomentries/lines/distanceText/distanceText";
|
||||||
import ReferenceDistanceText from "../../builder/geomentries/lines/distanceText/referenceDistanceText";
|
import ReferenceDistanceText from "./geomentries/lines/distanceText/referenceDistanceText";
|
||||||
|
|
||||||
////////// Assests Imports //////////
|
////////// Assests Imports //////////
|
||||||
|
|
||||||
import arch from "../../../assets/gltf-glb/arch.glb";
|
import arch from "../../assets/gltf-glb/arch.glb";
|
||||||
import door from "../../../assets/gltf-glb/door.glb";
|
import door from "../../assets/gltf-glb/door.glb";
|
||||||
import Window from "../../../assets/gltf-glb/window.glb";
|
import Window from "../../assets/gltf-glb/window.glb";
|
||||||
|
|
||||||
////////// Zustand State Imports //////////
|
////////// Zustand State Imports //////////
|
||||||
|
|
||||||
@@ -32,31 +32,31 @@ import {
|
|||||||
useRefTextUpdate,
|
useRefTextUpdate,
|
||||||
useRenderDistance,
|
useRenderDistance,
|
||||||
useLimitDistance,
|
useLimitDistance,
|
||||||
} from "../../../store/store";
|
} from "../../store/store";
|
||||||
|
|
||||||
////////// 3D Function Imports //////////
|
////////// 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 SocketResponses from "../collaboration/socketResponses.dev";
|
||||||
import FloorItemsGroup from "../../builder/groups/floorItemsGroup";
|
import FloorItemsGroup from "./groups/floorItemsGroup";
|
||||||
import FloorPlanGroup from "../../builder/groups/floorPlanGroup";
|
import FloorPlanGroup from "./groups/floorPlanGroup";
|
||||||
import FloorGroup from "../../builder/groups/floorGroup";
|
import FloorGroup from "./groups/floorGroup";
|
||||||
import FloorGroupAilse from "../../builder/groups/floorGroupAisle";
|
import FloorGroupAilse from "./groups/floorGroupAisle";
|
||||||
import Draw from "../../builder/functions/draw";
|
import Draw from "./functions/draw";
|
||||||
import WallsAndWallItems from "../../builder/groups/wallsAndWallItems";
|
import WallsAndWallItems from "./groups/wallsAndWallItems";
|
||||||
import Ground from "../environment/ground";
|
import Ground from "../scene/environment/ground";
|
||||||
// import ZoneGroup from "../groups/zoneGroup1";
|
// import ZoneGroup from "../groups/zoneGroup1";
|
||||||
import { findEnvironment } from "../../../services/factoryBuilder/environment/findEnvironment";
|
import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment";
|
||||||
import Layer2DVisibility from "../../builder/geomentries/layers/layer2DVisibility";
|
import Layer2DVisibility from "./geomentries/layers/layer2DVisibility";
|
||||||
import DrieHtmlTemp from "../mqttTemp/drieHtmlTemp";
|
import DrieHtmlTemp from "..//visualization/mqttTemp/drieHtmlTemp";
|
||||||
import ZoneGroup from "../../builder/groups/zoneGroup";
|
import ZoneGroup from "./groups/zoneGroup";
|
||||||
import useModuleStore from "../../../store/useModuleStore";
|
import useModuleStore from "../../store/useModuleStore";
|
||||||
import NavMeshCreator from "../../builder/agv/navMeshCreator";
|
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 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 csg = useRef(); // Reference for CSG object, used for 3D modeling.
|
||||||
const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
|
const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
|
||||||
@@ -365,10 +365,9 @@ export default function World() {
|
|||||||
anglesnappedPoint={anglesnappedPoint}
|
anglesnappedPoint={anglesnappedPoint}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MeasurementTool />
|
||||||
|
|
||||||
{/* <DrieHtmlTemp itemsGroup={itemsGroup} /> */}
|
{/* <DrieHtmlTemp itemsGroup={itemsGroup} /> */}
|
||||||
|
|
||||||
<NavMeshCreator lines={lines} />
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,10 @@ import { toast } from 'react-toastify';
|
|||||||
import TempLoader from './tempLoader';
|
import TempLoader from './tempLoader';
|
||||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||||
import * as Types from "../../../../types/world/worldTypes";
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
import * as SimulationTypes from "../../../../types/simulationTypes";
|
|
||||||
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
||||||
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
import { getAssetEventType } from '../../../../services/simulation/getAssetEventType';
|
|
||||||
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||||
|
|
||||||
async function addAssetModel(
|
async function addAssetModel(
|
||||||
@@ -25,7 +23,6 @@ async function addAssetModel(
|
|||||||
socket: Socket<any>,
|
socket: Socket<any>,
|
||||||
selectedItem: any,
|
selectedItem: any,
|
||||||
setSelectedItem: any,
|
setSelectedItem: any,
|
||||||
setSimulationStates: any,
|
|
||||||
plane: Types.RefMesh,
|
plane: Types.RefMesh,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
||||||
@@ -66,7 +63,7 @@ async function addAssetModel(
|
|||||||
const cachedModel = THREE.Cache.get(selectedItem.id);
|
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||||
if (cachedModel) {
|
if (cachedModel) {
|
||||||
// console.log(`[Cache] Fetching ${selectedItem.name}`);
|
// 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;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||||
@@ -79,7 +76,7 @@ async function addAssetModel(
|
|||||||
URL.revokeObjectURL(blobUrl);
|
URL.revokeObjectURL(blobUrl);
|
||||||
THREE.Cache.remove(blobUrl);
|
THREE.Cache.remove(blobUrl);
|
||||||
THREE.Cache.add(selectedItem.id, gltf);
|
THREE.Cache.add(selectedItem.id, gltf);
|
||||||
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
|
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
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());
|
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||||
await storeGLTF(selectedItem.id, modelBlob);
|
await storeGLTF(selectedItem.id, modelBlob);
|
||||||
THREE.Cache.add(selectedItem.id, gltf);
|
THREE.Cache.add(selectedItem.id, gltf);
|
||||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
|
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||||
@@ -114,7 +111,6 @@ async function handleModelLoad(
|
|||||||
tempLoader: Types.RefMesh,
|
tempLoader: Types.RefMesh,
|
||||||
isTempLoader: Types.RefBoolean,
|
isTempLoader: Types.RefBoolean,
|
||||||
setFloorItems: Types.setFloorItemSetState,
|
setFloorItems: Types.setFloorItemSetState,
|
||||||
setSimulationStates: any,
|
|
||||||
socket: Socket<any>
|
socket: Socket<any>
|
||||||
) {
|
) {
|
||||||
const model = gltf.scene.clone();
|
const model = gltf.scene.clone();
|
||||||
@@ -137,7 +133,7 @@ async function handleModelLoad(
|
|||||||
tempLoader.current = undefined;
|
tempLoader.current = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newFloorItem: SimulationTypes.EventData = {
|
const newFloorItem: Types.FloorItemType = {
|
||||||
modeluuid: model.uuid,
|
modeluuid: model.uuid,
|
||||||
modelname: selectedItem.name,
|
modelname: selectedItem.name,
|
||||||
modelfileID: selectedItem.id,
|
modelfileID: selectedItem.id,
|
||||||
@@ -150,276 +146,6 @@ async function handleModelLoad(
|
|||||||
const email = localStorage.getItem("email");
|
const email = localStorage.getItem("email");
|
||||||
const organization = email ? email.split("@")[1].split(".")[0] : "";
|
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
|
// API
|
||||||
|
|
||||||
// await setFloorItemApi(
|
// await setFloorItemApi(
|
||||||
@@ -455,11 +181,9 @@ async function handleModelLoad(
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.emit("v2:model-asset:add", data);
|
socket.emit("v2:model-asset:add", data);
|
||||||
}
|
|
||||||
|
|
||||||
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
|
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!"); } });
|
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default addAssetModel;
|
export default addAssetModel;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { toast } from 'react-toastify';
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
import * as Types from "../../../../types/world/worldTypes";
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
import * as SimulationTypes from "../../../../types/simulationTypes";
|
|
||||||
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { getFloorAssets } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
import { getFloorAssets } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||||
@@ -11,7 +10,6 @@ async function DeleteFloorItems(
|
|||||||
itemsGroup: Types.RefGroup,
|
itemsGroup: Types.RefGroup,
|
||||||
hoveredDeletableFloorItem: Types.RefMesh,
|
hoveredDeletableFloorItem: Types.RefMesh,
|
||||||
setFloorItems: Types.setFloorItemSetState,
|
setFloorItems: Types.setFloorItemSetState,
|
||||||
setSimulationStates: any,
|
|
||||||
socket: Socket<any>
|
socket: Socket<any>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
||||||
@@ -77,11 +75,6 @@ async function DeleteFloorItems(
|
|||||||
}
|
}
|
||||||
setFloorItems(updatedItems);
|
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!");
|
toast.success("Model Removed!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useFrame, useThree } from "@react-three/fiber";
|
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 assetVisibility from "../geomentries/assets/assetVisibility";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import * as THREE from "three";
|
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 { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||||
import DeletableHoveredFloorItems from "../geomentries/assets/deletableHoveredFloorItems";
|
import DeletableHoveredFloorItems from "../geomentries/assets/deletableHoveredFloorItems";
|
||||||
import DeleteFloorItems from "../geomentries/assets/deleteFloorItems";
|
import DeleteFloorItems from "../geomentries/assets/deleteFloorItems";
|
||||||
import loadInitialFloorItems from "../../scene/IntialLoad/loadInitialFloorItems";
|
import loadInitialFloorItems from "../IntialLoad/loadInitialFloorItems";
|
||||||
import addAssetModel from "../geomentries/assets/addAssetModel";
|
import addAssetModel from "../geomentries/assets/addAssetModel";
|
||||||
import { getFloorAssets } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi";
|
import { getFloorAssets } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi";
|
||||||
import useModuleStore from "../../../store/useModuleStore";
|
import useModuleStore from "../../../store/useModuleStore";
|
||||||
@@ -32,7 +32,6 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
|||||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||||
const { activeTool } = useActiveTool();
|
const { activeTool } = useActiveTool();
|
||||||
const { selectedItem, setSelectedItem } = useSelectedItem();
|
const { selectedItem, setSelectedItem } = useSelectedItem();
|
||||||
const { simulationStates, setSimulationStates } = useSimulationStates();
|
|
||||||
const { setLoadingProgress } = useLoadingProgress();
|
const { setLoadingProgress } = useLoadingProgress();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
@@ -73,7 +72,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
|||||||
gltfLoaderWorker.postMessage({ floorItems: data });
|
gltfLoaderWorker.postMessage({ floorItems: data });
|
||||||
} else {
|
} else {
|
||||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||||
loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates);
|
loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||||
updateLoadingProgress(100);
|
updateLoadingProgress(100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -92,7 +91,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
|||||||
updateLoadingProgress(progress);
|
updateLoadingProgress(progress);
|
||||||
|
|
||||||
if (loadedAssets === totalAssets) {
|
if (loadedAssets === totalAssets) {
|
||||||
loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates);
|
loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||||
updateLoadingProgress(100);
|
updateLoadingProgress(100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -192,9 +191,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
|||||||
if (drag) return;
|
if (drag) return;
|
||||||
|
|
||||||
if (deleteTool) {
|
if (deleteTool) {
|
||||||
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, setSimulationStates, socket);
|
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, socket);
|
||||||
|
|
||||||
// Remove EventData if there are any in the asset.
|
|
||||||
}
|
}
|
||||||
const Mode = transformMode;
|
const Mode = transformMode;
|
||||||
|
|
||||||
@@ -278,7 +275,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
|||||||
if (!event.dataTransfer?.files[0]) return;
|
if (!event.dataTransfer?.files[0]) return;
|
||||||
|
|
||||||
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,14 @@ import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
|
|||||||
import DeleteLayer from "../geomentries/layers/deleteLayer";
|
import DeleteLayer from "../geomentries/layers/deleteLayer";
|
||||||
import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
|
import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
|
||||||
import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
|
import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
|
||||||
import loadInitialPoint from "../../scene/IntialLoad/loadInitialPoint";
|
import loadInitialPoint from "../IntialLoad/loadInitialPoint";
|
||||||
import loadInitialLine from "../../scene/IntialLoad/loadInitialLine";
|
import loadInitialLine from "../IntialLoad/loadInitialLine";
|
||||||
import deletePoint from "../geomentries/points/deletePoint";
|
import deletePoint from "../geomentries/points/deletePoint";
|
||||||
import deleteLine from "../geomentries/lines/deleteLine";
|
import deleteLine from "../geomentries/lines/deleteLine";
|
||||||
import drawWall from "../geomentries/lines/drawWall";
|
import drawWall from "../geomentries/lines/drawWall";
|
||||||
import drawOnlyFloor from "../geomentries/floors/drawOnlyFloor";
|
import drawOnlyFloor from "../geomentries/floors/drawOnlyFloor";
|
||||||
import addDragControl from "../eventDeclaration/dragControlDeclaration";
|
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 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 state = useThree();
|
||||||
const { scene, camera, gl, raycaster, controls } = state;
|
const { scene, camera, gl, raycaster, controls } = state;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as THREE from "three";
|
|||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
|
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
|
||||||
import DeleteWallItems from "../geomentries/walls/deleteWallItems";
|
import DeleteWallItems from "../geomentries/walls/deleteWallItems";
|
||||||
import loadInitialWallItems from "../../scene/IntialLoad/loadInitialWallItems";
|
import loadInitialWallItems from "../IntialLoad/loadInitialWallItems";
|
||||||
import AddWallItems from "../geomentries/walls/addWallItems";
|
import AddWallItems from "../geomentries/walls/addWallItems";
|
||||||
import useModuleStore from "../../../store/useModuleStore";
|
import useModuleStore from "../../../store/useModuleStore";
|
||||||
|
|
||||||
@@ -37,51 +37,6 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable
|
|||||||
|
|
||||||
////////// Update the Rotation value changes in the selected item //////////
|
////////// 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(() => {
|
useEffect(() => {
|
||||||
const canvasElement = state.gl.domElement;
|
const canvasElement = state.gl.domElement;
|
||||||
function handlePointerMove(e: any) {
|
function handlePointerMove(e: any) {
|
||||||
|
|||||||
231
app/src/modules/collaboration/camera/collabCams.tsx
Normal file
231
app/src/modules/collaboration/camera/collabCams.tsx
Normal 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;
|
||||||
@@ -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;
|
|
||||||
14
app/src/modules/collaboration/collaboration.tsx
Normal file
14
app/src/modules/collaboration/collaboration.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CamModelsGroup from './camera/collabCams'
|
||||||
|
|
||||||
|
const Collaboration = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<CamModelsGroup />
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Collaboration
|
||||||
@@ -9,6 +9,7 @@ import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi"
|
|||||||
import updateCamPosition from "../camera/updateCameraPosition";
|
import updateCamPosition from "../camera/updateCameraPosition";
|
||||||
import CamMode from "../camera/camMode";
|
import CamMode from "../camera/camMode";
|
||||||
import SwitchView from "../camera/switchView";
|
import SwitchView from "../camera/switchView";
|
||||||
|
import SelectionControls from "./selectionControls/selectionControls";
|
||||||
|
|
||||||
export default function Controls() {
|
export default function Controls() {
|
||||||
const controlsRef = useRef<CameraControls>(null);
|
const controlsRef = useRef<CameraControls>(null);
|
||||||
@@ -128,9 +129,15 @@ export default function Controls() {
|
|||||||
boundaryEnclosesCamera={true}
|
boundaryEnclosesCamera={true}
|
||||||
dollyToCursor={toggleView}
|
dollyToCursor={toggleView}
|
||||||
>
|
>
|
||||||
|
|
||||||
<SwitchView />
|
<SwitchView />
|
||||||
|
|
||||||
<CamMode />
|
<CamMode />
|
||||||
|
|
||||||
</CameraControls>
|
</CameraControls>
|
||||||
|
|
||||||
|
<SelectionControls />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -3,12 +3,11 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|||||||
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
|
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
|
||||||
import { SelectionHelper } from "./selectionHelper";
|
import { SelectionHelper } from "./selectionHelper";
|
||||||
import { useFrame, useThree } from "@react-three/fiber";
|
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 BoundingBox from "./boundingBoxHelper";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||||
import * as Types from "../../../../types/world/worldTypes";
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
import * as SimulationTypes from "../../../../types/simulationTypes";
|
|
||||||
|
|
||||||
import DuplicationControls from "./duplicationControls";
|
import DuplicationControls from "./duplicationControls";
|
||||||
import CopyPasteControls from "./copyPasteControls";
|
import CopyPasteControls from "./copyPasteControls";
|
||||||
@@ -21,7 +20,6 @@ const SelectionControls: React.FC = () => {
|
|||||||
const itemsGroupRef = useRef<THREE.Group | undefined>(undefined);
|
const itemsGroupRef = useRef<THREE.Group | undefined>(undefined);
|
||||||
const selectionGroup = useRef() as Types.RefGroup;
|
const selectionGroup = useRef() as Types.RefGroup;
|
||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
const { simulationStates, setSimulationStates } = useSimulationStates();
|
|
||||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||||
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
|
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
|
||||||
const [rotatedObjects, setRotatedObjects] = useState<THREE.Object3D[]>([]);
|
const [rotatedObjects, setRotatedObjects] = useState<THREE.Object3D[]>([]);
|
||||||
@@ -199,203 +197,6 @@ const SelectionControls: React.FC = () => {
|
|||||||
setDuplicatedObjects([]);
|
setDuplicatedObjects([]);
|
||||||
setSelectedAssets([]);
|
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 = () => {
|
const deleteSelection = () => {
|
||||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
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);
|
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));
|
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
|
||||||
setFloorItems(updatedItems);
|
setFloorItems(updatedItems);
|
||||||
}
|
}
|
||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
|
||||||
export default function Sun() {
|
export default function Sun() {
|
||||||
|
const savedTheme: string | null = localStorage.getItem("theme");
|
||||||
const { elevation, setElevation } = useElevation();
|
const { elevation, setElevation } = useElevation();
|
||||||
const { sunPosition, setSunPosition } = useSunPosition();
|
const { sunPosition, setSunPosition } = useSunPosition();
|
||||||
const { azimuth, setAzimuth } = useAzimuth();
|
const { azimuth, setAzimuth } = useAzimuth();
|
||||||
@@ -28,7 +29,7 @@ export default function Sun() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(azimuth !== undefined && elevation !== undefined) && (
|
{(azimuth !== undefined && elevation !== undefined && savedTheme !== "dark") && (
|
||||||
<>
|
<>
|
||||||
<Sky
|
<Sky
|
||||||
distance={CONSTANTS.skyConfig.skyDistance}
|
distance={CONSTANTS.skyConfig.skyDistance}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { EffectComposer, N8AO, Outline } from "@react-three/postprocessing";
|
|||||||
import { BlendFunction } from "postprocessing";
|
import { BlendFunction } from "postprocessing";
|
||||||
import {
|
import {
|
||||||
useDeletableFloorItem,
|
useDeletableFloorItem,
|
||||||
useSelectedActionSphere,
|
|
||||||
useSelectedPath,
|
|
||||||
useSelectedWallItem,
|
useSelectedWallItem,
|
||||||
useSelectedFloorItem,
|
useSelectedFloorItem,
|
||||||
} from "../../../store/store";
|
} from "../../../store/store";
|
||||||
@@ -16,8 +14,6 @@ export default function PostProcessing() {
|
|||||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||||
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
|
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
|
||||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||||
const { selectedActionSphere } = useSelectedActionSphere();
|
|
||||||
const { selectedPath } = useSelectedPath();
|
|
||||||
|
|
||||||
function flattenChildren(children: any[]) {
|
function flattenChildren(children: any[]) {
|
||||||
const allChildren: any[] = [];
|
const allChildren: any[] = [];
|
||||||
@@ -89,36 +85,6 @@ export default function PostProcessing() {
|
|||||||
xRay={true}
|
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>
|
</EffectComposer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,62 +1,32 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Canvas } from "@react-three/fiber";
|
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 Builder from "../builder/builder";
|
||||||
import Controls from "./controls/controls";
|
import Visualization from "../visualization/visualization";
|
||||||
import TransformControl from "./controls/transformControls";
|
import Setup from "./setup/setup";
|
||||||
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 Simulation from "../simulation/simulation";
|
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() {
|
export default function Scene() {
|
||||||
const map = useMemo(
|
const map = useMemo(() => [
|
||||||
() => [
|
|
||||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||||
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
||||||
{ name: "right", keys: ["ArrowRight", "d", "D"] },
|
{ name: "right", keys: ["ArrowRight", "d", "D"] },],
|
||||||
],
|
[]);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const savedTheme: string | null = localStorage.getItem("theme");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardControls map={map}>
|
<KeyboardControls map={map}>
|
||||||
<Canvas
|
<Canvas eventPrefix="client" gl={{ powerPreference: "high-performance", antialias: true }} onContextMenu={(e) => { e.preventDefault(); }}>
|
||||||
eventPrefix="client"
|
|
||||||
gl={{ powerPreference: "high-performance", antialias: true }}
|
<Setup />
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
<Builder />
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Dropped3dWidgets />
|
|
||||||
<Controls />
|
|
||||||
<TransformControl />
|
|
||||||
<SelectionControls />
|
|
||||||
<MeasurementTool />
|
|
||||||
<World />
|
|
||||||
<ZoneCentreTarget />
|
|
||||||
<ZoneAssets />
|
|
||||||
<Simulation />
|
<Simulation />
|
||||||
<PostProcessing />
|
|
||||||
{savedTheme !== "dark" ? <Sun /> : <></>}
|
<Visualization />
|
||||||
<Shadows />
|
|
||||||
<CamModelsGroup />
|
|
||||||
<MqttEvents />
|
|
||||||
<Environment files={background} environmentIntensity={1.5} />
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</KeyboardControls>
|
</KeyboardControls>
|
||||||
);
|
);
|
||||||
|
|||||||
25
app/src/modules/scene/setup/setup.tsx
Normal file
25
app/src/modules/scene/setup/setup.tsx
Normal 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
|
||||||
@@ -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;
|
|
||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,74 +1,10 @@
|
|||||||
import { useState, useRef } from "react";
|
import React 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Simulation() {
|
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 (
|
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
|
||||||
@@ -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;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
function ColliderCreator() {
|
|
||||||
return (
|
|
||||||
<></>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ColliderCreator
|
|
||||||
@@ -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;
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
function ProcessCreator() {
|
|
||||||
return (
|
|
||||||
<></>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProcessCreator
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
|
|
||||||
@@ -3,7 +3,7 @@ import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
|||||||
import Panel from "./widgets/panel/Panel";
|
import Panel from "./widgets/panel/Panel";
|
||||||
import AddButtons from "./widgets/panel/AddButtons";
|
import AddButtons from "./widgets/panel/AddButtons";
|
||||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||||
import DisplayZone from "./DisplayZone";
|
import DisplayZone from "./zone/DisplayZone";
|
||||||
import Scene from "../scene/scene";
|
import Scene from "../scene/scene";
|
||||||
import useModuleStore from "../../store/useModuleStore";
|
import useModuleStore from "../../store/useModuleStore";
|
||||||
|
|
||||||
@@ -17,10 +17,10 @@ import {
|
|||||||
useWidgetSubOption,
|
useWidgetSubOption,
|
||||||
useZones,
|
useZones,
|
||||||
} from "../../store/store";
|
} from "../../store/store";
|
||||||
import { getZone2dData } from "../../services/realTimeVisulization/zoneData/getZoneData";
|
import { getZone2dData } from "../../services/visulization/zone/getZoneData";
|
||||||
import { generateUniqueId } from "../../functions/generateUniqueId";
|
import { generateUniqueId } from "../../functions/generateUniqueId";
|
||||||
import { determinePosition } from "./functions/determinePosition";
|
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 SocketRealTimeViz from "./socket/realTimeVizSocket.dev";
|
||||||
import RenderOverlay from "../../components/templates/Overlay";
|
import RenderOverlay from "../../components/templates/Overlay";
|
||||||
import ConfirmationPopup from "../../components/layout/confirmationPopup/ConfirmationPopup";
|
import ConfirmationPopup from "../../components/layout/confirmationPopup/ConfirmationPopup";
|
||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
useRightClickSelected,
|
useRightClickSelected,
|
||||||
useRightSelected,
|
useRightSelected,
|
||||||
} from "../../store/useZone3DWidgetStore";
|
} from "../../store/useZone3DWidgetStore";
|
||||||
import Dropped3dWidgets from "./widgets/3d/Dropped3dWidget";
|
|
||||||
import OuterClick from "../../utils/outerClick";
|
import OuterClick from "../../utils/outerClick";
|
||||||
import { useWidgetStore } from "../../store/useWidgetStore";
|
import { useWidgetStore } from "../../store/useWidgetStore";
|
||||||
import { getActiveProperties } from "./functions/getActiveProperties";
|
import { getActiveProperties } from "./functions/getActiveProperties";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||||||
import useTemplateStore from "../../../store/useTemplateStore";
|
import useTemplateStore from "../../../store/useTemplateStore";
|
||||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||||
import { useSocketStore } from "../../../store/store";
|
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 { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
|
||||||
import RenameInput from "../../../components/ui/inputs/RenameInput";
|
import RenameInput from "../../../components/ui/inputs/RenameInput";
|
||||||
|
|
||||||
|
|||||||
22
app/src/modules/visualization/visualization.tsx
Normal file
22
app/src/modules/visualization/visualization.tsx
Normal 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;
|
||||||
@@ -13,8 +13,6 @@ import {
|
|||||||
KebabIcon,
|
KebabIcon,
|
||||||
} from "../../../../components/icons/ExportCommonIcons";
|
} from "../../../../components/icons/ExportCommonIcons";
|
||||||
import { useEffect, useRef, useState } from "react";
|
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 { useClickOutside } from "../../functions/handleWidgetsOuterClick";
|
||||||
import { useSocketStore } from "../../../../store/store";
|
import { useSocketStore } from "../../../../store/store";
|
||||||
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ThreeState } from "../../../../types/world/worldTypes";
|
|||||||
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
|
||||||
import { useEditWidgetOptionsStore, useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore } from "../../../../store/useZone3DWidgetStore";
|
import { useEditWidgetOptionsStore, useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore } from "../../../../store/useZone3DWidgetStore";
|
||||||
import { use3DWidget } from "../../../../store/useDroppedObjectsStore";
|
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 { generateUniqueId } from "../../../../functions/generateUniqueId";
|
||||||
import ProductionCapacity from "./cards/ProductionCapacity";
|
import ProductionCapacity from "./cards/ProductionCapacity";
|
||||||
import ReturnOfInvestment from "./cards/ReturnOfInvestment";
|
import ReturnOfInvestment from "./cards/ReturnOfInvestment";
|
||||||
@@ -16,10 +16,6 @@ import Throughput from "./cards/Throughput";
|
|||||||
import { useWidgetStore } from "../../../../store/useWidgetStore";
|
import { useWidgetStore } from "../../../../store/useWidgetStore";
|
||||||
import useChartStore from "../../../../store/useChartStore";
|
import useChartStore from "../../../../store/useChartStore";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type WidgetData = {
|
type WidgetData = {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import {
|
|||||||
import useModuleStore from "../../../../store/useModuleStore";
|
import useModuleStore from "../../../../store/useModuleStore";
|
||||||
import { determinePosition } from "../../functions/determinePosition";
|
import { determinePosition } from "../../functions/determinePosition";
|
||||||
import { getActiveProperties } from "../../functions/getActiveProperties";
|
import { getActiveProperties } from "../../functions/getActiveProperties";
|
||||||
import { addingFloatingWidgets } from "../../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
|
import { addingFloatingWidgets } from "../../../../services/visulization/zone/addFloatingWidgets";
|
||||||
import {
|
import {
|
||||||
DublicateIcon,
|
DublicateIcon,
|
||||||
KebabIcon,
|
KebabIcon,
|
||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
} from "../../../../components/icons/ExportCommonIcons";
|
} from "../../../../components/icons/ExportCommonIcons";
|
||||||
import DistanceLines from "./DistanceLines"; // Import the DistanceLines component
|
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 TotalCardComponent from "./cards/TotalCardComponent";
|
||||||
import WarehouseThroughputComponent from "./cards/WarehouseThroughputComponent";
|
import WarehouseThroughputComponent from "./cards/WarehouseThroughputComponent";
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
} from "../../../../components/icons/RealTimeVisulationIcons";
|
} from "../../../../components/icons/RealTimeVisulationIcons";
|
||||||
import { AddIcon } from "../../../../components/icons/ExportCommonIcons";
|
import { AddIcon } from "../../../../components/icons/ExportCommonIcons";
|
||||||
import { useSocketStore } from "../../../../store/store";
|
import { useSocketStore } from "../../../../store/store";
|
||||||
import { clearPanel } from "../../../../services/realTimeVisulization/zoneData/clearPanel";
|
import { clearPanel } from "../../../../services/visulization/zone/clearPanel";
|
||||||
import { lockPanel } from "../../../../services/realTimeVisulization/zoneData/lockPanel";
|
import { lockPanel } from "../../../../services/visulization/zone/lockPanel";
|
||||||
|
|
||||||
// Define the type for `Side`
|
// Define the type for `Side`
|
||||||
type Side = "top" | "bottom" | "left" | "right";
|
type Side = "top" | "bottom" | "left" | "right";
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React, { useEffect, useRef, useState, useCallback } from "react";
|
import React, { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import { useWidgetStore, Widget } from "../../store/useWidgetStore";
|
import { useWidgetStore, Widget } from "../../../store/useWidgetStore";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useDroppedObjectsStore,
|
useDroppedObjectsStore,
|
||||||
useFloatingWidget,
|
useFloatingWidget,
|
||||||
} from "../../store/useDroppedObjectsStore";
|
} from "../../../store/useDroppedObjectsStore";
|
||||||
import { getSelect2dZoneData } from "../../services/realTimeVisulization/zoneData/getSelect2dZoneData";
|
import { getSelect2dZoneData } from "../../../services/visulization/zone/getSelect2dZoneData";
|
||||||
import { getFloatingZoneData } from "../../services/realTimeVisulization/zoneData/getFloatingData";
|
import { getFloatingZoneData } from "../../../services/visulization/zone/getFloatingData";
|
||||||
import { get3dWidgetZoneData } from "../../services/realTimeVisulization/zoneData/get3dWidgetData";
|
import { get3dWidgetZoneData } from "../../../services/visulization/zone/get3dWidgetData";
|
||||||
import {
|
import {
|
||||||
MoveArrowLeft,
|
MoveArrowLeft,
|
||||||
MoveArrowRight,
|
MoveArrowRight,
|
||||||
} from "../../components/icons/SimulationIcons";
|
} from "../../../components/icons/SimulationIcons";
|
||||||
import { InfoIcon } from "../../components/icons/ExportCommonIcons";
|
import { InfoIcon } from "../../../components/icons/ExportCommonIcons";
|
||||||
|
|
||||||
// Define the type for `Side`
|
// Define the type for `Side`
|
||||||
type Side = "top" | "bottom" | "left" | "right";
|
type Side = "top" | "bottom" | "left" | "right";
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { useSelectedFloorItem, useZoneAssetId } from '../../store/store';
|
import { useSelectedFloorItem, useZoneAssetId } from '../../../store/store';
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useThree } from '@react-three/fiber';
|
import { useThree } from '@react-three/fiber';
|
||||||
import * as Types from "../../types/world/worldTypes";
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
export default function ZoneAssets() {
|
export default function ZoneAssets() {
|
||||||
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
|
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
|
||||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||||
@@ -10,7 +10,6 @@ export default function ZoneAssets() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('zoneAssetId: ', zoneAssetId);
|
// console.log('zoneAssetId: ', zoneAssetId);
|
||||||
if (!zoneAssetId) return
|
if (!zoneAssetId) return
|
||||||
console.log('zoneAssetId: ', zoneAssetId);
|
|
||||||
let AssetMesh = scene.getObjectByProperty("uuid", zoneAssetId.id);
|
let AssetMesh = scene.getObjectByProperty("uuid", zoneAssetId.id);
|
||||||
if (AssetMesh) {
|
if (AssetMesh) {
|
||||||
const bbox = new THREE.Box3().setFromObject(AssetMesh);
|
const bbox = new THREE.Box3().setFromObject(AssetMesh);
|
||||||
@@ -30,20 +29,17 @@ export default function ZoneAssets() {
|
|||||||
|
|
||||||
setSelectedFloorItem(AssetMesh);
|
setSelectedFloorItem(AssetMesh);
|
||||||
} else {
|
} else {
|
||||||
console.log('zoneAssetId: ', zoneAssetId)
|
|
||||||
if (Array.isArray(zoneAssetId.position) && zoneAssetId.position.length >= 3) {
|
if (Array.isArray(zoneAssetId.position) && zoneAssetId.position.length >= 3) {
|
||||||
let selectedAssetPosition = [
|
let selectedAssetPosition = [
|
||||||
zoneAssetId.position[0],
|
zoneAssetId.position[0],
|
||||||
10,
|
10,
|
||||||
zoneAssetId.position[2]
|
zoneAssetId.position[2]
|
||||||
];
|
];
|
||||||
console.log('selectedAssetPosition: ', selectedAssetPosition);
|
|
||||||
let selectedAssetTarget = [
|
let selectedAssetTarget = [
|
||||||
zoneAssetId.position[0],
|
zoneAssetId.position[0],
|
||||||
zoneAssetId.position[1],
|
zoneAssetId.position[1],
|
||||||
zoneAssetId.position[2]
|
zoneAssetId.position[2]
|
||||||
];
|
];
|
||||||
console.log('selectedAssetTarget: ', selectedAssetTarget);
|
|
||||||
const setCam = async () => {
|
const setCam = async () => {
|
||||||
await controls?.setLookAt(...selectedAssetPosition, ...selectedAssetTarget, true);
|
await controls?.setLookAt(...selectedAssetPosition, ...selectedAssetTarget, true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -8,13 +8,9 @@ export const setFloorItemApi = async (
|
|||||||
rotation?: Object,
|
rotation?: Object,
|
||||||
isLocked?: boolean,
|
isLocked?: boolean,
|
||||||
isVisible?: boolean,
|
isVisible?: boolean,
|
||||||
eventData?: any
|
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const body: any = { organization, modeluuid, modelname, position, rotation, modelfileID, isLocked, isVisible };
|
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`, {
|
const response = await fetch(`${url_Backend_dwinzo}/api/v2/setasset`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user