v2-ui #68
18
app/package-lock.json
generated
18
app/package-lock.json
generated
@@ -30,6 +30,7 @@
|
||||
"glob": "^11.0.0",
|
||||
"gsap": "^3.12.5",
|
||||
"html2canvas": "^1.4.1",
|
||||
"immer": "^10.1.1",
|
||||
"leva": "^0.10.0",
|
||||
"mqtt": "^5.10.4",
|
||||
"postprocessing": "^6.36.4",
|
||||
@@ -12746,9 +12747,10 @@
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
@@ -18010,6 +18012,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils/node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils/node_modules/loader-utils": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"glob": "^11.0.0",
|
||||
"gsap": "^3.12.5",
|
||||
"html2canvas": "^1.4.1",
|
||||
"immer": "^10.1.1",
|
||||
"leva": "^0.10.0",
|
||||
"mqtt": "^5.10.4",
|
||||
"postprocessing": "^6.36.4",
|
||||
|
||||
@@ -73,7 +73,7 @@ const Assets: React.FC = () => {
|
||||
try {
|
||||
const filt = await fetchAssets();
|
||||
setFiltereredAssets(filt);
|
||||
} catch { }
|
||||
} catch {}
|
||||
};
|
||||
filteredAssets();
|
||||
}, [categoryAssets]);
|
||||
@@ -135,7 +135,7 @@ const Assets: React.FC = () => {
|
||||
const res = await getCategoryAsset(asset);
|
||||
setCategoryAssets(res);
|
||||
setFiltereredAssets(res);
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
}
|
||||
};
|
||||
return (
|
||||
@@ -151,7 +151,12 @@ const Assets: React.FC = () => {
|
||||
<div className="assets-container">
|
||||
{categoryAssets &&
|
||||
categoryAssets?.map((asset: any, index: number) => (
|
||||
<div key={index} className="assets" id={asset.filename}>
|
||||
<div
|
||||
key={index}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
@@ -187,17 +192,24 @@ const Assets: React.FC = () => {
|
||||
<div className="assets-container">
|
||||
{categoryAssets &&
|
||||
categoryAssets?.map((asset: any, index: number) => (
|
||||
<div key={index} className="assets" id={asset.filename}>
|
||||
<div
|
||||
key={index}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() =>
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
})
|
||||
}
|
||||
type:
|
||||
asset.type === "undefined" ? undefined : asset.type,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="asset-name">
|
||||
|
||||
@@ -5,8 +5,8 @@ import Header from "./Header";
|
||||
import useToggleStore from "../../../store/useUIToggleStore";
|
||||
import Assets from "./Assets";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import Widgets from "./visualization/widgets/Widgets";
|
||||
import Templates from "../../../modules/visualization/template/Templates";
|
||||
import Widgets from ".//visualization/widgets/Widgets";
|
||||
import Templates from "../../../modules//visualization/template/Templates";
|
||||
import Search from "../../ui/inputs/Search";
|
||||
|
||||
const SideBarLeft: React.FC = () => {
|
||||
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
GlobeIcon,
|
||||
WalletIcon,
|
||||
} from "../../../../icons/3dChartIcons";
|
||||
import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard";
|
||||
import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard";
|
||||
|
||||
import WarehouseThroughput from "../../../../../modules/visualization/widgets/floating/cards/WarehouseThroughput";
|
||||
import ProductivityDashboard from "../../../../../modules/visualization/widgets/floating/cards/ProductivityDashboard";
|
||||
import FleetEfficiency from "../../../../../modules/visualization/widgets/floating/cards/FleetEfficiency";
|
||||
import WarehouseThroughput from "../../../../../modules//visualization/widgets/floating/cards/WarehouseThroughput";
|
||||
import ProductivityDashboard from "../../../../../modules//visualization/widgets/floating/cards/ProductivityDashboard";
|
||||
import FleetEfficiency from "../../../../../modules//visualization/widgets/floating/cards/FleetEfficiency";
|
||||
|
||||
interface Widget {
|
||||
id: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { AppDockIcon } from "../../icons/HeaderIcons";
|
||||
import orgImg from "../../../assets/orgTemp.png";
|
||||
import { useActiveUsers } from "../../../store/store";
|
||||
import { getAvatarColor } from "../../../modules/collaboration/users/functions/getAvatarColor";
|
||||
import { getAvatarColor } from "../../../functions/users/functions/getAvatarColor";
|
||||
import { ActiveUser } from "../../../types/users";
|
||||
import CollaborationPopup from "../../templates/CollaborationPopup";
|
||||
|
||||
|
||||
@@ -10,33 +10,61 @@ import {
|
||||
SimulationIcon,
|
||||
} from "../../icons/SimulationIcons";
|
||||
import useToggleStore from "../../../store/useUIToggleStore";
|
||||
import ConveyorMechanics from "./mechanics/ConveyorMechanics";
|
||||
import Visualization from "./visualization/Visualization";
|
||||
import Analysis from "./analysis/Analysis";
|
||||
import Simulations from "./simulation/Simulations";
|
||||
import {
|
||||
useSelectedActionSphere,
|
||||
useSelectedFloorItem,
|
||||
} from "../../../store/store";
|
||||
import { useSelectedFloorItem } from "../../../store/store";
|
||||
import GlobalProperties from "./properties/GlobalProperties";
|
||||
import AsstePropertiies from "./properties/AssetProperties";
|
||||
import ZoneProperties from "./properties/ZoneProperties";
|
||||
import VehicleMechanics from "./mechanics/VehicleMechanics";
|
||||
import StaticMachineMechanics from "./mechanics/StaticMachineMechanics";
|
||||
import ArmBotMechanics from "./mechanics/ArmBotMechanics";
|
||||
import EventProperties from "./properties/eventProperties/EventProperties";
|
||||
|
||||
const SideBarRight: React.FC = () => {
|
||||
const { activeModule } = useModuleStore();
|
||||
const { toggleUI } = useToggleStore();
|
||||
const { selectedActionSphere } = useSelectedActionSphere();
|
||||
const { subModule, setSubModule } = useSubModuleStore();
|
||||
const { selectedFloorItem } = useSelectedFloorItem();
|
||||
|
||||
// Reset activeList whenever activeModule changes
|
||||
useEffect(() => {
|
||||
if (activeModule !== "simulation") setSubModule("properties");
|
||||
if (activeModule === "simulation") setSubModule("mechanics");
|
||||
}, [activeModule]);
|
||||
|
||||
// romove late
|
||||
const dummyData = {
|
||||
assetType: "store",
|
||||
selectedPoint: {
|
||||
name: "Point A",
|
||||
uuid: "123e4567-e89b-12d3-a456-426614174000",
|
||||
actions: [
|
||||
{
|
||||
uuid: "action-1",
|
||||
name: "Action One",
|
||||
},
|
||||
{
|
||||
uuid: "action-2",
|
||||
name: "Action Two",
|
||||
},
|
||||
{
|
||||
uuid: "action-3",
|
||||
name: "Action Three",
|
||||
},
|
||||
],
|
||||
},
|
||||
selectedItem: {
|
||||
item: {
|
||||
uuid: "item-1",
|
||||
name: "Item One",
|
||||
isUsed: false,
|
||||
},
|
||||
},
|
||||
setSelectedPoint: (value: string) => {
|
||||
console.log(`Selected point updated to: ${value}`);
|
||||
},
|
||||
selectedActionSphere: "Sphere A",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sidebar-right-wrapper">
|
||||
<Header />
|
||||
@@ -44,8 +72,9 @@ const SideBarRight: React.FC = () => {
|
||||
<div className="sidebar-actions-container">
|
||||
{/* {activeModule === "builder" && ( */}
|
||||
<div
|
||||
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
|
||||
}`}
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "properties" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("properties")}
|
||||
>
|
||||
<PropertiesIcon isActive={subModule === "properties"} />
|
||||
@@ -54,22 +83,25 @@ const SideBarRight: React.FC = () => {
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<div
|
||||
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
|
||||
}`}
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "mechanics" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("mechanics")}
|
||||
>
|
||||
<MechanicsIcon isActive={subModule === "mechanics"} />
|
||||
</div>
|
||||
<div
|
||||
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
|
||||
}`}
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "simulations" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("simulations")}
|
||||
>
|
||||
<SimulationIcon isActive={subModule === "simulations"} />
|
||||
</div>
|
||||
<div
|
||||
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
|
||||
}`}
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "analysis" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setSubModule("analysis")}
|
||||
>
|
||||
<AnalysisIcon isActive={subModule === "analysis"} />
|
||||
@@ -112,46 +144,10 @@ const SideBarRight: React.FC = () => {
|
||||
|
||||
{toggleUI && activeModule === "simulation" && (
|
||||
<>
|
||||
{subModule === "mechanics" &&
|
||||
selectedActionSphere &&
|
||||
selectedActionSphere.path.type === "Conveyor" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<ConveyorMechanics />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "mechanics" &&
|
||||
selectedActionSphere &&
|
||||
selectedActionSphere.path.type === "Vehicle" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<VehicleMechanics />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "mechanics" &&
|
||||
selectedActionSphere &&
|
||||
selectedActionSphere.path.type === "StaticMachine" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<StaticMachineMechanics />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "mechanics" &&
|
||||
selectedActionSphere &&
|
||||
selectedActionSphere.path.type === "ArmBot" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<ArmBotMechanics />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "mechanics" && !selectedActionSphere && (
|
||||
{subModule === "mechanics" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<ConveyorMechanics /> {/* default */}
|
||||
<EventProperties {...dummyData} />
|
||||
</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);
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import Vector3Input from "../customInput/Vector3Input";
|
||||
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
|
||||
import { useSelectedZoneStore } from "../../../../store/visualization/useZoneStore";
|
||||
import { useEditPosition, usezonePosition, useZones, usezoneTarget } from "../../../../store/store";
|
||||
import { zoneCameraUpdate } from "../../../../services/realTimeVisulization/zoneData/zoneCameraUpdation";
|
||||
import { zoneCameraUpdate } from "../../../../services/visulization/zone/zoneCameraUpdation";
|
||||
|
||||
const ZoneProperties: React.FC = () => {
|
||||
const { Edit, setEdit } = useEditPosition();
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import InputWithDropDown from "../../../../ui/inputs/InputWithDropDown";
|
||||
import LabledDropdown from "../../../../ui/inputs/LabledDropdown";
|
||||
import {
|
||||
AddIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
} from "../../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../../functions/handleResizePannel";
|
||||
import { handleActionToggle } from "./functions/handleActionToggle";
|
||||
import { handleDeleteAction } from "./functions/handleDeleteAction";
|
||||
import DefaultAction from "./actions/DefaultAction";
|
||||
import SpawnAction from "./actions/SpawnAction";
|
||||
import SwapAction from "./actions/SwapAction";
|
||||
import DespawnAction from "./actions/DespawnAction";
|
||||
import TravelAction from "./actions/TravelAction";
|
||||
import PickAndPlaceAction from "./actions/PickAndPlaceAction";
|
||||
import ProcessAction from "./actions/ProcessAction";
|
||||
import StorageAction from "./actions/StorageAction";
|
||||
import Trigger from "./trigger/Trigger";
|
||||
|
||||
interface EventPropertiesProps {
|
||||
assetType: string;
|
||||
selectedPoint: {
|
||||
name: string;
|
||||
uuid: string;
|
||||
actions: {
|
||||
uuid: string;
|
||||
name: string;
|
||||
}[];
|
||||
};
|
||||
selectedItem: {
|
||||
item: {
|
||||
uuid: string;
|
||||
name: string;
|
||||
} | null;
|
||||
};
|
||||
setSelectedPoint: (value: string) => void;
|
||||
selectedActionSphere: string;
|
||||
}
|
||||
|
||||
const EventProperties: React.FC<EventPropertiesProps> = ({
|
||||
assetType,
|
||||
selectedPoint,
|
||||
selectedItem,
|
||||
setSelectedPoint,
|
||||
selectedActionSphere,
|
||||
}) => {
|
||||
const actionsContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [activeOption, setActiveOption] = useState("default");
|
||||
const [dummyactiveOption, setTypeOption] = useState("default");
|
||||
|
||||
const getAvailableActions = () => {
|
||||
if (assetType === "conveyor") {
|
||||
return {
|
||||
defaultOption: "default",
|
||||
options: ["default", "spawn", "swap", "despawn"],
|
||||
};
|
||||
}
|
||||
if (assetType === "vehicle") {
|
||||
return {
|
||||
defaultOption: "travel",
|
||||
options: ["travel"],
|
||||
};
|
||||
}
|
||||
if (assetType === "roboticArm") {
|
||||
return {
|
||||
defaultOption: "pickAndPlace",
|
||||
options: ["pickAndPlace"],
|
||||
};
|
||||
}
|
||||
if (assetType === "machine") {
|
||||
return {
|
||||
defaultOption: "process",
|
||||
options: ["process"],
|
||||
};
|
||||
}
|
||||
if (assetType === "store") {
|
||||
return {
|
||||
defaultOption: "store",
|
||||
options: ["store", "spawn"],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
defaultOption: "default",
|
||||
options: ["default"],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="event-proprties-wrapper">
|
||||
<div className="header">
|
||||
<div className="header-value">{selectedPoint.name}</div>
|
||||
</div>
|
||||
<div className="global-props">
|
||||
<div className="property-list-container">
|
||||
{/* <div className="property-item">
|
||||
<LabledDropdown
|
||||
defaultOption={assetType}
|
||||
options={[]}
|
||||
onSelect={(option) => setTypeOption(option)}
|
||||
/>
|
||||
</div> */}
|
||||
<div className="property-item">
|
||||
<InputWithDropDown
|
||||
label="Speed"
|
||||
value="0.5"
|
||||
min={0}
|
||||
step={0.1}
|
||||
defaultValue="0.5"
|
||||
max={10}
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="property-item">
|
||||
<InputWithDropDown
|
||||
label="Delay"
|
||||
value="0.5"
|
||||
min={0}
|
||||
step={0.1}
|
||||
defaultValue="0.5"
|
||||
max={10}
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="actions-list-container">
|
||||
<div className="actions">
|
||||
<div className="header">
|
||||
<div className="header-value">Actions</div>
|
||||
<div className="add-button" onClick={() => {}}>
|
||||
<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.item?.uuid === action.uuid ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="value"
|
||||
onClick={() => handleActionToggle(action.uuid)}
|
||||
>
|
||||
<RenameInput value={action.name} />
|
||||
</div>
|
||||
{selectedPoint?.actions.length > 1 && (
|
||||
<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>
|
||||
<div className="selected-actions-details">
|
||||
<div className="selected-actions-header">
|
||||
<RenameInput value="Action Name" />
|
||||
</div>
|
||||
<div className="selected-actions-list">
|
||||
<LabledDropdown
|
||||
defaultOption={getAvailableActions().defaultOption}
|
||||
options={getAvailableActions().options}
|
||||
onSelect={(option) => setActiveOption(option)}
|
||||
/>
|
||||
{activeOption === "default" && <DefaultAction />} {/* done */}
|
||||
{activeOption === "spawn" && <SpawnAction />} {/* done */}
|
||||
{activeOption === "swap" && <SwapAction />} {/* done */}
|
||||
{activeOption === "despawn" && <DespawnAction />} {/* done */}
|
||||
{activeOption === "travel" && <TravelAction />} {/* done */}
|
||||
{activeOption === "pickAndPlace" && <PickAndPlaceAction />} {/* done */}
|
||||
{activeOption === "process" && <ProcessAction />} {/* done */}
|
||||
{activeOption === "store" && <StorageAction />} {/* done */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventProperties;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const DefaultAction:React.FC = () => {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default DefaultAction;
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
const DespawnAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Delay"
|
||||
value=""
|
||||
min={0}
|
||||
step={0.1}
|
||||
max={10}
|
||||
defaultValue="0"
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DespawnAction;
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import EyeDropInput from "../../../../../ui/inputs/EyeDropInput";
|
||||
|
||||
const PickAndPlaceAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<EyeDropInput label="Pick Point" value="na" onChange={() => {}} />
|
||||
<EyeDropInput label="Unload Point" value="na" onChange={() => {}} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickAndPlaceAction;
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import SwapAction from "./SwapAction";
|
||||
|
||||
const ProcessAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Process Time"
|
||||
value="6"
|
||||
min={0}
|
||||
step={1}
|
||||
max={10}
|
||||
defaultValue="0"
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<SwapAction />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProcessAction;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
const SpawnAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Spawn interval"
|
||||
value="0"
|
||||
min={0}
|
||||
step={1}
|
||||
defaultValue="0"
|
||||
max={10}
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Spawn count"
|
||||
value="0"
|
||||
min={0}
|
||||
step={1}
|
||||
defaultValue="0"
|
||||
max={10}
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<PreviewSelectionWithUpload />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpawnAction;
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
const StorageAction: React.FC = () => {
|
||||
return (
|
||||
<InputWithDropDown
|
||||
label="Storage Capacity"
|
||||
value=""
|
||||
min={0}
|
||||
step={0.1}
|
||||
max={10}
|
||||
defaultValue="0"
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageAction;
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
|
||||
const SwapAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<PreviewSelectionWithUpload />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwapAction;
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import EyeDropInput from "../../../../../ui/inputs/EyeDropInput";
|
||||
|
||||
const TravelAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Load Capacity"
|
||||
value=""
|
||||
min={0}
|
||||
step={0.1}
|
||||
max={10}
|
||||
defaultValue="0"
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Unload Duration"
|
||||
value=""
|
||||
min={0}
|
||||
step={0.1}
|
||||
max={10}
|
||||
defaultValue="0"
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
<EyeDropInput label="Pick Point" value="na" onChange={() => {}} />
|
||||
<EyeDropInput label="Unload Point" value="na" onChange={() => {}} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TravelAction;
|
||||
@@ -0,0 +1,6 @@
|
||||
export function handleActionToggle(uuid: string) {
|
||||
// This function handles the action toggle for the event properties.
|
||||
// It updates the selected action and its properties based on the provided UUID.
|
||||
// The function is currently empty and needs to be implemented.
|
||||
// You can add your logic here to handle the action toggle.
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export function handleDeleteAction(uuid: string) {
|
||||
// This function handles the action toggle for the event properties.
|
||||
// It updates the selected action and its properties based on the provided UUID.
|
||||
// The function is currently empty and needs to be implemented.
|
||||
// You can add your logic here to handle the action toggle.
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import React, { useState } from "react";
|
||||
import { AddIcon, RemoveIcon } from "../../../../../icons/ExportCommonIcons";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
|
||||
const Trigger: React.FC = () => {
|
||||
// State to hold the list of triggers
|
||||
const [triggers, setTriggers] = useState<string[]>([]);
|
||||
const [activeOption, setActiveOption] = useState("onComplete");
|
||||
|
||||
// States for dropdowns
|
||||
const [triggeredModel, setTriggeredModel] = useState<string[]>([]);
|
||||
const [triggeredPoint, setTriggeredPoint] = useState<string[]>([]);
|
||||
const [triggeredAction, setTriggeredAction] = useState<string[]>([]);
|
||||
|
||||
// Function to handle adding a new trigger
|
||||
const addTrigger = (): void => {
|
||||
const newTrigger = `Trigger ${triggers.length + 1}`;
|
||||
setTriggers([...triggers, newTrigger]); // Add new trigger to the state
|
||||
|
||||
// Initialize the states for the new trigger
|
||||
setTriggeredModel([...triggeredModel, ""]);
|
||||
setTriggeredPoint([...triggeredPoint, ""]);
|
||||
setTriggeredAction([...triggeredAction, ""]);
|
||||
};
|
||||
|
||||
// Function to handle removing a trigger
|
||||
const removeTrigger = (index: number): void => {
|
||||
setTriggers(triggers.filter((_, i) => i !== index)); // Remove trigger by index
|
||||
setTriggeredModel(triggeredModel.filter((_, i) => i !== index));
|
||||
setTriggeredPoint(triggeredPoint.filter((_, i) => i !== index));
|
||||
setTriggeredAction(triggeredAction.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="trigger-wrapper">
|
||||
<div className="header">
|
||||
<div className="title">Trigger</div>
|
||||
<div
|
||||
className="add-button"
|
||||
onClick={addTrigger}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<AddIcon /> Add
|
||||
</div>
|
||||
</div>
|
||||
<div className="trigger-list">
|
||||
{/* Map over triggers and render them */}
|
||||
{triggers.map((trigger, index) => (
|
||||
<div key={index} className="trigger-item">
|
||||
<div className="trigger-name">
|
||||
{trigger}
|
||||
<div
|
||||
className="remove-button"
|
||||
onClick={() => removeTrigger(index)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</div>
|
||||
</div>
|
||||
<LabledDropdown
|
||||
defaultOption={activeOption}
|
||||
options={["onComplete", "onStart", "onStop", "delay"]}
|
||||
onSelect={(option) => setActiveOption(option)}
|
||||
/>
|
||||
<div className="trigger-options">
|
||||
<div>
|
||||
<LabledDropdown
|
||||
defaultOption={triggeredModel[index] || "Select Model"}
|
||||
options={["Model 1", "Model 2", "Model 3"]}
|
||||
onSelect={(option) => {
|
||||
const newModel = [...triggeredModel];
|
||||
newModel[index] = option;
|
||||
setTriggeredModel(newModel);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<LabledDropdown
|
||||
defaultOption={triggeredPoint[index] || "Select Point"}
|
||||
options={["Point 1", "Point 2", "Point 3"]}
|
||||
onSelect={(option) => {
|
||||
const newPoint = [...triggeredPoint];
|
||||
newPoint[index] = option;
|
||||
setTriggeredPoint(newPoint);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<LabledDropdown
|
||||
defaultOption={triggeredAction[index] || "Select Action"}
|
||||
options={["Action 1", "Action 2", "Action 3"]}
|
||||
onSelect={(option) => {
|
||||
const newAction = [...triggeredAction];
|
||||
newAction[index] = option;
|
||||
setTriggeredAction(newAction);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Trigger;
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -121,8 +121,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/useZoneStore";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent";
|
||||
import ChartComponent from "../../../sidebarLeft//visualization/widgets/ChartComponent";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import { WalletIcon } from "../../../../icons/3dChartIcons";
|
||||
import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard";
|
||||
import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard";
|
||||
|
||||
interface Widget {
|
||||
id: string;
|
||||
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
} from "../icons/ExportToolsIcons";
|
||||
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
|
||||
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
|
||||
import { handleSaveTemplate } from "../../modules/visualization/functions/handleSaveTemplate";
|
||||
import { handleSaveTemplate } from "../../modules//visualization/functions/handleSaveTemplate";
|
||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import useTemplateStore from "../../store/useTemplateStore";
|
||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
|
||||
import {
|
||||
useActiveTool,
|
||||
useAddAction,
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
use3DWidget,
|
||||
useDroppedObjectsStore,
|
||||
useFloatingWidget,
|
||||
} from "../../store/useDroppedObjectsStore";
|
||||
} from "../../store/visualization/useDroppedObjectsStore";
|
||||
|
||||
const Tools: React.FC = () => {
|
||||
const { templates } = useTemplateStore();
|
||||
|
||||
@@ -5,6 +5,7 @@ type InputWithDropDownProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
defaultValue?: string;
|
||||
options?: string[]; // Array of dropdown options
|
||||
@@ -19,6 +20,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
label,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
defaultValue,
|
||||
options,
|
||||
@@ -47,6 +49,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
<div className="input default" id={separatedWords}>
|
||||
<input
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
type="number"
|
||||
defaultValue={value}
|
||||
|
||||
49
app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx
Normal file
49
app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState } from "react";
|
||||
import LabledDropdown from "./LabledDropdown";
|
||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
const PreviewSelectionWithUpload: React.FC = () => {
|
||||
const [showPreview, setSetshowPreview] = useState(false);
|
||||
return (
|
||||
<div className="preview-selection-with-upload-wrapper">
|
||||
<div
|
||||
className="input-header-container"
|
||||
onClick={() => setSetshowPreview(!showPreview)}
|
||||
>
|
||||
<div className="input-header">Preview</div>
|
||||
<div
|
||||
className="arrow-container"
|
||||
style={{ rotate: showPreview ? "0deg" : "90deg" }}
|
||||
>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
{showPreview && (
|
||||
<div className="canvas-wrapper">
|
||||
<div className="canvas-container"></div>
|
||||
</div>
|
||||
)}
|
||||
<div className="asset-selection-container">
|
||||
<div className="upload-custom-asset-button">
|
||||
<div className="title">Upload Product</div>
|
||||
<input
|
||||
type="file"
|
||||
accept=".glb, .gltf"
|
||||
id="simulation-product-upload"
|
||||
/>
|
||||
<label className="upload-button" htmlFor="simulation-product-upload">
|
||||
Upload here
|
||||
</label>
|
||||
</div>
|
||||
<LabledDropdown
|
||||
label="Presets"
|
||||
defaultOption={"Default material"}
|
||||
options={["Default material", "Product 1", "Product 2"]}
|
||||
onSelect={(option) => console.log(option)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewSelectionWithUpload;
|
||||
@@ -3,8 +3,7 @@ import List from "./List";
|
||||
import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons";
|
||||
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
|
||||
import { useFloorItems, useZones } from "../../../store/store";
|
||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||
import { getZone2dData } from "../../../services/realTimeVisulization/zoneData/getZoneData";
|
||||
import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore";
|
||||
|
||||
interface DropDownListProps {
|
||||
value?: string; // Value to display in the DropDownList
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import RenameInput from "../inputs/RenameInput";
|
||||
|
||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||
import { getZoneData } from "../../../services/realTimeVisulization/zoneData/getZones";
|
||||
import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore";
|
||||
import { getZoneData } from "../../../services/visulization/zone/getZones";
|
||||
import useModuleStore, {
|
||||
useSubModuleStore,
|
||||
} from "../../../store/useModuleStore";
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "../../icons/ExportCommonIcons";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { useFloorItems, useZoneAssetId, useZones } from "../../../store/store";
|
||||
import { zoneCameraUpdate } from "../../../services/realTimeVisulization/zoneData/zoneCameraUpdation";
|
||||
import { zoneCameraUpdate } from "../../../services/visulization/zone/zoneCameraUpdation";
|
||||
import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
|
||||
|
||||
interface Asset {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useRightClickSelected,
|
||||
useRightSelected,
|
||||
useTopData,
|
||||
} from "../../../store/useZone3DWidgetStore";
|
||||
} from "../../../store/visualization/useZone3DWidgetStore";
|
||||
|
||||
interface EditWidgetOptionProps {
|
||||
options: string[];
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import React from "react";
|
||||
import CustomAvatar from "./users/Avatar";
|
||||
|
||||
interface CollabUserIconProps {
|
||||
userName: string;
|
||||
userImage?: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
||||
userImage,
|
||||
userName,
|
||||
color,
|
||||
}) => {
|
||||
return (
|
||||
<div className="collab-user-live-container">
|
||||
<div className="user-image-container">
|
||||
{userImage ? (
|
||||
<img className="user-image" src={userImage} alt={userName} />
|
||||
) : (
|
||||
<CustomAvatar name={userName} color={color} />
|
||||
)}
|
||||
</div>
|
||||
<div className="user-name" style={{ backgroundColor: color }}>
|
||||
{userName}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollabUserIcon;
|
||||
import React from "react";
|
||||
import CustomAvatar from "./users/Avatar";
|
||||
|
||||
interface CollabUserIconProps {
|
||||
userName: string;
|
||||
userImage?: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
||||
userImage,
|
||||
userName,
|
||||
color,
|
||||
}) => {
|
||||
return (
|
||||
<div className="collab-user-live-container">
|
||||
<div className="user-image-container">
|
||||
{userImage ? (
|
||||
<img className="user-image" src={userImage} alt={userName} />
|
||||
) : (
|
||||
<CustomAvatar name={userName} color={color} />
|
||||
)}
|
||||
</div>
|
||||
<div className="user-name" style={{ backgroundColor: color }}>
|
||||
{userName}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollabUserIcon;
|
||||
@@ -25,19 +25,8 @@ const avatarColors: string[] = [
|
||||
export function getAvatarColor(index: number, name?: string): string {
|
||||
// Check if the color is already stored in localStorage
|
||||
const localStorageKey = "userAvatarColors";
|
||||
// Helper function to check if local storage is available
|
||||
function isLocalStorageAvailable(): boolean {
|
||||
try {
|
||||
const testKey = "__test__";
|
||||
localStorage.setItem(testKey, "test");
|
||||
localStorage.removeItem(testKey);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check if local storage is available
|
||||
if (isLocalStorageAvailable() && name) {
|
||||
if (name) {
|
||||
let userColors = JSON.parse(localStorage.getItem(localStorageKey) || "{}");
|
||||
|
||||
// Check if the user already has an assigned color
|
||||
@@ -1,270 +1,202 @@
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import gsap from 'gsap';
|
||||
import * as THREE from 'three';
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import { toast } from 'react-toastify';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import * as SimulationTypes from "../../../types/simulationTypes";
|
||||
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
|
||||
async function loadInitialFloorItems(
|
||||
itemsGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
setSimulationStates: (paths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => void
|
||||
): Promise<void> {
|
||||
if (!itemsGroup.current) return;
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
const email = localStorage.getItem('email');
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
const items = await getFloorAssets(organization);
|
||||
localStorage.setItem("FloorItems", JSON.stringify(items));
|
||||
await initializeDB();
|
||||
|
||||
if (items.message === "floorItems not found") return;
|
||||
|
||||
if (items) {
|
||||
const storedFloorItems: SimulationTypes.EventData[] = items;
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
|
||||
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
let modelsLoaded = 0;
|
||||
const modelsToLoad = storedFloorItems.length;
|
||||
|
||||
const camData = await getCamera(organization, localStorage.getItem('userId')!);
|
||||
let storedPosition;
|
||||
if (camData && camData.position) {
|
||||
storedPosition = camData?.position;
|
||||
} else {
|
||||
storedPosition = new THREE.Vector3(0, 40, 30);
|
||||
}
|
||||
if (!storedPosition) return;
|
||||
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
|
||||
|
||||
storedFloorItems.sort((a, b) => {
|
||||
const aPosition = new THREE.Vector3(a.position[0], a.position[1], a.position[2]);
|
||||
const bPosition = new THREE.Vector3(b.position[0], b.position[1], b.position[2]);
|
||||
return cameraPosition.distanceTo(aPosition) - cameraPosition.distanceTo(bPosition);
|
||||
});
|
||||
|
||||
for (const item of storedFloorItems) {
|
||||
if (!item.modelfileID) return;
|
||||
const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]);
|
||||
let storedPosition;
|
||||
if (localStorage.getItem("cameraPosition")) {
|
||||
storedPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
|
||||
} else {
|
||||
storedPosition = new THREE.Vector3(0, 40, 30);
|
||||
}
|
||||
|
||||
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
|
||||
|
||||
if (cameraPosition.distanceTo(itemPosition) < 50) {
|
||||
await new Promise<void>(async (resolve) => {
|
||||
|
||||
// Check Three.js Cache
|
||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${item.modelname}`);
|
||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||
if (indexedDBModel) {
|
||||
// console.log(`[IndexedDB] Fetching ${item.modelname}`);
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
// console.log(`[Backend] Fetching ${item.modelname}`);
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`;
|
||||
loader.load(modelUrl, async (gltf) => {
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(item.modelfileID!, modelBlob);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, setSimulationStates);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[Backend] Error loading ${item.modelname}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// console.log(`Item ${item.modelname} is not near`);
|
||||
setFloorItems((prevItems) => [
|
||||
...(prevItems || []),
|
||||
{
|
||||
modeluuid: item.modeluuid,
|
||||
modelname: item.modelname,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
modelfileID: item.modelfileID,
|
||||
isLocked: item.isLocked,
|
||||
isVisible: item.isVisible,
|
||||
},
|
||||
]);
|
||||
|
||||
if (item.eventData) {
|
||||
processEventData(item, setSimulationStates);
|
||||
}
|
||||
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
|
||||
}
|
||||
}
|
||||
|
||||
// Dispose loader after all models
|
||||
dracoLoader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function processLoadedModel(
|
||||
gltf: any,
|
||||
item: SimulationTypes.EventData,
|
||||
itemsGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
setSimulationStates: (paths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => void
|
||||
) {
|
||||
const model = gltf;
|
||||
model.uuid = item.modeluuid;
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||
model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid };
|
||||
model.position.set(...item.position);
|
||||
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
||||
|
||||
model.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
// Clone the material to ensure changes are independent
|
||||
// child.material = child.material.clone();
|
||||
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
itemsGroup?.current?.add(model);
|
||||
|
||||
setFloorItems((prevItems) => [
|
||||
...(prevItems || []),
|
||||
{
|
||||
modeluuid: item.modeluuid,
|
||||
modelname: item.modelname,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
modelfileID: item.modelfileID,
|
||||
isLocked: item.isLocked,
|
||||
isVisible: item.isVisible,
|
||||
},
|
||||
]);
|
||||
|
||||
if (item.eventData) {
|
||||
processEventData(item, setSimulationStates);
|
||||
}
|
||||
|
||||
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
|
||||
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
|
||||
}
|
||||
|
||||
function processEventData(item: SimulationTypes.EventData, setSimulationStates: any) {
|
||||
|
||||
if (item.eventData?.type === 'Conveyor') {
|
||||
|
||||
const data: any = item.eventData;
|
||||
data.modeluuid = item.modeluuid;
|
||||
data.modelName = item.modelname;
|
||||
data.position = item.position;
|
||||
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
|
||||
|
||||
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
|
||||
...(prevEvents || []),
|
||||
data as SimulationTypes.ConveyorEventsSchema
|
||||
]);
|
||||
|
||||
} else if (item.eventData?.type === 'Vehicle') {
|
||||
|
||||
const data: any = item.eventData;
|
||||
data.modeluuid = item.modeluuid;
|
||||
data.modelName = item.modelname;
|
||||
data.position = item.position;
|
||||
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
|
||||
|
||||
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
|
||||
...(prevEvents || []),
|
||||
data as SimulationTypes.VehicleEventsSchema
|
||||
]);
|
||||
|
||||
} else if (item.eventData?.type === 'StaticMachine') {
|
||||
|
||||
const data: any = item.eventData;
|
||||
data.modeluuid = item.modeluuid;
|
||||
data.modelName = item.modelname;
|
||||
data.position = item.position;
|
||||
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
|
||||
|
||||
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
|
||||
...(prevEvents || []),
|
||||
data as SimulationTypes.StaticMachineEventsSchema
|
||||
]);
|
||||
|
||||
} else if (item.eventData?.type === 'ArmBot') {
|
||||
|
||||
const data: any = item.eventData;
|
||||
data.modeluuid = item.modeluuid;
|
||||
data.modelName = item.modelname;
|
||||
data.position = item.position;
|
||||
data.rotation = [item.rotation.x, item.rotation.y, item.rotation.z];
|
||||
|
||||
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
|
||||
...(prevEvents || []),
|
||||
data as SimulationTypes.ArmBotEventsSchema
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function checkLoadingCompletion(
|
||||
modelsLoaded: number,
|
||||
modelsToLoad: number,
|
||||
dracoLoader: DRACOLoader,
|
||||
resolve: () => void
|
||||
) {
|
||||
if (modelsLoaded === modelsToLoad) {
|
||||
toast.success("Models Loaded!");
|
||||
dracoLoader.dispose();
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import gsap from 'gsap';
|
||||
import * as THREE from 'three';
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import { toast } from 'react-toastify';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
|
||||
async function loadInitialFloorItems(
|
||||
itemsGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
): Promise<void> {
|
||||
if (!itemsGroup.current) return;
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
const email = localStorage.getItem('email');
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
const items = await getFloorAssets(organization);
|
||||
localStorage.setItem("FloorItems", JSON.stringify(items));
|
||||
await initializeDB();
|
||||
|
||||
if (items.message === "floorItems not found") return;
|
||||
|
||||
if (items) {
|
||||
const storedFloorItems: Types.FloorItems = items;
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
|
||||
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
let modelsLoaded = 0;
|
||||
const modelsToLoad = storedFloorItems.length;
|
||||
|
||||
const camData = await getCamera(organization, localStorage.getItem('userId')!);
|
||||
let storedPosition;
|
||||
if (camData && camData.position) {
|
||||
storedPosition = camData?.position;
|
||||
} else {
|
||||
storedPosition = new THREE.Vector3(0, 40, 30);
|
||||
}
|
||||
if (!storedPosition) return;
|
||||
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
|
||||
|
||||
storedFloorItems.sort((a, b) => {
|
||||
const aPosition = new THREE.Vector3(a.position[0], a.position[1], a.position[2]);
|
||||
const bPosition = new THREE.Vector3(b.position[0], b.position[1], b.position[2]);
|
||||
return cameraPosition.distanceTo(aPosition) - cameraPosition.distanceTo(bPosition);
|
||||
});
|
||||
|
||||
for (const item of storedFloorItems) {
|
||||
if (!item.modelfileID) return;
|
||||
const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]);
|
||||
let storedPosition;
|
||||
if (localStorage.getItem("cameraPosition")) {
|
||||
storedPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
|
||||
} else {
|
||||
storedPosition = new THREE.Vector3(0, 40, 30);
|
||||
}
|
||||
|
||||
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
|
||||
|
||||
if (cameraPosition.distanceTo(itemPosition) < 50) {
|
||||
await new Promise<void>(async (resolve) => {
|
||||
|
||||
// Check Three.js Cache
|
||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${item.modelname}`);
|
||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||
if (indexedDBModel) {
|
||||
// console.log(`[IndexedDB] Fetching ${item.modelname}`);
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
// console.log(`[Backend] Fetching ${item.modelname}`);
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`;
|
||||
loader.load(modelUrl, async (gltf) => {
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(item.modelfileID!, modelBlob);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[Backend] Error loading ${item.modelname}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// console.log(`Item ${item.modelname} is not near`);
|
||||
setFloorItems((prevItems) => [
|
||||
...(prevItems || []),
|
||||
{
|
||||
modeluuid: item.modeluuid,
|
||||
modelname: item.modelname,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
modelfileID: item.modelfileID,
|
||||
isLocked: item.isLocked,
|
||||
isVisible: item.isVisible,
|
||||
},
|
||||
]);
|
||||
|
||||
modelsLoaded++;
|
||||
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
|
||||
}
|
||||
}
|
||||
|
||||
// Dispose loader after all models
|
||||
dracoLoader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function processLoadedModel(
|
||||
gltf: any,
|
||||
item: Types.FloorItemType,
|
||||
itemsGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
) {
|
||||
const model = gltf;
|
||||
model.uuid = item.modeluuid;
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||
model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid };
|
||||
model.position.set(...item.position);
|
||||
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
||||
|
||||
model.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
// Clone the material to ensure changes are independent
|
||||
// child.material = child.material.clone();
|
||||
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
itemsGroup?.current?.add(model);
|
||||
|
||||
setFloorItems((prevItems) => [
|
||||
...(prevItems || []),
|
||||
{
|
||||
modeluuid: item.modeluuid,
|
||||
modelname: item.modelname,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
modelfileID: item.modelfileID,
|
||||
isLocked: item.isLocked,
|
||||
isVisible: item.isVisible,
|
||||
},
|
||||
]);
|
||||
|
||||
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
|
||||
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
|
||||
}
|
||||
|
||||
function checkLoadingCompletion(
|
||||
modelsLoaded: number,
|
||||
modelsToLoad: number,
|
||||
dracoLoader: DRACOLoader,
|
||||
resolve: () => void
|
||||
) {
|
||||
if (modelsLoaded === modelsToLoad) {
|
||||
toast.success("Models Loaded!");
|
||||
dracoLoader.dispose();
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
|
||||
export default loadInitialFloorItems;
|
||||
@@ -1,30 +1,30 @@
|
||||
import addLineToScene from '../../builder/geomentries/lines/addLineToScene';
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
function loadInitialLine(
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
lines: Types.RefLines
|
||||
): void {
|
||||
|
||||
if (!floorPlanGroupLine.current) return
|
||||
|
||||
////////// Load the Lines initially if there are any //////////
|
||||
|
||||
floorPlanGroupLine.current.children = [];
|
||||
lines.current.forEach((line) => {
|
||||
let colour;
|
||||
if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.wallName) {
|
||||
colour = CONSTANTS.lineConfig.wallColor;
|
||||
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName) {
|
||||
colour = CONSTANTS.lineConfig.floorColor;
|
||||
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName) {
|
||||
colour = CONSTANTS.lineConfig.aisleColor;
|
||||
}
|
||||
if (colour) {
|
||||
addLineToScene(line[0][0], line[1][0], colour, line, floorPlanGroupLine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default loadInitialLine;
|
||||
import addLineToScene from '../../builder/geomentries/lines/addLineToScene';
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
function loadInitialLine(
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
lines: Types.RefLines
|
||||
): void {
|
||||
|
||||
if (!floorPlanGroupLine.current) return
|
||||
|
||||
////////// Load the Lines initially if there are any //////////
|
||||
|
||||
floorPlanGroupLine.current.children = [];
|
||||
lines.current.forEach((line) => {
|
||||
let colour;
|
||||
if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.wallName) {
|
||||
colour = CONSTANTS.lineConfig.wallColor;
|
||||
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName) {
|
||||
colour = CONSTANTS.lineConfig.floorColor;
|
||||
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName) {
|
||||
colour = CONSTANTS.lineConfig.aisleColor;
|
||||
}
|
||||
if (colour) {
|
||||
addLineToScene(line[0][0], line[1][0], colour, line, floorPlanGroupLine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default loadInitialLine;
|
||||
@@ -1,87 +1,87 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
////////// Load the Boxes initially if there are any //////////
|
||||
|
||||
function loadInitialPoint(
|
||||
lines: Types.RefLines,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
currentLayerPoint: Types.RefMeshArray,
|
||||
dragPointControls: Types.RefDragControl
|
||||
): void {
|
||||
|
||||
if (!floorPlanGroupPoint.current) return
|
||||
|
||||
floorPlanGroupPoint.current.children = [];
|
||||
currentLayerPoint.current = [];
|
||||
lines.current.forEach((line) => {
|
||||
const colour = getPointColor(line[0][3]);
|
||||
line.forEach((pointData) => {
|
||||
const [point, id] = pointData;
|
||||
|
||||
/////////// Check if a box with this id already exists //////////
|
||||
|
||||
const existingBox = floorPlanGroupPoint.current?.getObjectByProperty('uuid', id);
|
||||
if (existingBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
|
||||
const material = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
uColor: { value: new THREE.Color(colour) }, // Blue color for the border
|
||||
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
|
||||
},
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec2 vUv;
|
||||
uniform vec3 uColor;
|
||||
uniform vec3 uInnerColor;
|
||||
|
||||
void main() {
|
||||
// Define the size of the white square as a proportion of the face
|
||||
float borderThickness = 0.2; // Adjust this value for border thickness
|
||||
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
|
||||
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
|
||||
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
|
||||
} else {
|
||||
gl_FragColor = vec4(uColor, 1.0); // Blue border
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
const box = new THREE.Mesh(geometry, material);
|
||||
box.name = "point";
|
||||
box.uuid = id;
|
||||
box.userData = { type: line[0][3], color: colour };
|
||||
box.position.set(point.x, point.y, point.z);
|
||||
currentLayerPoint.current.push(box);
|
||||
|
||||
floorPlanGroupPoint.current?.add(box);
|
||||
});
|
||||
});
|
||||
|
||||
function getPointColor(lineType: string | undefined): string {
|
||||
switch (lineType) {
|
||||
case CONSTANTS.lineConfig.wallName: return CONSTANTS.pointConfig.wallOuterColor;
|
||||
case CONSTANTS.lineConfig.floorName: return CONSTANTS.pointConfig.floorOuterColor;
|
||||
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.pointConfig.aisleOuterColor;
|
||||
default: return CONSTANTS.pointConfig.defaultOuterColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (dragPointControls.current) {
|
||||
dragPointControls.current!.objects = currentLayerPoint.current;
|
||||
}
|
||||
}
|
||||
|
||||
export default loadInitialPoint;
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
////////// Load the Boxes initially if there are any //////////
|
||||
|
||||
function loadInitialPoint(
|
||||
lines: Types.RefLines,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
currentLayerPoint: Types.RefMeshArray,
|
||||
dragPointControls: Types.RefDragControl
|
||||
): void {
|
||||
|
||||
if (!floorPlanGroupPoint.current) return
|
||||
|
||||
floorPlanGroupPoint.current.children = [];
|
||||
currentLayerPoint.current = [];
|
||||
lines.current.forEach((line) => {
|
||||
const colour = getPointColor(line[0][3]);
|
||||
line.forEach((pointData) => {
|
||||
const [point, id] = pointData;
|
||||
|
||||
/////////// Check if a box with this id already exists //////////
|
||||
|
||||
const existingBox = floorPlanGroupPoint.current?.getObjectByProperty('uuid', id);
|
||||
if (existingBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
|
||||
const material = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
uColor: { value: new THREE.Color(colour) }, // Blue color for the border
|
||||
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
|
||||
},
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec2 vUv;
|
||||
uniform vec3 uColor;
|
||||
uniform vec3 uInnerColor;
|
||||
|
||||
void main() {
|
||||
// Define the size of the white square as a proportion of the face
|
||||
float borderThickness = 0.2; // Adjust this value for border thickness
|
||||
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
|
||||
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
|
||||
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
|
||||
} else {
|
||||
gl_FragColor = vec4(uColor, 1.0); // Blue border
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
const box = new THREE.Mesh(geometry, material);
|
||||
box.name = "point";
|
||||
box.uuid = id;
|
||||
box.userData = { type: line[0][3], color: colour };
|
||||
box.position.set(point.x, point.y, point.z);
|
||||
currentLayerPoint.current.push(box);
|
||||
|
||||
floorPlanGroupPoint.current?.add(box);
|
||||
});
|
||||
});
|
||||
|
||||
function getPointColor(lineType: string | undefined): string {
|
||||
switch (lineType) {
|
||||
case CONSTANTS.lineConfig.wallName: return CONSTANTS.pointConfig.wallOuterColor;
|
||||
case CONSTANTS.lineConfig.floorName: return CONSTANTS.pointConfig.floorOuterColor;
|
||||
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.pointConfig.aisleOuterColor;
|
||||
default: return CONSTANTS.pointConfig.defaultOuterColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (dragPointControls.current) {
|
||||
dragPointControls.current!.objects = currentLayerPoint.current;
|
||||
}
|
||||
}
|
||||
|
||||
export default loadInitialPoint;
|
||||
@@ -1,54 +1,54 @@
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import { getWallItems } from '../../../services/factoryBuilder/assest/wallAsset/getWallItemsApi';
|
||||
|
||||
////////// Load the Wall Items's intially of there is any //////////
|
||||
|
||||
async function loadInitialWallItems(
|
||||
setWallItems: Types.setWallItemSetState,
|
||||
AssetConfigurations: Types.AssetConfigurations
|
||||
): Promise<void> {
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
const items = await getWallItems(organization);
|
||||
|
||||
localStorage.setItem("WallItems", JSON.stringify(items));
|
||||
if (items.length > 0) {
|
||||
const storedWallItems: Types.wallItems = items;
|
||||
|
||||
const loadedWallItems = await Promise.all(storedWallItems.map(async (item) => {
|
||||
const loader = new GLTFLoader();
|
||||
return new Promise<Types.WallItem>((resolve) => {
|
||||
loader.load(AssetConfigurations[item.modelname!].modelUrl, (gltf) => {
|
||||
const model = gltf.scene;
|
||||
model.uuid = item.modeluuid!;
|
||||
|
||||
model.children[0].children.forEach((child: any) => {
|
||||
if (child.name !== "CSG_REF") {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
resolve({
|
||||
type: item.type,
|
||||
model: model,
|
||||
modelname: item.modelname,
|
||||
scale: item.scale,
|
||||
csgscale: item.csgscale,
|
||||
csgposition: item.csgposition,
|
||||
position: item.position,
|
||||
quaternion: item.quaternion,
|
||||
});
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
setWallItems(loadedWallItems);
|
||||
}
|
||||
}
|
||||
|
||||
export default loadInitialWallItems;
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import { getWallItems } from '../../../services/factoryBuilder/assest/wallAsset/getWallItemsApi';
|
||||
|
||||
////////// Load the Wall Items's intially of there is any //////////
|
||||
|
||||
async function loadInitialWallItems(
|
||||
setWallItems: Types.setWallItemSetState,
|
||||
AssetConfigurations: Types.AssetConfigurations
|
||||
): Promise<void> {
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
const items = await getWallItems(organization);
|
||||
|
||||
localStorage.setItem("WallItems", JSON.stringify(items));
|
||||
if (items.length > 0) {
|
||||
const storedWallItems: Types.wallItems = items;
|
||||
|
||||
const loadedWallItems = await Promise.all(storedWallItems.map(async (item) => {
|
||||
const loader = new GLTFLoader();
|
||||
return new Promise<Types.WallItem>((resolve) => {
|
||||
loader.load(AssetConfigurations[item.modelname!].modelUrl, (gltf) => {
|
||||
const model = gltf.scene;
|
||||
model.uuid = item.modeluuid!;
|
||||
|
||||
model.children[0].children.forEach((child: any) => {
|
||||
if (child.name !== "CSG_REF") {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
resolve({
|
||||
type: item.type,
|
||||
model: model,
|
||||
modelname: item.modelname,
|
||||
scale: item.scale,
|
||||
csgscale: item.csgscale,
|
||||
csgposition: item.csgposition,
|
||||
position: item.position,
|
||||
quaternion: item.quaternion,
|
||||
});
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
setWallItems(loadedWallItems);
|
||||
}
|
||||
}
|
||||
|
||||
export default loadInitialWallItems;
|
||||
@@ -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}>
|
||||
<sphereGeometry args={[0.3, 15, 15]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Agv;
|
||||
@@ -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">
|
||||
{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;
|
||||
}
|
||||
355
app/src/modules/builder/builder.tsx
Normal file
355
app/src/modules/builder/builder.tsx
Normal file
@@ -0,0 +1,355 @@
|
||||
////////// Three and React Three Fiber Imports //////////
|
||||
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
|
||||
////////// Component Imports //////////
|
||||
|
||||
import DistanceText from "./geomentries/lines/distanceText/distanceText";
|
||||
import ReferenceDistanceText from "./geomentries/lines/distanceText/referenceDistanceText";
|
||||
|
||||
////////// Assests Imports //////////
|
||||
|
||||
import arch from "../../assets/gltf-glb/arch.glb";
|
||||
import door from "../../assets/gltf-glb/door.glb";
|
||||
import Window from "../../assets/gltf-glb/window.glb";
|
||||
|
||||
////////// Zustand State Imports //////////
|
||||
|
||||
import {
|
||||
useToggleView,
|
||||
useDeletePointOrLine,
|
||||
useMovePoint,
|
||||
useActiveLayer,
|
||||
useSocketStore,
|
||||
useWallVisibility,
|
||||
useRoofVisibility,
|
||||
useShadows,
|
||||
useUpdateScene,
|
||||
useWalls,
|
||||
useToolMode,
|
||||
useRefTextUpdate,
|
||||
useRenderDistance,
|
||||
useLimitDistance,
|
||||
} from "../../store/store";
|
||||
|
||||
////////// 3D Function Imports //////////
|
||||
|
||||
import loadWalls from "./geomentries/walls/loadWalls";
|
||||
|
||||
import * as Types from "../../types/world/worldTypes";
|
||||
|
||||
import SocketResponses from "../collaboration/socket/socketResponses.dev";
|
||||
import FloorItemsGroup from "./groups/floorItemsGroup";
|
||||
import FloorPlanGroup from "./groups/floorPlanGroup";
|
||||
import FloorGroup from "./groups/floorGroup";
|
||||
import FloorGroupAilse from "./groups/floorGroupAisle";
|
||||
import Draw from "./functions/draw";
|
||||
import WallsAndWallItems from "./groups/wallsAndWallItems";
|
||||
import Ground from "../scene/environment/ground";
|
||||
// import ZoneGroup from "../groups/zoneGroup1";
|
||||
import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment";
|
||||
import Layer2DVisibility from "./geomentries/layers/layer2DVisibility";
|
||||
import DrieHtmlTemp from "..//visualization/mqttTemp/drieHtmlTemp";
|
||||
import ZoneGroup from "./groups/zoneGroup";
|
||||
import useModuleStore from "../../store/useModuleStore";
|
||||
import MeasurementTool from "../scene/tools/measurementTool";
|
||||
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
||||
|
||||
export default function Builder() {
|
||||
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
||||
const csg = useRef(); // Reference for CSG object, used for 3D modeling.
|
||||
const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
|
||||
const scene = useRef() as Types.RefScene; // Reference to the scene.
|
||||
const camera = useRef() as Types.RefCamera; // Reference to the camera object.
|
||||
const controls = useRef<any>(); // Reference to the controls object.
|
||||
const raycaster = useRef() as Types.RefRaycaster; // Reference for raycaster used for detecting objects being pointed at in the scene.
|
||||
const dragPointControls = useRef() as Types.RefDragControl; // Reference for drag point controls, an array for drag control.
|
||||
|
||||
// Assigning the scene and camera from the Three.js state to the references.
|
||||
|
||||
scene.current = state.scene;
|
||||
camera.current = state.camera;
|
||||
controls.current = state.controls;
|
||||
raycaster.current = state.raycaster;
|
||||
|
||||
const plane = useRef<THREE.Mesh>(null); // Reference for a plane object for raycaster reference.
|
||||
const grid = useRef() as any; // Reference for a grid object for raycaster reference.
|
||||
const snappedPoint = useRef() as Types.RefVector3; // Reference for storing a snapped point at the (end = isSnapped) and (start = ispreSnapped) of the line.
|
||||
const isSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (end).
|
||||
const anglesnappedPoint = useRef() as Types.RefVector3; // Reference for storing an angle-snapped point when the line is in 90 degree etc...
|
||||
const isAngleSnapped = useRef(false) as Types.RefBoolean; // Boolean to indicate if angle snapping is active.
|
||||
const isSnappedUUID = useRef() as Types.RefString; // UUID reference to identify the snapped point.
|
||||
const ispreSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (start).
|
||||
const tempLoader = useRef() as Types.RefMesh; // Reference for a temporary loader for the floor items.
|
||||
const isTempLoader = useRef() as Types.RefBoolean; // Reference to check if a temporary loader is active.
|
||||
const Tube = useRef() as Types.RefTubeGeometry; // Reference for tubes used for reference line creation and updation.
|
||||
const line = useRef([]) as Types.RefLine; // Reference for line which stores the current line that is being drawn.
|
||||
const lines = useRef([]) as Types.RefLines; // Reference for lines which stores all the lines that are ever drawn.
|
||||
const onlyFloorline = useRef<Types.OnlyFloorLine>([]); // Reference for floor lines which does not have walls or roof and have only floor used to store the current line that is being drawn.
|
||||
const onlyFloorlines = useRef<Types.OnlyFloorLines>([]); // Reference for all the floor lines that are ever drawn.
|
||||
const ReferenceLineMesh = useRef() as Types.RefMesh; // Reference for storing the mesh of the reference line for moving it during draw.
|
||||
const LineCreated = useRef(false) as Types.RefBoolean; // Boolean to track whether the reference line is created or not.
|
||||
const referencePole = useRef() as Types.RefMesh; // Reference for a pole that is used as the reference for the user to show where it is placed.
|
||||
const itemsGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the floor items (Gltf).
|
||||
const floorGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the roofs and the floors.
|
||||
const AttachedObject = useRef() as Types.RefMesh; // Reference for an object that is attached using dbl click for transform controls rotation.
|
||||
const floorPlanGroup = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines group and the points group.
|
||||
const floorPlanGroupLine = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines that are drawn.
|
||||
const floorPlanGroupPoint = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the points that are created.
|
||||
const floorGroupAisle = useRef() as Types.RefGroup;
|
||||
const zoneGroup = useRef() as Types.RefGroup;
|
||||
const currentLayerPoint = useRef([]) as Types.RefMeshArray; // Reference for points that re in the current layer used to update the points in drag controls.
|
||||
const hoveredDeletablePoint = useRef() as Types.RefMesh; // Reference for the currently hovered point that can be deleted.
|
||||
const hoveredDeletableLine = useRef() as Types.RefMesh; // Reference for the currently hovered line that can be deleted.
|
||||
const hoveredDeletableFloorItem = useRef() as Types.RefMesh; // Reference for the currently hovered floor item that can be deleted.
|
||||
const hoveredDeletableWallItem = useRef() as Types.RefMesh; // Reference for the currently hovered wall item that can be deleted.
|
||||
const hoveredDeletablePillar = useRef() as Types.RefMesh; // Reference for the currently hovered pillar that can be deleted.
|
||||
const currentWallItem = useRef() as Types.RefMesh; // Reference for the currently selected wall item that can be scaled, dragged etc...
|
||||
|
||||
const cursorPosition = new THREE.Vector3(); // 3D vector for storing the cursor position.
|
||||
|
||||
const [selectedItemsIndex, setSelectedItemsIndex] = useState<Types.Number | null>(null); // State for tracking the index of the selected item.
|
||||
const { activeLayer, setActiveLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
|
||||
const { toggleView, setToggleView } = useToggleView(); // State for toggling between 2D and 3D.
|
||||
const { toolMode, setToolMode } = useToolMode();
|
||||
const { movePoint, setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
|
||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { socket } = useSocketStore();
|
||||
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
|
||||
const { wallVisibility, setWallVisibility } = useWallVisibility();
|
||||
const { shadows, setShadows } = useShadows();
|
||||
const { renderDistance, setRenderDistance } = useRenderDistance();
|
||||
const { limitDistance, setLimitDistance } = useLimitDistance();
|
||||
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||
const { walls, setWalls } = useWalls();
|
||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
// const loader = new GLTFLoader();
|
||||
// const dracoLoader = new DRACOLoader();
|
||||
|
||||
// dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
// loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
////////// Assest Configuration Values //////////
|
||||
|
||||
const AssetConfigurations: Types.AssetConfigurations = {
|
||||
arch: {
|
||||
modelUrl: arch,
|
||||
scale: [0.75, 0.75, 0.75],
|
||||
csgscale: [2, 4, 0.5],
|
||||
csgposition: [0, 2, 0],
|
||||
positionY: () => 0,
|
||||
type: "Fixed-Move",
|
||||
},
|
||||
door: {
|
||||
modelUrl: door,
|
||||
scale: [0.75, 0.75, 0.75],
|
||||
csgscale: [2, 4, 0.5],
|
||||
csgposition: [0, 2, 0],
|
||||
positionY: () => 0,
|
||||
type: "Fixed-Move",
|
||||
},
|
||||
window: {
|
||||
modelUrl: Window,
|
||||
scale: [0.75, 0.75, 0.75],
|
||||
csgscale: [5, 3, 0.5],
|
||||
csgposition: [0, 1.5, 0],
|
||||
positionY: (intersectionPoint) => intersectionPoint.point.y,
|
||||
type: "Free-Move",
|
||||
},
|
||||
};
|
||||
|
||||
////////// All Toggle's //////////
|
||||
|
||||
useEffect(() => {
|
||||
setRefTextUpdate((prevUpdate: number) => prevUpdate - 1);
|
||||
if (dragPointControls.current) {
|
||||
dragPointControls.current.enabled = false;
|
||||
}
|
||||
if (toggleView) {
|
||||
Layer2DVisibility(
|
||||
activeLayer,
|
||||
floorPlanGroup,
|
||||
floorPlanGroupLine,
|
||||
floorPlanGroupPoint,
|
||||
currentLayerPoint,
|
||||
dragPointControls
|
||||
);
|
||||
} else {
|
||||
setToolMode(null);
|
||||
setDeletePointOrLine(false);
|
||||
setMovePoint(false);
|
||||
loadWalls(lines, setWalls);
|
||||
setUpdateScene(true);
|
||||
line.current = [];
|
||||
}
|
||||
}, [toggleView]);
|
||||
|
||||
useEffect(() => {
|
||||
THREE.Cache.clear();
|
||||
THREE.Cache.enabled = true;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
async function fetchVisibility() {
|
||||
const visibility = await findEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!
|
||||
);
|
||||
if (visibility) {
|
||||
setRoofVisibility(visibility.roofVisibility);
|
||||
setWallVisibility(visibility.wallVisibility);
|
||||
setShadows(visibility.shadowVisibility);
|
||||
setRenderDistance(visibility.renderDistance);
|
||||
setLimitDistance(visibility.limitDistance);
|
||||
}
|
||||
}
|
||||
fetchVisibility();
|
||||
}, []);
|
||||
|
||||
////////// UseFrame is Here //////////
|
||||
|
||||
useFrame(() => {
|
||||
if (toolMode) {
|
||||
Draw(
|
||||
state,
|
||||
plane,
|
||||
cursorPosition,
|
||||
floorPlanGroupPoint,
|
||||
floorPlanGroupLine,
|
||||
snappedPoint,
|
||||
isSnapped,
|
||||
isSnappedUUID,
|
||||
line,
|
||||
lines,
|
||||
ispreSnapped,
|
||||
floorPlanGroup,
|
||||
ReferenceLineMesh,
|
||||
LineCreated,
|
||||
setRefTextUpdate,
|
||||
Tube,
|
||||
anglesnappedPoint,
|
||||
isAngleSnapped,
|
||||
toolMode
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
////////// Return //////////
|
||||
|
||||
return (
|
||||
<>
|
||||
<Ground grid={grid} plane={plane} />
|
||||
|
||||
<DistanceText key={toggleView} />
|
||||
|
||||
<ReferenceDistanceText
|
||||
key={refTextupdate}
|
||||
line={ReferenceLineMesh.current}
|
||||
/>
|
||||
|
||||
<SocketResponses
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
lines={lines}
|
||||
floorGroup={floorGroup}
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
scene={scene}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
AssetConfigurations={AssetConfigurations}
|
||||
itemsGroup={itemsGroup}
|
||||
isTempLoader={isTempLoader}
|
||||
tempLoader={tempLoader}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
zoneGroup={zoneGroup}
|
||||
dragPointControls={dragPointControls}
|
||||
/>
|
||||
|
||||
<WallsAndWallItems
|
||||
CSGGroup={CSGGroup}
|
||||
AssetConfigurations={AssetConfigurations}
|
||||
setSelectedItemsIndex={setSelectedItemsIndex}
|
||||
selectedItemsIndex={selectedItemsIndex}
|
||||
currentWallItem={currentWallItem}
|
||||
csg={csg}
|
||||
lines={lines}
|
||||
hoveredDeletableWallItem={hoveredDeletableWallItem}
|
||||
/>
|
||||
|
||||
<FloorItemsGroup
|
||||
itemsGroup={itemsGroup}
|
||||
hoveredDeletableFloorItem={hoveredDeletableFloorItem}
|
||||
AttachedObject={AttachedObject}
|
||||
floorGroup={floorGroup}
|
||||
tempLoader={tempLoader}
|
||||
isTempLoader={isTempLoader}
|
||||
plane={plane}
|
||||
/>
|
||||
|
||||
<FloorGroup
|
||||
floorGroup={floorGroup}
|
||||
lines={lines}
|
||||
referencePole={referencePole}
|
||||
hoveredDeletablePillar={hoveredDeletablePillar}
|
||||
/>
|
||||
|
||||
<FloorPlanGroup
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorGroup={floorGroup}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
hoveredDeletablePoint={hoveredDeletablePoint}
|
||||
hoveredDeletableLine={hoveredDeletableLine}
|
||||
plane={plane}
|
||||
line={line}
|
||||
lines={lines}
|
||||
onlyFloorline={onlyFloorline}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
|
||||
<ZoneGroup />
|
||||
|
||||
<FloorGroupAilse
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
plane={plane}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
line={line}
|
||||
lines={lines}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
|
||||
<MeasurementTool />
|
||||
|
||||
<NavMesh lines={lines} />
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -5,13 +5,12 @@ import { toast } from 'react-toastify';
|
||||
import TempLoader from './tempLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import * as SimulationTypes from "../../../../types/simulationTypes";
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
||||
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import { getAssetEventType } from '../../../../services/simulation/getAssetEventType';
|
||||
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||
import PointsCalculator from '../../../simulation/events/points/pointsCalculator';
|
||||
|
||||
async function addAssetModel(
|
||||
raycaster: THREE.Raycaster,
|
||||
@@ -25,7 +24,7 @@ async function addAssetModel(
|
||||
socket: Socket<any>,
|
||||
selectedItem: any,
|
||||
setSelectedItem: any,
|
||||
setSimulationStates: any,
|
||||
addEvent: (event: EventsSchema) => void,
|
||||
plane: Types.RefMesh,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -66,7 +65,7 @@ async function addAssetModel(
|
||||
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${selectedItem.name}`);
|
||||
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
|
||||
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, addEvent, socket);
|
||||
return;
|
||||
} else {
|
||||
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||
@@ -79,7 +78,7 @@ async function addAssetModel(
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
|
||||
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, addEvent, socket);
|
||||
},
|
||||
() => {
|
||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||
@@ -91,7 +90,7 @@ async function addAssetModel(
|
||||
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||
await storeGLTF(selectedItem.id, modelBlob);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, setSimulationStates, socket);
|
||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, addEvent, socket);
|
||||
},
|
||||
() => {
|
||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||
@@ -114,7 +113,7 @@ async function handleModelLoad(
|
||||
tempLoader: Types.RefMesh,
|
||||
isTempLoader: Types.RefBoolean,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
setSimulationStates: any,
|
||||
addEvent: (event: EventsSchema) => void,
|
||||
socket: Socket<any>
|
||||
) {
|
||||
const model = gltf.scene.clone();
|
||||
@@ -137,7 +136,7 @@ async function handleModelLoad(
|
||||
tempLoader.current = undefined;
|
||||
}
|
||||
|
||||
const newFloorItem: SimulationTypes.EventData = {
|
||||
const newFloorItem: Types.FloorItemType = {
|
||||
modeluuid: model.uuid,
|
||||
modelname: selectedItem.name,
|
||||
modelfileID: selectedItem.id,
|
||||
@@ -150,316 +149,159 @@ async function handleModelLoad(
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email ? email.split("@")[1].split(".")[0] : "";
|
||||
|
||||
getAssetEventType(selectedItem.id, organization).then(async (res) => {
|
||||
// API
|
||||
|
||||
if (res.type === "Conveyor") {
|
||||
const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID());
|
||||
// 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,
|
||||
// );
|
||||
|
||||
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: []
|
||||
// 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,
|
||||
socketId: socket.id
|
||||
};
|
||||
|
||||
if (selectedItem.type) {
|
||||
const data = PointsCalculator(
|
||||
selectedItem.type,
|
||||
gltf.scene.clone(),
|
||||
new THREE.Vector3(...model.rotation)
|
||||
);
|
||||
|
||||
if (!data || !data.points) return;
|
||||
|
||||
if (selectedItem.type === "Conveyor") {
|
||||
const ConveyorEvent: ConveyorEventSchema = {
|
||||
modelUuid: newFloorItem.modeluuid,
|
||||
modelName: newFloorItem.modelname,
|
||||
position: newFloorItem.position,
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: 'transfer',
|
||||
speed: 1,
|
||||
points: data.points.map((point: THREE.Vector3, index: number) => ({
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [point.x, point.y, point.z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: `Action ${index}`,
|
||||
actionType: 'default',
|
||||
material: 'inherit',
|
||||
delay: 0,
|
||||
spawnInterval: 5,
|
||||
spawnCount: 1,
|
||||
triggers: []
|
||||
}
|
||||
})),
|
||||
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,
|
||||
addEvent(ConveyorEvent);
|
||||
} else if (selectedItem.type === "Vehicle") {
|
||||
const vehicleEvent: VehicleEventSchema = {
|
||||
modelUuid: newFloorItem.modeluuid,
|
||||
modelName: newFloorItem.modelname,
|
||||
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: [] },
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "vehicle",
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Vehicle Action",
|
||||
actionType: "travel",
|
||||
material: null,
|
||||
unLoadDuration: 5,
|
||||
loadCapacity: 10,
|
||||
pickUpPoint: null,
|
||||
unLoadPoint: null,
|
||||
triggers: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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: [] },
|
||||
addEvent(vehicleEvent);
|
||||
} else if (selectedItem.type === "ArmBot") {
|
||||
const roboticArmEvent: RoboticArmEventSchema = {
|
||||
modelUuid: newFloorItem.modeluuid,
|
||||
modelName: newFloorItem.modelname,
|
||||
position: newFloorItem.position,
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "roboticArm",
|
||||
speed: 1,
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
rotation: [0, 0, 0],
|
||||
actions: [
|
||||
{
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Pick and Place",
|
||||
actionType: "pickAndPlace",
|
||||
process: {
|
||||
startPoint: [0, 0, 0],
|
||||
endPoint: [0, 0, 0]
|
||||
},
|
||||
triggers: []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// await setFloorItemApi(
|
||||
// organization,
|
||||
// newFloorItem.modeluuid,
|
||||
// newFloorItem.modelname,
|
||||
// newFloorItem.modelfileID,
|
||||
// newFloorItem.position,
|
||||
// { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
|
||||
// false,
|
||||
// true,
|
||||
// { type: backendEventData.type, points: backendEventData.points }
|
||||
// );
|
||||
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modeluuid: newFloorItem.modeluuid,
|
||||
modelname: newFloorItem.modelname,
|
||||
modelfileID: newFloorItem.modelfileID,
|
||||
position: newFloorItem.position,
|
||||
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: { type: backendEventData.type, points: backendEventData.points },
|
||||
socketId: socket.id
|
||||
};
|
||||
|
||||
const eventData: any = backendEventData;
|
||||
eventData.modeluuid = newFloorItem.modeluuid;
|
||||
eventData.modelName = newFloorItem.modelname;
|
||||
eventData.position = newFloorItem.position;
|
||||
eventData.rotation = [model.rotation.x, model.rotation.y, model.rotation.z];
|
||||
|
||||
setFloorItems((prevItems) => {
|
||||
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||
return updatedItems;
|
||||
});
|
||||
|
||||
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => [
|
||||
...(prevEvents || []),
|
||||
eventData as SimulationTypes.ArmBotEventsSchema
|
||||
]);
|
||||
|
||||
socket.emit("v2:model-asset:add", data);
|
||||
|
||||
} else {
|
||||
|
||||
// API
|
||||
|
||||
// await setFloorItemApi(
|
||||
// organization,
|
||||
// newFloorItem.modeluuid,
|
||||
// newFloorItem.modelname,
|
||||
// newFloorItem.modelfileID,
|
||||
// newFloorItem.position,
|
||||
// { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
|
||||
// false,
|
||||
// true,
|
||||
// );
|
||||
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modeluuid: newFloorItem.modeluuid,
|
||||
modelname: newFloorItem.modelname,
|
||||
modelfileID: newFloorItem.modelfileID,
|
||||
addEvent(roboticArmEvent);
|
||||
} else if (selectedItem.type === "Machine") {
|
||||
const machineEvent: MachineEventSchema = {
|
||||
modelUuid: newFloorItem.modeluuid,
|
||||
modelName: newFloorItem.modelname,
|
||||
position: newFloorItem.position,
|
||||
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: socket.id
|
||||
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
|
||||
state: "idle",
|
||||
type: "machine",
|
||||
point: {
|
||||
uuid: THREE.MathUtils.generateUUID(),
|
||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||
rotation: [0, 0, 0],
|
||||
action: {
|
||||
actionUuid: THREE.MathUtils.generateUUID(),
|
||||
actionName: "Process Action",
|
||||
actionType: "process",
|
||||
processTime: 10,
|
||||
swapMaterial: "material-id",
|
||||
triggers: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
setFloorItems((prevItems) => {
|
||||
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||
return updatedItems;
|
||||
});
|
||||
|
||||
socket.emit("v2:model-asset:add", data);
|
||||
addEvent(machineEvent);
|
||||
}
|
||||
}
|
||||
|
||||
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!"); } });
|
||||
setFloorItems((prevItems) => {
|
||||
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||
return updatedItems;
|
||||
});
|
||||
|
||||
socket.emit("v2:model-asset:add", data);
|
||||
|
||||
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
|
||||
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
|
||||
}
|
||||
|
||||
export default addAssetModel;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { toast } from 'react-toastify';
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import * as SimulationTypes from "../../../../types/simulationTypes";
|
||||
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { getFloorAssets } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
@@ -11,7 +10,6 @@ async function DeleteFloorItems(
|
||||
itemsGroup: Types.RefGroup,
|
||||
hoveredDeletableFloorItem: Types.RefMesh,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
setSimulationStates: any,
|
||||
socket: Socket<any>
|
||||
): Promise<void> {
|
||||
|
||||
@@ -77,11 +75,6 @@ async function DeleteFloorItems(
|
||||
}
|
||||
setFloorItems(updatedItems);
|
||||
|
||||
setSimulationStates((prevEvents: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
|
||||
const updatedEvents = (prevEvents || []).filter(event => event.modeluuid !== removedItem.modeluuid);
|
||||
return updatedEvents;
|
||||
});
|
||||
|
||||
toast.success("Model Removed!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useActiveTool, useAsset3dWidget, useCamMode, useDeletableFloorItem, useDeleteTool, useFloorItems, useLoadingProgress, useRenderDistance, useSelectedFloorItem, useSelectedItem, useSimulationStates, useSocketStore, useToggleView, useTransformMode, } from "../../../store/store";
|
||||
import { useActiveTool, useAsset3dWidget, useCamMode, useDeletableFloorItem, useDeleteTool, useFloorItems, useLoadingProgress, useRenderDistance, useSelectedFloorItem, useSelectedItem, useSocketStore, useToggleView, useTransformMode, } from "../../../store/store";
|
||||
import assetVisibility from "../geomentries/assets/assetVisibility";
|
||||
import { useEffect } from "react";
|
||||
import * as THREE from "three";
|
||||
@@ -11,11 +11,13 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import DeletableHoveredFloorItems from "../geomentries/assets/deletableHoveredFloorItems";
|
||||
import DeleteFloorItems from "../geomentries/assets/deleteFloorItems";
|
||||
import loadInitialFloorItems from "../../scene/IntialLoad/loadInitialFloorItems";
|
||||
import loadInitialFloorItems from "../IntialLoad/loadInitialFloorItems";
|
||||
import addAssetModel from "../geomentries/assets/addAssetModel";
|
||||
import { getFloorAssets } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
// import { retrieveGLTF } from "../../../utils/indexDB/idbUtils";
|
||||
import { useEventsStore } from "../../../store/simulation/useEventsStore";
|
||||
|
||||
const assetManagerWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/assetManagerWorker.js", import.meta.url));
|
||||
const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
|
||||
|
||||
@@ -32,12 +34,12 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { selectedItem, setSelectedItem } = useSelectedItem();
|
||||
const { simulationStates, setSimulationStates } = useSimulationStates();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
const { addEvent } = useEventsStore();
|
||||
|
||||
dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/");
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
@@ -73,7 +75,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
||||
gltfLoaderWorker.postMessage({ floorItems: data });
|
||||
} else {
|
||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates);
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
@@ -92,7 +94,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
||||
updateLoadingProgress(progress);
|
||||
|
||||
if (loadedAssets === totalAssets) {
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems, setSimulationStates);
|
||||
loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
@@ -192,9 +194,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
||||
if (drag) return;
|
||||
|
||||
if (deleteTool) {
|
||||
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, setSimulationStates, socket);
|
||||
|
||||
// Remove EventData if there are any in the asset.
|
||||
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, socket);
|
||||
}
|
||||
const Mode = transformMode;
|
||||
|
||||
@@ -278,7 +278,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
||||
if (!event.dataTransfer?.files[0]) return;
|
||||
|
||||
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
|
||||
addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, setSimulationStates, plane);
|
||||
addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, addEvent, plane);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -314,14 +314,14 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
|
||||
useFrame(() => {
|
||||
if (controls)
|
||||
// assetVisibility(itemsGroup, state.camera.position, renderDistance);
|
||||
if (deleteTool && activeModule === "builder") {
|
||||
DeletableHoveredFloorItems(state, itemsGroup, hoveredDeletableFloorItem, setDeletableFloorItem);
|
||||
} else if (!deleteTool) {
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
if (deleteTool && activeModule === "builder") {
|
||||
DeletableHoveredFloorItems(state, itemsGroup, hoveredDeletableFloorItem, setDeletableFloorItem);
|
||||
} else if (!deleteTool) {
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return <group ref={itemsGroup} name="itemsGroup"></group>;
|
||||
|
||||
@@ -9,15 +9,14 @@ import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
|
||||
import DeleteLayer from "../geomentries/layers/deleteLayer";
|
||||
import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
|
||||
import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
|
||||
import loadInitialPoint from "../../scene/IntialLoad/loadInitialPoint";
|
||||
import loadInitialLine from "../../scene/IntialLoad/loadInitialLine";
|
||||
import loadInitialPoint from "../IntialLoad/loadInitialPoint";
|
||||
import loadInitialLine from "../IntialLoad/loadInitialLine";
|
||||
import deletePoint from "../geomentries/points/deletePoint";
|
||||
import deleteLine from "../geomentries/lines/deleteLine";
|
||||
import drawWall from "../geomentries/lines/drawWall";
|
||||
import drawOnlyFloor from "../geomentries/floors/drawOnlyFloor";
|
||||
import addDragControl from "../eventDeclaration/dragControlDeclaration";
|
||||
|
||||
|
||||
const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, floorGroup, currentLayerPoint, dragPointControls, hoveredDeletablePoint, hoveredDeletableLine, plane, line, lines, onlyFloorline, onlyFloorlines, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
|
||||
const state = useThree();
|
||||
const { scene, camera, gl, raycaster, controls } = state;
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as THREE from "three";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
|
||||
import DeleteWallItems from "../geomentries/walls/deleteWallItems";
|
||||
import loadInitialWallItems from "../../scene/IntialLoad/loadInitialWallItems";
|
||||
import loadInitialWallItems from "../IntialLoad/loadInitialWallItems";
|
||||
import AddWallItems from "../geomentries/walls/addWallItems";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
|
||||
@@ -37,51 +37,6 @@ const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletable
|
||||
|
||||
////////// Update the Rotation value changes in the selected item //////////
|
||||
|
||||
useEffect(() => {
|
||||
if (objectScale.x && objectScale.y && objectScale.z) {
|
||||
let ScaledWallItems: Types.wallItems = [];
|
||||
wallItems.forEach((items: any) => {
|
||||
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
|
||||
items.scale = [objectScale.x, objectScale.y, objectScale.z];
|
||||
}
|
||||
ScaledWallItems.push(items);
|
||||
});
|
||||
setWallItems(ScaledWallItems);
|
||||
}
|
||||
}, [objectScale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (objectPosition.x && objectPosition.y && objectPosition.z) {
|
||||
let ScaledWallItems: Types.wallItems = [];
|
||||
wallItems.forEach((items: any) => {
|
||||
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
|
||||
items.position = [objectPosition.x, objectPosition.y, objectPosition.z];
|
||||
}
|
||||
ScaledWallItems.push(items);
|
||||
});
|
||||
setWallItems(ScaledWallItems);
|
||||
}
|
||||
}, [objectPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (objectRotation.x && objectRotation.y && objectRotation.z) {
|
||||
let ScaledWallItems: Types.wallItems = [];
|
||||
wallItems.forEach((items: any) => {
|
||||
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
|
||||
const radiansX = objectRotation.x * (Math.PI / 180);
|
||||
const radiansY = objectRotation.y * (Math.PI / 180);
|
||||
const radiansZ = objectRotation.z * (Math.PI / 180);
|
||||
const quaternion = new THREE.Quaternion().setFromEuler(
|
||||
new THREE.Euler(radiansX, radiansY, radiansZ)
|
||||
);
|
||||
items.quaternion = [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
|
||||
}
|
||||
ScaledWallItems.push(items);
|
||||
});
|
||||
setWallItems(ScaledWallItems);
|
||||
}
|
||||
}, [objectRotation]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = state.gl.domElement;
|
||||
function handlePointerMove(e: any) {
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import * as THREE from 'three';
|
||||
import * as Types from '../../../types/world/worldTypes';
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
import { useActiveLayer, useSocketStore, useDeleteTool, useDeletePointOrLine, useMovePoint, useToggleView, useUpdateScene, useNewLines, useToolMode } from "../../../store/store";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import arrayLineToObject from "../geomentries/lines/lineConvertions/arrayLineToObject";
|
||||
import addPointToScene from "../geomentries/points/addPointToScene";
|
||||
import addLineToScene from "../geomentries/lines/addLineToScene";
|
||||
import removeSoloPoint from "../geomentries/points/removeSoloPoint";
|
||||
import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
|
||||
import getClosestIntersection from "../geomentries/lines/getClosestIntersection";
|
||||
import loadZones from "../geomentries/zones/loadZones";
|
||||
|
||||
const ZoneGroup = ({ zoneGroup, plane, floorPlanGroupLine, floorPlanGroupPoint, line, lines, currentLayerPoint, dragPointControls, floorPlanGroup, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
|
||||
const { toggleView, setToggleView } = useToggleView();
|
||||
const { deleteTool, setDeleteTool } = useDeleteTool();
|
||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { toolMode, setToolMode } = useToolMode();
|
||||
const { movePoint, setMovePoint } = useMovePoint();
|
||||
const { socket } = useSocketStore();
|
||||
const { activeLayer } = useActiveLayer();
|
||||
const { gl, raycaster, camera, pointer } = useThree();
|
||||
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||
const { newLines, setNewLines } = useNewLines();
|
||||
|
||||
useEffect(() => {
|
||||
if (updateScene) {
|
||||
loadZones(lines, zoneGroup);
|
||||
setUpdateScene(false);
|
||||
}
|
||||
}, [updateScene])
|
||||
|
||||
useEffect(() => {
|
||||
if (toolMode === "Zone") {
|
||||
setDeletePointOrLine(false);
|
||||
setMovePoint(false);
|
||||
setDeleteTool(false);
|
||||
} else {
|
||||
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||
}
|
||||
}, [toolMode])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
let drag = false;
|
||||
let isLeftMouseDown = false;
|
||||
|
||||
const onMouseDown = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isLeftMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onContextMenu = (e: any) => {
|
||||
e.preventDefault();
|
||||
if (toolMode === "Zone") {
|
||||
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseClick = (evt: any) => {
|
||||
if (!plane.current || drag) return;
|
||||
const intersects = raycaster.intersectObject(plane.current, true);
|
||||
let intersectionPoint = intersects[0].point;
|
||||
const points = floorPlanGroupPoint.current?.children ?? [];
|
||||
const intersectsPoint = raycaster.intersectObjects(points, true).find(intersect => intersect.object.visible);
|
||||
let intersectsLines: any = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
|
||||
|
||||
if (intersectsLines.length > 0 && intersects && intersects.length > 0 && !intersectsPoint) {
|
||||
const lineType = intersectsLines[0].object.userData.linePoints[0][3];
|
||||
if (lineType === CONSTANTS.lineConfig.zoneName) {
|
||||
// console.log("intersected a zone line");
|
||||
|
||||
const ThroughPoint = (intersectsLines[0].object.geometry.parameters.path).getPoints(300);
|
||||
let intersection = getClosestIntersection(ThroughPoint, intersectionPoint);
|
||||
if (!intersection) return;
|
||||
const point = addPointToScene(intersection, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
|
||||
(line.current as Types.Line).push([new THREE.Vector3(intersection.x, 0.01, intersection.z), point.uuid, activeLayer, CONSTANTS.lineConfig.zoneName,]);
|
||||
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||
lines.current.push(line.current as Types.Line);
|
||||
|
||||
const data = arrayLineToObject(line.current as Types.Line);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const input = {
|
||||
organization: organization,
|
||||
layer: data.layer,
|
||||
line: data.line,
|
||||
type: data.type,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:create', input);
|
||||
|
||||
setNewLines([line.current]);
|
||||
|
||||
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
|
||||
let lastPoint = line.current[line.current.length - 1];
|
||||
line.current = [lastPoint];
|
||||
}
|
||||
}
|
||||
} else if (intersectsPoint && intersects && intersects.length > 0) {
|
||||
if (intersectsPoint.object.userData.type === CONSTANTS.lineConfig.zoneName) {
|
||||
// console.log("intersected a zone point");
|
||||
|
||||
intersectionPoint = intersectsPoint.object.position;
|
||||
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), intersectsPoint.object.uuid, activeLayer, CONSTANTS.lineConfig.zoneName]);
|
||||
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||
lines.current.push(line.current as Types.Line);
|
||||
|
||||
const data = arrayLineToObject(line.current as Types.Line);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const input = {
|
||||
organization: organization,
|
||||
layer: data.layer,
|
||||
line: data.line,
|
||||
type: data.type,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:create', input);
|
||||
|
||||
setNewLines([line.current]);
|
||||
|
||||
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
|
||||
let lastPoint = line.current[line.current.length - 1];
|
||||
line.current = [lastPoint];
|
||||
ispreSnapped.current = false;
|
||||
isSnapped.current = false;
|
||||
}
|
||||
}
|
||||
} else if (intersects && intersects.length > 0) {
|
||||
// console.log("intersected a empty area");
|
||||
|
||||
let uuid: string = "";
|
||||
if (isAngleSnapped.current && anglesnappedPoint.current && line.current.length > 0) {
|
||||
intersectionPoint = anglesnappedPoint.current;
|
||||
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
|
||||
uuid = point.uuid;
|
||||
} else if (isSnapped.current && snappedPoint.current && line.current.length > 0) {
|
||||
intersectionPoint = snappedPoint.current;
|
||||
uuid = isSnappedUUID.current!;
|
||||
} else if (ispreSnapped.current && snappedPoint.current) {
|
||||
intersectionPoint = snappedPoint.current;
|
||||
uuid = isSnappedUUID.current!;
|
||||
} else {
|
||||
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
|
||||
uuid = point.uuid;
|
||||
}
|
||||
|
||||
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), uuid, activeLayer, CONSTANTS.lineConfig.zoneName]);
|
||||
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||
lines.current.push(line.current as Types.Line);
|
||||
|
||||
const data = arrayLineToObject(line.current as Types.Line);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const input = {
|
||||
organization: organization,
|
||||
layer: data.layer,
|
||||
line: data.line,
|
||||
type: data.type,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:create', input);
|
||||
|
||||
setNewLines([line.current]);
|
||||
|
||||
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
|
||||
let lastPoint = line.current[line.current.length - 1];
|
||||
line.current = [lastPoint];
|
||||
ispreSnapped.current = false;
|
||||
isSnapped.current = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toolMode === 'Zone') {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener("click", onMouseClick);
|
||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("click", onMouseClick);
|
||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||
};
|
||||
}, [toolMode])
|
||||
|
||||
return (
|
||||
<group ref={zoneGroup} visible={!toggleView} name="zoneGroup">
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
export default ZoneGroup;
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,128 +9,135 @@ import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi"
|
||||
import updateCamPosition from "../camera/updateCameraPosition";
|
||||
import CamMode from "../camera/camMode";
|
||||
import SwitchView from "../camera/switchView";
|
||||
import SelectionControls from "./selectionControls/selectionControls";
|
||||
|
||||
export default function Controls() {
|
||||
const controlsRef = useRef<CameraControls>(null);
|
||||
const controlsRef = useRef<CameraControls>(null);
|
||||
|
||||
const { toggleView } = useToggleView();
|
||||
const { resetCamera, setResetCamera } = useResetCamera();
|
||||
const { socket } = useSocketStore();
|
||||
const state = useThree();
|
||||
const { toggleView } = useToggleView();
|
||||
const { resetCamera, setResetCamera } = useResetCamera();
|
||||
const { socket } = useSocketStore();
|
||||
const state = useThree();
|
||||
|
||||
useEffect(() => {
|
||||
if (controlsRef.current) {
|
||||
(controlsRef.current as any).mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
|
||||
(controlsRef.current as any).mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
|
||||
}
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
getCamera(organization, localStorage.getItem("userId")!).then((data) => {
|
||||
if (data && data.position && data.target) {
|
||||
controlsRef.current?.setPosition(data.position.x, data.position.y, data.position.z);
|
||||
controlsRef.current?.setTarget(data.target.x, data.target.y, data.target.z);
|
||||
} else {
|
||||
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
})
|
||||
.catch((error) => console.error("Failed to fetch camera data:", error));
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (controlsRef.current) {
|
||||
(controlsRef.current as any).mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
|
||||
(controlsRef.current as any).mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
|
||||
}
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
getCamera(organization, localStorage.getItem("userId")!).then((data) => {
|
||||
if (data && data.position && data.target) {
|
||||
controlsRef.current?.setPosition(data.position.x, data.position.y, data.position.z);
|
||||
controlsRef.current?.setTarget(data.target.x, data.target.y, data.target.z);
|
||||
} else {
|
||||
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
})
|
||||
.catch((error) => console.error("Failed to fetch camera data:", error));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetCamera) {
|
||||
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
|
||||
useEffect(() => {
|
||||
if (resetCamera) {
|
||||
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
|
||||
|
||||
localStorage.setItem("cameraPosition", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition)));
|
||||
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget)));
|
||||
localStorage.setItem("cameraPosition", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition)));
|
||||
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget)));
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
const camData = {
|
||||
organization: organization,
|
||||
userId: localStorage.getItem('userId')!,
|
||||
position: new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
|
||||
target: new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
|
||||
rotation: new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation),
|
||||
socketId: socket.id
|
||||
};
|
||||
socket.emit('v1:Camera:set', camData)
|
||||
const camData = {
|
||||
organization: organization,
|
||||
userId: localStorage.getItem('userId')!,
|
||||
position: new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
|
||||
target: new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
|
||||
rotation: new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation),
|
||||
socketId: socket.id
|
||||
};
|
||||
socket.emit('v1:Camera:set', camData)
|
||||
|
||||
setResetCamera(false);
|
||||
}
|
||||
}, [resetCamera]);
|
||||
setResetCamera(false);
|
||||
}
|
||||
}, [resetCamera]);
|
||||
|
||||
useEffect(() => {
|
||||
controlsRef.current?.setBoundary(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)));
|
||||
// state.scene.add(new THREE.Box3Helper(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)), 0xffff00));
|
||||
let hasInteracted = false;
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
useEffect(() => {
|
||||
controlsRef.current?.setBoundary(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)));
|
||||
// state.scene.add(new THREE.Box3Helper(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)), 0xffff00));
|
||||
let hasInteracted = false;
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
|
||||
const handleRest = () => {
|
||||
if (hasInteracted && controlsRef.current && state.camera.position && !toggleView) {
|
||||
const position = state.camera.position;
|
||||
if (position.x === 0 && position.y === 0 && position.z === 0) return;
|
||||
updateCamPosition(controlsRef, socket, position, state.camera.rotation);
|
||||
stopInterval();
|
||||
}
|
||||
};
|
||||
const handleRest = () => {
|
||||
if (hasInteracted && controlsRef.current && state.camera.position && !toggleView) {
|
||||
const position = state.camera.position;
|
||||
if (position.x === 0 && position.y === 0 && position.z === 0) return;
|
||||
updateCamPosition(controlsRef, socket, position, state.camera.rotation);
|
||||
stopInterval();
|
||||
}
|
||||
};
|
||||
|
||||
const startInterval = () => {
|
||||
hasInteracted = true;
|
||||
if (!intervalId) {
|
||||
intervalId = setInterval(() => {
|
||||
if (controlsRef.current && !toggleView) {
|
||||
handleRest();
|
||||
}
|
||||
}, CONSTANTS.camPositionUpdateInterval);
|
||||
}
|
||||
};
|
||||
const startInterval = () => {
|
||||
hasInteracted = true;
|
||||
if (!intervalId) {
|
||||
intervalId = setInterval(() => {
|
||||
if (controlsRef.current && !toggleView) {
|
||||
handleRest();
|
||||
}
|
||||
}, CONSTANTS.camPositionUpdateInterval);
|
||||
}
|
||||
};
|
||||
|
||||
const stopInterval = () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
};
|
||||
const stopInterval = () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
};
|
||||
|
||||
const controls = controlsRef.current;
|
||||
if (controls) {
|
||||
controls.addEventListener("sleep", handleRest);
|
||||
controls.addEventListener("control", startInterval);
|
||||
controls.addEventListener("controlend", stopInterval);
|
||||
}
|
||||
const controls = controlsRef.current;
|
||||
if (controls) {
|
||||
controls.addEventListener("sleep", handleRest);
|
||||
controls.addEventListener("control", startInterval);
|
||||
controls.addEventListener("controlend", stopInterval);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (controls) {
|
||||
controls.removeEventListener("sleep", handleRest);
|
||||
controls.removeEventListener("control", startInterval);
|
||||
controls.removeEventListener("controlend", stopInterval);
|
||||
}
|
||||
stopInterval();
|
||||
};
|
||||
}, [toggleView, state, socket]);
|
||||
return () => {
|
||||
if (controls) {
|
||||
controls.removeEventListener("sleep", handleRest);
|
||||
controls.removeEventListener("control", startInterval);
|
||||
controls.removeEventListener("controlend", stopInterval);
|
||||
}
|
||||
stopInterval();
|
||||
};
|
||||
}, [toggleView, state, socket]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CameraControls
|
||||
makeDefault
|
||||
ref={controlsRef}
|
||||
minDistance={toggleView ? CONSTANTS.twoDimension.minDistance : CONSTANTS.threeDimension.minDistance}
|
||||
maxDistance={CONSTANTS.thirdPersonControls.maxDistance}
|
||||
minZoom={CONSTANTS.thirdPersonControls.minZoom}
|
||||
maxZoom={CONSTANTS.thirdPersonControls.maxZoom}
|
||||
maxPolarAngle={CONSTANTS.thirdPersonControls.maxPolarAngle}
|
||||
camera={state.camera}
|
||||
verticalDragToForward={true}
|
||||
boundaryEnclosesCamera={true}
|
||||
dollyToCursor={toggleView}
|
||||
>
|
||||
<SwitchView />
|
||||
<CamMode />
|
||||
</CameraControls>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<CameraControls
|
||||
makeDefault
|
||||
ref={controlsRef}
|
||||
minDistance={toggleView ? CONSTANTS.twoDimension.minDistance : CONSTANTS.threeDimension.minDistance}
|
||||
maxDistance={CONSTANTS.thirdPersonControls.maxDistance}
|
||||
minZoom={CONSTANTS.thirdPersonControls.minZoom}
|
||||
maxZoom={CONSTANTS.thirdPersonControls.maxZoom}
|
||||
maxPolarAngle={CONSTANTS.thirdPersonControls.maxPolarAngle}
|
||||
camera={state.camera}
|
||||
verticalDragToForward={true}
|
||||
boundaryEnclosesCamera={true}
|
||||
dollyToCursor={toggleView}
|
||||
>
|
||||
|
||||
<SwitchView />
|
||||
|
||||
<CamMode />
|
||||
|
||||
</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
|
||||
@@ -1,64 +1,64 @@
|
||||
import { Line } from "@react-three/drei";
|
||||
import { useMemo } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useSelectedAssets } from "../../../../store/store";
|
||||
|
||||
const BoundingBox = ({ boundingBoxRef }: any) => {
|
||||
const { selectedAssets } = useSelectedAssets();
|
||||
|
||||
const { points, boxProps } = useMemo(() => {
|
||||
if (selectedAssets.length === 0) return { points: [], boxProps: {} };
|
||||
|
||||
const box = new THREE.Box3();
|
||||
selectedAssets.forEach((obj: any) => box.expandByObject(obj.clone()));
|
||||
|
||||
const size = new THREE.Vector3();
|
||||
box.getSize(size);
|
||||
const center = new THREE.Vector3();
|
||||
box.getCenter(center);
|
||||
|
||||
const halfSize = size.clone().multiplyScalar(0.5);
|
||||
const min = center.clone().sub(halfSize);
|
||||
const max = center.clone().add(halfSize);
|
||||
|
||||
const points: any = [
|
||||
[min.x, min.y, min.z], [max.x, min.y, min.z],
|
||||
[max.x, min.y, min.z], [max.x, max.y, min.z],
|
||||
[max.x, max.y, min.z], [min.x, max.y, min.z],
|
||||
[min.x, max.y, min.z], [min.x, min.y, min.z],
|
||||
|
||||
[min.x, min.y, max.z], [max.x, min.y, max.z],
|
||||
[max.x, min.y, max.z], [max.x, max.y, max.z],
|
||||
[max.x, max.y, max.z], [min.x, max.y, max.z],
|
||||
[min.x, max.y, max.z], [min.x, min.y, max.z],
|
||||
|
||||
[min.x, min.y, min.z], [min.x, min.y, max.z],
|
||||
[max.x, min.y, min.z], [max.x, min.y, max.z],
|
||||
[max.x, max.y, min.z], [max.x, max.y, max.z],
|
||||
[min.x, max.y, min.z], [min.x, max.y, max.z],
|
||||
];
|
||||
|
||||
return {
|
||||
points,
|
||||
boxProps: { position: center.toArray(), args: size.toArray() }
|
||||
};
|
||||
}, [selectedAssets]);
|
||||
|
||||
const savedTheme: string | null = localStorage.getItem("theme") || "light";
|
||||
|
||||
return (
|
||||
<>
|
||||
{points.length > 0 && (
|
||||
<>
|
||||
<Line points={points} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} segments />
|
||||
<mesh ref={boundingBoxRef} visible={false} position={boxProps.position}>
|
||||
<boxGeometry args={boxProps.args} />
|
||||
<meshBasicMaterial />
|
||||
</mesh>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoundingBox;
|
||||
import { Line } from "@react-three/drei";
|
||||
import { useMemo } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useSelectedAssets } from "../../../../store/store";
|
||||
|
||||
const BoundingBox = ({ boundingBoxRef }: any) => {
|
||||
const { selectedAssets } = useSelectedAssets();
|
||||
|
||||
const { points, boxProps } = useMemo(() => {
|
||||
if (selectedAssets.length === 0) return { points: [], boxProps: {} };
|
||||
|
||||
const box = new THREE.Box3();
|
||||
selectedAssets.forEach((obj: any) => box.expandByObject(obj.clone()));
|
||||
|
||||
const size = new THREE.Vector3();
|
||||
box.getSize(size);
|
||||
const center = new THREE.Vector3();
|
||||
box.getCenter(center);
|
||||
|
||||
const halfSize = size.clone().multiplyScalar(0.5);
|
||||
const min = center.clone().sub(halfSize);
|
||||
const max = center.clone().add(halfSize);
|
||||
|
||||
const points: any = [
|
||||
[min.x, min.y, min.z], [max.x, min.y, min.z],
|
||||
[max.x, min.y, min.z], [max.x, max.y, min.z],
|
||||
[max.x, max.y, min.z], [min.x, max.y, min.z],
|
||||
[min.x, max.y, min.z], [min.x, min.y, min.z],
|
||||
|
||||
[min.x, min.y, max.z], [max.x, min.y, max.z],
|
||||
[max.x, min.y, max.z], [max.x, max.y, max.z],
|
||||
[max.x, max.y, max.z], [min.x, max.y, max.z],
|
||||
[min.x, max.y, max.z], [min.x, min.y, max.z],
|
||||
|
||||
[min.x, min.y, min.z], [min.x, min.y, max.z],
|
||||
[max.x, min.y, min.z], [max.x, min.y, max.z],
|
||||
[max.x, max.y, min.z], [max.x, max.y, max.z],
|
||||
[min.x, max.y, min.z], [min.x, max.y, max.z],
|
||||
];
|
||||
|
||||
return {
|
||||
points,
|
||||
boxProps: { position: center.toArray(), args: size.toArray() }
|
||||
};
|
||||
}, [selectedAssets]);
|
||||
|
||||
const savedTheme: string | null = localStorage.getItem("theme") || "light";
|
||||
|
||||
return (
|
||||
<>
|
||||
{points.length > 0 && (
|
||||
<>
|
||||
<Line points={points} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} segments />
|
||||
<mesh ref={boundingBoxRef} visible={false} position={boxProps.position}>
|
||||
<boxGeometry args={boxProps.args} />
|
||||
<meshBasicMaterial />
|
||||
</mesh>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoundingBox;
|
||||
@@ -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
|
||||
@@ -1,479 +1,272 @@
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
|
||||
import { SelectionHelper } from "./selectionHelper";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView, } from "../../../../store/store";
|
||||
import BoundingBox from "./boundingBoxHelper";
|
||||
import { toast } from "react-toastify";
|
||||
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import * as SimulationTypes from "../../../../types/simulationTypes";
|
||||
|
||||
import DuplicationControls from "./duplicationControls";
|
||||
import CopyPasteControls from "./copyPasteControls";
|
||||
import MoveControls from "./moveControls";
|
||||
import RotateControls from "./rotateControls";
|
||||
import useModuleStore from "../../../../store/useModuleStore";
|
||||
|
||||
const SelectionControls: React.FC = () => {
|
||||
const { camera, controls, gl, scene, pointer } = useThree();
|
||||
const itemsGroupRef = useRef<THREE.Group | undefined>(undefined);
|
||||
const selectionGroup = useRef() as Types.RefGroup;
|
||||
const { toggleView } = useToggleView();
|
||||
const { simulationStates, setSimulationStates } = useSimulationStates();
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [rotatedObjects, setRotatedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [copiedObjects, setCopiedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [pastedObjects, setpastedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [duplicatedObjects, setDuplicatedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const boundingBoxRef = useRef<THREE.Mesh>();
|
||||
const { floorItems, setFloorItems } = useFloorItems();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
|
||||
const canvasElement = gl.domElement;
|
||||
canvasElement.tabIndex = 0;
|
||||
|
||||
const itemsGroup: any = scene.getObjectByName("itemsGroup");
|
||||
itemsGroupRef.current = itemsGroup;
|
||||
|
||||
let isSelecting = false;
|
||||
let isRightClick = false;
|
||||
let rightClickMoved = false;
|
||||
let isCtrlSelecting = false;
|
||||
|
||||
const helper = new SelectionHelper(gl);
|
||||
|
||||
if (!itemsGroup) {
|
||||
toast.warn("itemsGroup not found in the scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
if (event.button === 2) {
|
||||
isRightClick = true;
|
||||
rightClickMoved = false;
|
||||
} else if (event.button === 0) {
|
||||
isSelecting = false;
|
||||
isCtrlSelecting = event.ctrlKey;
|
||||
if (event.ctrlKey && duplicatedObjects.length === 0) {
|
||||
if (controls) (controls as any).enabled = false;
|
||||
selectionBox.startPoint.set(pointer.x, pointer.y, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (isRightClick) {
|
||||
rightClickMoved = true;
|
||||
}
|
||||
isSelecting = true;
|
||||
if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting) {
|
||||
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (event: PointerEvent) => {
|
||||
if (event.button === 2) {
|
||||
isRightClick = false;
|
||||
if (!rightClickMoved) {
|
||||
clearSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelecting && isCtrlSelecting) {
|
||||
isCtrlSelecting = false;
|
||||
isSelecting = false;
|
||||
if (event.ctrlKey && duplicatedObjects.length === 0) {
|
||||
selectAssets();
|
||||
}
|
||||
} else if (!isSelecting && selectedAssets.length > 0 && ((pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) {
|
||||
clearSelection();
|
||||
helper.enabled = true;
|
||||
isCtrlSelecting = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
|
||||
if (event.key.toLowerCase() === "escape") {
|
||||
event.preventDefault();
|
||||
clearSelection();
|
||||
}
|
||||
if (event.key.toLowerCase() === "delete") {
|
||||
event.preventDefault();
|
||||
deleteSelection();
|
||||
}
|
||||
};
|
||||
|
||||
const onContextMenu = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if (!rightClickMoved) {
|
||||
clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
if (!toggleView && activeModule === "builder") {
|
||||
helper.enabled = true;
|
||||
if (duplicatedObjects.length === 0 && pastedObjects.length === 0) {
|
||||
canvasElement.addEventListener("pointerdown", onPointerDown);
|
||||
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||
} else {
|
||||
helper.enabled = false;
|
||||
helper.dispose();
|
||||
}
|
||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||
canvasElement.addEventListener("keydown", onKeyDown);
|
||||
} else {
|
||||
helper.enabled = false;
|
||||
helper.dispose();
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("pointerdown", onPointerDown);
|
||||
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||
helper.enabled = false;
|
||||
helper.dispose();
|
||||
};
|
||||
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects, activeModule,]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeModule !== "builder") {
|
||||
clearSelection();
|
||||
}
|
||||
}, [activeModule]);
|
||||
|
||||
useFrame(() => {
|
||||
if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||
selectionGroup.current.position.set(0, 0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
const selectAssets = () => {
|
||||
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
|
||||
if (controls) (controls as any).enabled = true;
|
||||
|
||||
let selectedObjects = selectionBox.select();
|
||||
let Objects = new Set<THREE.Object3D>();
|
||||
|
||||
selectedObjects.map((object) => {
|
||||
let currentObject: THREE.Object3D | null = object;
|
||||
while (currentObject) {
|
||||
if (currentObject.userData.modelId) {
|
||||
Objects.add(currentObject);
|
||||
break;
|
||||
}
|
||||
currentObject = currentObject.parent || null;
|
||||
}
|
||||
});
|
||||
|
||||
if (Objects.size === 0) {
|
||||
clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSelections = new Set(selectedAssets);
|
||||
Objects.forEach((obj) => { updatedSelections.has(obj) ? updatedSelections.delete(obj) : updatedSelections.add(obj); });
|
||||
|
||||
const selected = Array.from(updatedSelections);
|
||||
|
||||
setSelectedAssets(selected);
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
selectionGroup.current.children = [];
|
||||
selectionGroup.current.position.set(0, 0, 0);
|
||||
selectionGroup.current.rotation.set(0, 0, 0);
|
||||
setpastedObjects([]);
|
||||
setDuplicatedObjects([]);
|
||||
setSelectedAssets([]);
|
||||
};
|
||||
const updateBackend = async (updatedPaths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
|
||||
if (updatedPaths.length === 0) return;
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email ? email.split("@")[1].split(".")[0] : "";
|
||||
|
||||
updatedPaths.forEach(async (updatedPath) => {
|
||||
if (updatedPath.type === "Conveyor") {
|
||||
// await setEventApi(
|
||||
// organization,
|
||||
// updatedPath.modeluuid,
|
||||
// { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed }
|
||||
// );
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: updatedPath.modeluuid,
|
||||
eventData: {
|
||||
type: "Conveyor",
|
||||
points: updatedPath.points,
|
||||
speed: updatedPath.speed,
|
||||
},
|
||||
};
|
||||
|
||||
socket.emit("v2:model-asset:updateEventData", data);
|
||||
} else if (updatedPath.type === "Vehicle") {
|
||||
// await setEventApi(
|
||||
// organization,
|
||||
// updatedPath.modeluuid,
|
||||
// { type: "Vehicle", points: updatedPath.points }
|
||||
// );
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: updatedPath.modeluuid,
|
||||
eventData: { type: "Vehicle", points: updatedPath.points },
|
||||
};
|
||||
|
||||
socket.emit("v2:model-asset:updateEventData", data);
|
||||
} else if (updatedPath.type === "StaticMachine") {
|
||||
// await setEventApi(
|
||||
// organization,
|
||||
// updatedPath.modeluuid,
|
||||
// { type: "StaticMachine", points: updatedPath.points }
|
||||
// );
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: updatedPath.modeluuid,
|
||||
eventData: { type: "StaticMachine", points: updatedPath.points },
|
||||
};
|
||||
|
||||
socket.emit("v2:model-asset:updateEventData", data);
|
||||
} else if (updatedPath.type === "ArmBot") {
|
||||
// await setEventApi(
|
||||
// organization,
|
||||
// updatedPath.modeluuid,
|
||||
// { type: "ArmBot", points: updatedPath.points }
|
||||
// );
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: updatedPath.modeluuid,
|
||||
eventData: { type: "ArmBot", points: updatedPath.points },
|
||||
};
|
||||
|
||||
socket.emit("v2:model-asset:updateEventData", data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removeConnections = (deletedModelUUIDs: string[]) => {
|
||||
|
||||
const deletedPointUUIDs = new Set<string>();
|
||||
simulationStates.forEach(state => {
|
||||
if (deletedModelUUIDs.includes(state.modeluuid)) {
|
||||
if (state.type === "Conveyor" && state.points) {
|
||||
state.points.forEach(point => {
|
||||
deletedPointUUIDs.add(point.uuid);
|
||||
});
|
||||
} else if (state.points && 'uuid' in state.points) {
|
||||
deletedPointUUIDs.add(state.points.uuid);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updatedStates = simulationStates.map((state) => {
|
||||
// Handle Conveyor
|
||||
if (state.type === "Conveyor") {
|
||||
const updatedConveyor: SimulationTypes.ConveyorEventsSchema = {
|
||||
...state,
|
||||
points: state.points.map((point) => {
|
||||
return {
|
||||
...point,
|
||||
connections: {
|
||||
...point.connections,
|
||||
targets: point.connections.targets.filter(
|
||||
(target) => !deletedModelUUIDs.includes(target.modelUUID)
|
||||
),
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
return updatedConveyor;
|
||||
}
|
||||
|
||||
// Handle Vehicle
|
||||
else if (state.type === "Vehicle") {
|
||||
const updatedVehicle: SimulationTypes.VehicleEventsSchema = {
|
||||
...state,
|
||||
points: {
|
||||
...state.points,
|
||||
connections: {
|
||||
...state.points.connections,
|
||||
targets: state.points.connections.targets.filter(
|
||||
(target) => !deletedModelUUIDs.includes(target.modelUUID)
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
return updatedVehicle;
|
||||
}
|
||||
|
||||
// Handle StaticMachine
|
||||
else if (state.type === "StaticMachine") {
|
||||
const updatedStaticMachine: SimulationTypes.StaticMachineEventsSchema =
|
||||
{
|
||||
...state,
|
||||
points: {
|
||||
...state.points,
|
||||
connections: {
|
||||
...state.points.connections,
|
||||
targets: state.points.connections.targets.filter(
|
||||
(target) => !deletedModelUUIDs.includes(target.modelUUID)
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
return updatedStaticMachine;
|
||||
}
|
||||
|
||||
// Handle ArmBot
|
||||
else if (state.type === "ArmBot") {
|
||||
const updatedArmBot: SimulationTypes.ArmBotEventsSchema = {
|
||||
...state,
|
||||
points: {
|
||||
...state.points,
|
||||
connections: {
|
||||
...state.points.connections,
|
||||
targets: state.points.connections.targets.filter(
|
||||
(target: any) => !deletedModelUUIDs.includes(target.modelUUID)
|
||||
),
|
||||
},
|
||||
actions: {
|
||||
...state.points.actions,
|
||||
processes: state.points.actions.processes?.filter((process) => {
|
||||
// Check if trigger is from deleted model
|
||||
const matchedStates = simulationStates.filter((s) => deletedModelUUIDs.includes(s.modeluuid));
|
||||
|
||||
if (matchedStates.length > 0) {
|
||||
if (matchedStates[0]?.type === "StaticMachine") {
|
||||
const trigPoints = matchedStates[0]?.points;
|
||||
if (process.triggerId === trigPoints?.triggers?.uuid) {
|
||||
return false;
|
||||
}
|
||||
} else if (matchedStates[0]?.type === "Conveyor") {
|
||||
const trigPoints = matchedStates[0]?.points;
|
||||
if (Array.isArray(trigPoints)) {
|
||||
const nonEmptyTriggers = trigPoints.filter((point) => point && point.triggers && point.triggers.length > 0);
|
||||
const allTriggerUUIDs = nonEmptyTriggers.flatMap((point) => point.triggers).map((trigger) => trigger.uuid);
|
||||
if (allTriggerUUIDs.includes(process.triggerId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if startPoint or endPoint is from deleted model
|
||||
if (deletedPointUUIDs.has(process.startPoint) || deletedPointUUIDs.has(process.endPoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
return updatedArmBot;
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
const filteredStates = updatedStates.filter((state) => !deletedModelUUIDs.includes(state.modeluuid));
|
||||
|
||||
updateBackend(filteredStates);
|
||||
setSimulationStates(filteredStates);
|
||||
};
|
||||
|
||||
const deleteSelection = () => {
|
||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || "[]");
|
||||
const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid);
|
||||
|
||||
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
|
||||
|
||||
selectedAssets.forEach((selectedMesh: THREE.Object3D) => {
|
||||
//REST
|
||||
|
||||
// const response = await deleteFloorItem(organization, selectedMesh.uuid, selectedMesh.userData.name);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: selectedMesh.uuid,
|
||||
modelname: selectedMesh.userData.name,
|
||||
socketId: socket.id,
|
||||
};
|
||||
|
||||
socket.emit("v2:model-asset:delete", data);
|
||||
|
||||
selectedMesh.traverse((child: THREE.Object3D) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (child.geometry) child.geometry.dispose();
|
||||
if (Array.isArray(child.material)) {
|
||||
child.material.forEach((material) => {
|
||||
if (material.map) material.map.dispose();
|
||||
material.dispose();
|
||||
});
|
||||
} else if (child.material) {
|
||||
if (child.material.map) child.material.map.dispose();
|
||||
child.material.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setSimulationStates((prevEvents: (| SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
|
||||
const updatedEvents = (prevEvents || []).filter((event) => event.modeluuid !== selectedMesh.uuid);
|
||||
return updatedEvents;
|
||||
});
|
||||
|
||||
itemsGroupRef.current?.remove(selectedMesh);
|
||||
});
|
||||
|
||||
const allUUIDs = selectedAssets.map((val: any) => val.uuid);
|
||||
removeConnections(allUUIDs);
|
||||
|
||||
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
|
||||
setFloorItems(updatedItems);
|
||||
}
|
||||
toast.success("Selected models removed!");
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<group name="SelectionGroup">
|
||||
<group ref={selectionGroup} name="selectionAssetGroup">
|
||||
<BoundingBox boundingBoxRef={boundingBoxRef} />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<MoveControls movedObjects={movedObjects} setMovedObjects={setMovedObjects} itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} selectionGroup={selectionGroup} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<RotateControls rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} selectionGroup={selectionGroup} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<DuplicationControls itemsGroupRef={itemsGroupRef} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<CopyPasteControls itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} setDuplicatedObjects={setDuplicatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectionControls;
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
|
||||
import { SelectionHelper } from "./selectionHelper";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView, } from "../../../../store/store";
|
||||
import BoundingBox from "./boundingBoxHelper";
|
||||
import { toast } from "react-toastify";
|
||||
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
import DuplicationControls from "./duplicationControls";
|
||||
import CopyPasteControls from "./copyPasteControls";
|
||||
import MoveControls from "./moveControls";
|
||||
import RotateControls from "./rotateControls";
|
||||
import useModuleStore from "../../../../store/useModuleStore";
|
||||
|
||||
const SelectionControls: React.FC = () => {
|
||||
const { camera, controls, gl, scene, pointer } = useThree();
|
||||
const itemsGroupRef = useRef<THREE.Group | undefined>(undefined);
|
||||
const selectionGroup = useRef() as Types.RefGroup;
|
||||
const { toggleView } = useToggleView();
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [rotatedObjects, setRotatedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [copiedObjects, setCopiedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [pastedObjects, setpastedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const [duplicatedObjects, setDuplicatedObjects] = useState<THREE.Object3D[]>([]);
|
||||
const boundingBoxRef = useRef<THREE.Mesh>();
|
||||
const { floorItems, setFloorItems } = useFloorItems();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
|
||||
const canvasElement = gl.domElement;
|
||||
canvasElement.tabIndex = 0;
|
||||
|
||||
const itemsGroup: any = scene.getObjectByName("itemsGroup");
|
||||
itemsGroupRef.current = itemsGroup;
|
||||
|
||||
let isSelecting = false;
|
||||
let isRightClick = false;
|
||||
let rightClickMoved = false;
|
||||
let isCtrlSelecting = false;
|
||||
|
||||
const helper = new SelectionHelper(gl);
|
||||
|
||||
if (!itemsGroup) {
|
||||
toast.warn("itemsGroup not found in the scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
if (event.button === 2) {
|
||||
isRightClick = true;
|
||||
rightClickMoved = false;
|
||||
} else if (event.button === 0) {
|
||||
isSelecting = false;
|
||||
isCtrlSelecting = event.ctrlKey;
|
||||
if (event.ctrlKey && duplicatedObjects.length === 0) {
|
||||
if (controls) (controls as any).enabled = false;
|
||||
selectionBox.startPoint.set(pointer.x, pointer.y, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (isRightClick) {
|
||||
rightClickMoved = true;
|
||||
}
|
||||
isSelecting = true;
|
||||
if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting) {
|
||||
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (event: PointerEvent) => {
|
||||
if (event.button === 2) {
|
||||
isRightClick = false;
|
||||
if (!rightClickMoved) {
|
||||
clearSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelecting && isCtrlSelecting) {
|
||||
isCtrlSelecting = false;
|
||||
isSelecting = false;
|
||||
if (event.ctrlKey && duplicatedObjects.length === 0) {
|
||||
selectAssets();
|
||||
}
|
||||
} else if (!isSelecting && selectedAssets.length > 0 && ((pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) {
|
||||
clearSelection();
|
||||
helper.enabled = true;
|
||||
isCtrlSelecting = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
|
||||
if (event.key.toLowerCase() === "escape") {
|
||||
event.preventDefault();
|
||||
clearSelection();
|
||||
}
|
||||
if (event.key.toLowerCase() === "delete") {
|
||||
event.preventDefault();
|
||||
deleteSelection();
|
||||
}
|
||||
};
|
||||
|
||||
const onContextMenu = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if (!rightClickMoved) {
|
||||
clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
if (!toggleView && activeModule === "builder") {
|
||||
helper.enabled = true;
|
||||
if (duplicatedObjects.length === 0 && pastedObjects.length === 0) {
|
||||
canvasElement.addEventListener("pointerdown", onPointerDown);
|
||||
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||
} else {
|
||||
helper.enabled = false;
|
||||
helper.dispose();
|
||||
}
|
||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||
canvasElement.addEventListener("keydown", onKeyDown);
|
||||
} else {
|
||||
helper.enabled = false;
|
||||
helper.dispose();
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("pointerdown", onPointerDown);
|
||||
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||
helper.enabled = false;
|
||||
helper.dispose();
|
||||
};
|
||||
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects, activeModule,]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeModule !== "builder") {
|
||||
clearSelection();
|
||||
}
|
||||
}, [activeModule]);
|
||||
|
||||
useFrame(() => {
|
||||
if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||
selectionGroup.current.position.set(0, 0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
const selectAssets = () => {
|
||||
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
|
||||
if (controls) (controls as any).enabled = true;
|
||||
|
||||
let selectedObjects = selectionBox.select();
|
||||
let Objects = new Set<THREE.Object3D>();
|
||||
|
||||
selectedObjects.map((object) => {
|
||||
let currentObject: THREE.Object3D | null = object;
|
||||
while (currentObject) {
|
||||
if (currentObject.userData.modelId) {
|
||||
Objects.add(currentObject);
|
||||
break;
|
||||
}
|
||||
currentObject = currentObject.parent || null;
|
||||
}
|
||||
});
|
||||
|
||||
if (Objects.size === 0) {
|
||||
clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSelections = new Set(selectedAssets);
|
||||
Objects.forEach((obj) => { updatedSelections.has(obj) ? updatedSelections.delete(obj) : updatedSelections.add(obj); });
|
||||
|
||||
const selected = Array.from(updatedSelections);
|
||||
|
||||
setSelectedAssets(selected);
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
selectionGroup.current.children = [];
|
||||
selectionGroup.current.position.set(0, 0, 0);
|
||||
selectionGroup.current.rotation.set(0, 0, 0);
|
||||
setpastedObjects([]);
|
||||
setDuplicatedObjects([]);
|
||||
setSelectedAssets([]);
|
||||
};
|
||||
|
||||
const deleteSelection = () => {
|
||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || "[]");
|
||||
const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid);
|
||||
|
||||
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
|
||||
|
||||
selectedAssets.forEach((selectedMesh: THREE.Object3D) => {
|
||||
//REST
|
||||
|
||||
// const response = await deleteFloorItem(organization, selectedMesh.uuid, selectedMesh.userData.name);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: selectedMesh.uuid,
|
||||
modelname: selectedMesh.userData.name,
|
||||
socketId: socket.id,
|
||||
};
|
||||
|
||||
socket.emit("v2:model-asset:delete", data);
|
||||
|
||||
selectedMesh.traverse((child: THREE.Object3D) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (child.geometry) child.geometry.dispose();
|
||||
if (Array.isArray(child.material)) {
|
||||
child.material.forEach((material) => {
|
||||
if (material.map) material.map.dispose();
|
||||
material.dispose();
|
||||
});
|
||||
} else if (child.material) {
|
||||
if (child.material.map) child.material.map.dispose();
|
||||
child.material.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itemsGroupRef.current?.remove(selectedMesh);
|
||||
});
|
||||
|
||||
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
|
||||
setFloorItems(updatedItems);
|
||||
}
|
||||
toast.success("Selected models removed!");
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<group name="SelectionGroup">
|
||||
<group ref={selectionGroup} name="selectionAssetGroup">
|
||||
<BoundingBox boundingBoxRef={boundingBoxRef} />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<MoveControls movedObjects={movedObjects} setMovedObjects={setMovedObjects} itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} selectionGroup={selectionGroup} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<RotateControls rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} selectionGroup={selectionGroup} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<DuplicationControls itemsGroupRef={itemsGroupRef} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<CopyPasteControls itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} setDuplicatedObjects={setDuplicatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectionControls;
|
||||
@@ -1,115 +1,115 @@
|
||||
import { Vector2, WebGLRenderer } from 'three';
|
||||
|
||||
class SelectionHelper {
|
||||
element: HTMLDivElement;
|
||||
renderer: WebGLRenderer;
|
||||
startPoint: Vector2;
|
||||
pointTopLeft: Vector2;
|
||||
pointBottomRight: Vector2;
|
||||
isDown: boolean;
|
||||
enabled: boolean;
|
||||
|
||||
constructor(renderer: WebGLRenderer) {
|
||||
this.element = document.createElement('div');
|
||||
this.element.style.position = 'fixed';
|
||||
this.element.style.border = '1px solid #55aaff';
|
||||
this.element.style.backgroundColor = 'rgba(75, 160, 255, 0.3)';
|
||||
this.element.style.pointerEvents = 'none';
|
||||
this.element.style.display = 'none';
|
||||
|
||||
this.renderer = renderer;
|
||||
|
||||
this.startPoint = new Vector2();
|
||||
this.pointTopLeft = new Vector2();
|
||||
this.pointBottomRight = new Vector2();
|
||||
|
||||
this.isDown = false;
|
||||
this.enabled = true;
|
||||
|
||||
this.onPointerDown = this.onPointerDown.bind(this);
|
||||
this.onPointerMove = this.onPointerMove.bind(this);
|
||||
this.onPointerUp = this.onPointerUp.bind(this);
|
||||
|
||||
this.renderer.domElement.addEventListener('pointerdown', this.onPointerDown);
|
||||
this.renderer.domElement.addEventListener('pointermove', this.onPointerMove);
|
||||
this.renderer.domElement.addEventListener('pointerup', this.onPointerUp);
|
||||
window.addEventListener("blur", this.cleanup.bind(this));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.enabled = false;
|
||||
this.isDown = false;
|
||||
this.cleanup();
|
||||
|
||||
this.renderer.domElement.removeEventListener("pointerdown", this.onPointerDown);
|
||||
this.renderer.domElement.removeEventListener("pointermove", this.onPointerMove);
|
||||
this.renderer.domElement.removeEventListener("pointerup", this.onPointerUp);
|
||||
window.removeEventListener("blur", this.cleanup);
|
||||
}
|
||||
|
||||
private cleanup() {
|
||||
this.isDown = false;
|
||||
this.element.style.display = 'none';
|
||||
if (this.element.parentElement) {
|
||||
this.element.parentElement.removeChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
onPointerDown(event: PointerEvent) {
|
||||
if (!this.enabled || !event.ctrlKey || event.button !== 0) return;
|
||||
|
||||
this.isDown = true;
|
||||
this.onSelectStart(event);
|
||||
}
|
||||
|
||||
onPointerMove(event: PointerEvent) {
|
||||
if (!this.enabled || !this.isDown || !event.ctrlKey) return;
|
||||
|
||||
this.onSelectMove(event);
|
||||
}
|
||||
|
||||
onPointerUp() {
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.isDown = false;
|
||||
this.onSelectOver();
|
||||
}
|
||||
|
||||
onSelectStart(event: PointerEvent) {
|
||||
this.element.style.display = 'none';
|
||||
this.renderer.domElement.parentElement?.appendChild(this.element);
|
||||
|
||||
this.element.style.left = `${event.clientX}px`;
|
||||
this.element.style.top = `${event.clientY}px`;
|
||||
this.element.style.width = '0px';
|
||||
this.element.style.height = '0px';
|
||||
|
||||
this.startPoint.x = event.clientX;
|
||||
this.startPoint.y = event.clientY;
|
||||
}
|
||||
|
||||
onSelectMove(event: PointerEvent) {
|
||||
if (!this.isDown) return;
|
||||
|
||||
this.element.style.display = 'block';
|
||||
|
||||
this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX);
|
||||
this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY);
|
||||
this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX);
|
||||
this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY);
|
||||
|
||||
this.element.style.left = `${this.pointTopLeft.x}px`;
|
||||
this.element.style.top = `${this.pointTopLeft.y}px`;
|
||||
this.element.style.width = `${this.pointBottomRight.x - this.pointTopLeft.x}px`;
|
||||
this.element.style.height = `${this.pointBottomRight.y - this.pointTopLeft.y}px`;
|
||||
}
|
||||
|
||||
onSelectOver() {
|
||||
this.element.style.display = 'none';
|
||||
if (this.element.parentElement) {
|
||||
this.element.parentElement.removeChild(this.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import { Vector2, WebGLRenderer } from 'three';
|
||||
|
||||
class SelectionHelper {
|
||||
element: HTMLDivElement;
|
||||
renderer: WebGLRenderer;
|
||||
startPoint: Vector2;
|
||||
pointTopLeft: Vector2;
|
||||
pointBottomRight: Vector2;
|
||||
isDown: boolean;
|
||||
enabled: boolean;
|
||||
|
||||
constructor(renderer: WebGLRenderer) {
|
||||
this.element = document.createElement('div');
|
||||
this.element.style.position = 'fixed';
|
||||
this.element.style.border = '1px solid #55aaff';
|
||||
this.element.style.backgroundColor = 'rgba(75, 160, 255, 0.3)';
|
||||
this.element.style.pointerEvents = 'none';
|
||||
this.element.style.display = 'none';
|
||||
|
||||
this.renderer = renderer;
|
||||
|
||||
this.startPoint = new Vector2();
|
||||
this.pointTopLeft = new Vector2();
|
||||
this.pointBottomRight = new Vector2();
|
||||
|
||||
this.isDown = false;
|
||||
this.enabled = true;
|
||||
|
||||
this.onPointerDown = this.onPointerDown.bind(this);
|
||||
this.onPointerMove = this.onPointerMove.bind(this);
|
||||
this.onPointerUp = this.onPointerUp.bind(this);
|
||||
|
||||
this.renderer.domElement.addEventListener('pointerdown', this.onPointerDown);
|
||||
this.renderer.domElement.addEventListener('pointermove', this.onPointerMove);
|
||||
this.renderer.domElement.addEventListener('pointerup', this.onPointerUp);
|
||||
window.addEventListener("blur", this.cleanup.bind(this));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.enabled = false;
|
||||
this.isDown = false;
|
||||
this.cleanup();
|
||||
|
||||
this.renderer.domElement.removeEventListener("pointerdown", this.onPointerDown);
|
||||
this.renderer.domElement.removeEventListener("pointermove", this.onPointerMove);
|
||||
this.renderer.domElement.removeEventListener("pointerup", this.onPointerUp);
|
||||
window.removeEventListener("blur", this.cleanup);
|
||||
}
|
||||
|
||||
private cleanup() {
|
||||
this.isDown = false;
|
||||
this.element.style.display = 'none';
|
||||
if (this.element.parentElement) {
|
||||
this.element.parentElement.removeChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
onPointerDown(event: PointerEvent) {
|
||||
if (!this.enabled || !event.ctrlKey || event.button !== 0) return;
|
||||
|
||||
this.isDown = true;
|
||||
this.onSelectStart(event);
|
||||
}
|
||||
|
||||
onPointerMove(event: PointerEvent) {
|
||||
if (!this.enabled || !this.isDown || !event.ctrlKey) return;
|
||||
|
||||
this.onSelectMove(event);
|
||||
}
|
||||
|
||||
onPointerUp() {
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.isDown = false;
|
||||
this.onSelectOver();
|
||||
}
|
||||
|
||||
onSelectStart(event: PointerEvent) {
|
||||
this.element.style.display = 'none';
|
||||
this.renderer.domElement.parentElement?.appendChild(this.element);
|
||||
|
||||
this.element.style.left = `${event.clientX}px`;
|
||||
this.element.style.top = `${event.clientY}px`;
|
||||
this.element.style.width = '0px';
|
||||
this.element.style.height = '0px';
|
||||
|
||||
this.startPoint.x = event.clientX;
|
||||
this.startPoint.y = event.clientY;
|
||||
}
|
||||
|
||||
onSelectMove(event: PointerEvent) {
|
||||
if (!this.isDown) return;
|
||||
|
||||
this.element.style.display = 'block';
|
||||
|
||||
this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX);
|
||||
this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY);
|
||||
this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX);
|
||||
this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY);
|
||||
|
||||
this.element.style.left = `${this.pointTopLeft.x}px`;
|
||||
this.element.style.top = `${this.pointTopLeft.y}px`;
|
||||
this.element.style.width = `${this.pointBottomRight.x - this.pointTopLeft.x}px`;
|
||||
this.element.style.height = `${this.pointBottomRight.y - this.pointTopLeft.y}px`;
|
||||
}
|
||||
|
||||
onSelectOver() {
|
||||
this.element.style.display = 'none';
|
||||
if (this.element.parentElement) {
|
||||
this.element.parentElement.removeChild(this.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { SelectionHelper };
|
||||
@@ -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';
|
||||
|
||||
export default function Sun() {
|
||||
const savedTheme: string | null = localStorage.getItem("theme");
|
||||
const { elevation, setElevation } = useElevation();
|
||||
const { sunPosition, setSunPosition } = useSunPosition();
|
||||
const { azimuth, setAzimuth } = useAzimuth();
|
||||
@@ -28,7 +29,7 @@ export default function Sun() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{(azimuth !== undefined && elevation !== undefined) && (
|
||||
{(azimuth !== undefined && elevation !== undefined && savedTheme !== "dark") && (
|
||||
<>
|
||||
<Sky
|
||||
distance={CONSTANTS.skyConfig.skyDistance}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { EffectComposer, N8AO, Outline } from "@react-three/postprocessing";
|
||||
import { BlendFunction } from "postprocessing";
|
||||
import {
|
||||
useDeletableFloorItem,
|
||||
useSelectedActionSphere,
|
||||
useSelectedPath,
|
||||
useSelectedWallItem,
|
||||
useSelectedFloorItem,
|
||||
} from "../../../store/store";
|
||||
@@ -16,8 +14,6 @@ export default function PostProcessing() {
|
||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { selectedActionSphere } = useSelectedActionSphere();
|
||||
const { selectedPath } = useSelectedPath();
|
||||
|
||||
function flattenChildren(children: any[]) {
|
||||
const allChildren: any[] = [];
|
||||
@@ -89,36 +85,6 @@ export default function PostProcessing() {
|
||||
xRay={true}
|
||||
/>
|
||||
)}
|
||||
{selectedActionSphere && (
|
||||
<Outline
|
||||
selection={[selectedActionSphere.points]}
|
||||
selectionLayer={10}
|
||||
width={1000}
|
||||
blendFunction={BlendFunction.ALPHA}
|
||||
edgeStrength={10}
|
||||
resolutionScale={2}
|
||||
pulseSpeed={0}
|
||||
visibleEdgeColor={0x6f42c1}
|
||||
hiddenEdgeColor={0x6f42c1}
|
||||
blur={true}
|
||||
xRay={true}
|
||||
/>
|
||||
)}
|
||||
{selectedPath && (
|
||||
<Outline
|
||||
selection={flattenChildren(selectedPath.group.children)}
|
||||
selectionLayer={10}
|
||||
width={1000}
|
||||
blendFunction={BlendFunction.ALPHA}
|
||||
edgeStrength={10}
|
||||
resolutionScale={2}
|
||||
pulseSpeed={0}
|
||||
visibleEdgeColor={0x6f42c1}
|
||||
hiddenEdgeColor={0x6f42c1}
|
||||
blur={true}
|
||||
xRay={true}
|
||||
/>
|
||||
)}
|
||||
</EffectComposer>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,63 +1,36 @@
|
||||
import { useMemo } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { Environment, KeyboardControls, Stars } from "@react-three/drei";
|
||||
import { KeyboardControls } from "@react-three/drei";
|
||||
|
||||
import World from "./world/world";
|
||||
import Controls from "./controls/controls";
|
||||
import TransformControl from "./controls/transformControls";
|
||||
import PostProcessing from "./postProcessing/postProcessing";
|
||||
import Sun from "./environment/sky";
|
||||
import CamModelsGroup from "../collaboration/collabCams";
|
||||
import Shadows from "./environment/shadow";
|
||||
import MqttEvents from "../../services/factoryBuilder/mqtt/mqttEvents";
|
||||
|
||||
import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr";
|
||||
import SelectionControls from "./controls/selection/selectionControls";
|
||||
import MeasurementTool from "./tools/measurementTool";
|
||||
import Builder from "../builder/builder";
|
||||
import Visualization from "../visualization/visualization";
|
||||
import Setup from "./setup/setup";
|
||||
import Simulation from "../simulation/simulation";
|
||||
|
||||
// import Simulation from "./simulationtemp/simulation";
|
||||
import ZoneCentreTarget from "../visualization/functions/zoneCameraTarget";
|
||||
import Dropped3dWidgets from "../../modules/visualization/widgets/3d/Dropped3dWidget";
|
||||
import ZoneAssets from "../visualization/zoneAssets";
|
||||
import Collaboration from "../collaboration/collaboration";
|
||||
|
||||
export default function Scene() {
|
||||
const map = useMemo(
|
||||
() => [
|
||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
||||
{ name: "right", keys: ["ArrowRight", "d", "D"] },
|
||||
],
|
||||
[]
|
||||
);
|
||||
const savedTheme: string | null = localStorage.getItem("theme");
|
||||
const map = useMemo(() => [
|
||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
||||
{ name: "right", keys: ["ArrowRight", "d", "D"] },],
|
||||
[]);
|
||||
|
||||
return (
|
||||
<KeyboardControls map={map}>
|
||||
<Canvas
|
||||
eventPrefix="client"
|
||||
gl={{ powerPreference: "high-performance", antialias: true }}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Dropped3dWidgets />
|
||||
<Controls />
|
||||
<TransformControl />
|
||||
<SelectionControls />
|
||||
<MeasurementTool />
|
||||
<World />
|
||||
<ZoneCentreTarget />
|
||||
<ZoneAssets />
|
||||
<Simulation />
|
||||
<PostProcessing />
|
||||
{savedTheme !== "dark" ? <Sun /> : <></>}
|
||||
<Shadows />
|
||||
<CamModelsGroup />
|
||||
<MqttEvents />
|
||||
<Environment files={background} environmentIntensity={1.5} />
|
||||
</Canvas>
|
||||
</KeyboardControls>
|
||||
);
|
||||
return (
|
||||
<KeyboardControls map={map}>
|
||||
<Canvas eventPrefix="client" gl={{ powerPreference: "high-performance", antialias: true }} onContextMenu={(e) => { e.preventDefault(); }}>
|
||||
|
||||
<Setup />
|
||||
|
||||
<Collaboration />
|
||||
|
||||
<Builder />
|
||||
|
||||
<Simulation />
|
||||
|
||||
<Visualization />
|
||||
|
||||
</Canvas>
|
||||
</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
|
||||
@@ -5,240 +5,225 @@ import { useToolMode } from "../../../store/store";
|
||||
import { Html } from "@react-three/drei";
|
||||
|
||||
const MeasurementTool = () => {
|
||||
const { gl, raycaster, pointer, camera, scene } = useThree();
|
||||
const { toolMode } = useToolMode();
|
||||
const { gl, raycaster, pointer, camera, scene } = useThree();
|
||||
const { toolMode } = useToolMode();
|
||||
|
||||
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
||||
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
|
||||
null
|
||||
);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const [startConePosition, setStartConePosition] =
|
||||
useState<THREE.Vector3 | null>(null);
|
||||
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
|
||||
null
|
||||
);
|
||||
const [startConeQuaternion, setStartConeQuaternion] = useState(
|
||||
new THREE.Quaternion()
|
||||
);
|
||||
const [endConeQuaternion, setEndConeQuaternion] = useState(
|
||||
new THREE.Quaternion()
|
||||
);
|
||||
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
|
||||
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
||||
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
|
||||
null
|
||||
);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const [startConePosition, setStartConePosition] =
|
||||
useState<THREE.Vector3 | null>(null);
|
||||
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
|
||||
null
|
||||
);
|
||||
const [startConeQuaternion, setStartConeQuaternion] = useState(
|
||||
new THREE.Quaternion()
|
||||
);
|
||||
const [endConeQuaternion, setEndConeQuaternion] = useState(
|
||||
new THREE.Quaternion()
|
||||
);
|
||||
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
|
||||
|
||||
const MIN_RADIUS = 0.001,
|
||||
MAX_RADIUS = 0.1;
|
||||
const MIN_CONE_RADIUS = 0.01,
|
||||
MAX_CONE_RADIUS = 0.4;
|
||||
const MIN_CONE_HEIGHT = 0.035,
|
||||
MAX_CONE_HEIGHT = 2.0;
|
||||
const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1;
|
||||
const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4;
|
||||
const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0;
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
let drag = false;
|
||||
let isLeftMouseDown = false;
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
let drag = false;
|
||||
let isLeftMouseDown = false;
|
||||
|
||||
const onMouseDown = () => {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
};
|
||||
const onMouseDown = () => {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
};
|
||||
|
||||
const onMouseUp = (evt: any) => {
|
||||
isLeftMouseDown = false;
|
||||
if (evt.button === 0 && !drag) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster
|
||||
.intersectObjects(scene.children, true)
|
||||
.filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.name.includes("agv-collider") &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
const onMouseUp = (evt: any) => {
|
||||
isLeftMouseDown = false;
|
||||
if (evt.button === 0 && !drag) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster
|
||||
.intersectObjects(scene.children, true)
|
||||
.filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.name.includes("agv-collider") &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const intersectionPoint = intersects[0].point.clone();
|
||||
if (points.length < 2) {
|
||||
setPoints([...points, intersectionPoint]);
|
||||
} else {
|
||||
setPoints([intersectionPoint]);
|
||||
}
|
||||
if (intersects.length > 0) {
|
||||
const intersectionPoint = intersects[0].point.clone();
|
||||
if (points.length < 2) {
|
||||
setPoints([...points, intersectionPoint]);
|
||||
} else {
|
||||
setPoints([intersectionPoint]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isLeftMouseDown) drag = true;
|
||||
};
|
||||
|
||||
const onContextMenu = (evt: any) => {
|
||||
evt.preventDefault();
|
||||
if (!drag) {
|
||||
evt.preventDefault();
|
||||
setPoints([]);
|
||||
setTubeGeometry(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (toolMode === "MeasurementScale") {
|
||||
canvasElement.addEventListener("pointerdown", onMouseDown);
|
||||
canvasElement.addEventListener("pointermove", onMouseMove);
|
||||
canvasElement.addEventListener("pointerup", onMouseUp);
|
||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||
} else {
|
||||
resetMeasurement();
|
||||
setPoints([]);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("pointerdown", onMouseDown);
|
||||
canvasElement.removeEventListener("pointermove", onMouseMove);
|
||||
canvasElement.removeEventListener("pointerup", onMouseUp);
|
||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||
};
|
||||
}, [toolMode, camera, raycaster, pointer, scene, points]);
|
||||
|
||||
useFrame(() => {
|
||||
if (points.length === 1) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster
|
||||
.intersectObjects(scene.children, true)
|
||||
.filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.name.includes("agv-collider") &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
updateMeasurement(points[0], intersects[0].point);
|
||||
}
|
||||
} else if (points.length === 2) {
|
||||
updateMeasurement(points[0], points[1]);
|
||||
} else {
|
||||
resetMeasurement();
|
||||
}
|
||||
});
|
||||
|
||||
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||
const distance = start.distanceTo(end);
|
||||
|
||||
const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS);
|
||||
const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS);
|
||||
const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT);
|
||||
|
||||
setConeSize({ radius: coneRadius, height: coneHeight });
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
|
||||
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
|
||||
|
||||
let tubeStart = start.clone().add(offset);
|
||||
let tubeEnd = end.clone().sub(offset);
|
||||
|
||||
tubeStart.y = Math.max(tubeStart.y, 0);
|
||||
tubeEnd.y = Math.max(tubeEnd.y, 0);
|
||||
|
||||
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
|
||||
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
|
||||
|
||||
setStartConePosition(tubeStart);
|
||||
setEndConePosition(tubeEnd);
|
||||
setStartConeQuaternion(getArrowOrientation(start, end));
|
||||
setEndConeQuaternion(getArrowOrientation(end, start));
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isLeftMouseDown) drag = true;
|
||||
};
|
||||
|
||||
const onContextMenu = (evt: any) => {
|
||||
evt.preventDefault();
|
||||
if (!drag) {
|
||||
evt.preventDefault();
|
||||
setPoints([]);
|
||||
const resetMeasurement = () => {
|
||||
setTubeGeometry(null);
|
||||
}
|
||||
setStartConePosition(null);
|
||||
setEndConePosition(null);
|
||||
};
|
||||
|
||||
if (toolMode === "MeasurementScale") {
|
||||
canvasElement.addEventListener("pointerdown", onMouseDown);
|
||||
canvasElement.addEventListener("pointermove", onMouseMove);
|
||||
canvasElement.addEventListener("pointerup", onMouseUp);
|
||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||
} else {
|
||||
resetMeasurement();
|
||||
setPoints([]);
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("pointerdown", onMouseDown);
|
||||
canvasElement.removeEventListener("pointermove", onMouseMove);
|
||||
canvasElement.removeEventListener("pointerup", onMouseUp);
|
||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||
const direction = new THREE.Vector3()
|
||||
.subVectors(end, start)
|
||||
.normalize()
|
||||
.negate();
|
||||
const quaternion = new THREE.Quaternion();
|
||||
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
||||
return quaternion;
|
||||
};
|
||||
}, [toolMode, camera, raycaster, pointer, scene, points]);
|
||||
|
||||
useFrame(() => {
|
||||
if (points.length === 1) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster
|
||||
.intersectObjects(scene.children, true)
|
||||
.filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.name.includes("agv-collider") &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
useEffect(() => {
|
||||
if (points.length === 2) {
|
||||
// console.log(points[0].distanceTo(points[1]));
|
||||
}
|
||||
}, [points]);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
updateMeasurement(points[0], intersects[0].point);
|
||||
}
|
||||
} else if (points.length === 2) {
|
||||
updateMeasurement(points[0], points[1]);
|
||||
} else {
|
||||
resetMeasurement();
|
||||
}
|
||||
});
|
||||
return (
|
||||
<group ref={groupRef} name="MeasurementGroup">
|
||||
{startConePosition && (
|
||||
<mesh
|
||||
name="MeasurementReference"
|
||||
position={startConePosition}
|
||||
quaternion={startConeQuaternion}
|
||||
>
|
||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||
<meshBasicMaterial color="yellow" />
|
||||
</mesh>
|
||||
)}
|
||||
{endConePosition && (
|
||||
<mesh
|
||||
name="MeasurementReference"
|
||||
position={endConePosition}
|
||||
quaternion={endConeQuaternion}
|
||||
>
|
||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||
<meshBasicMaterial color="yellow" />
|
||||
</mesh>
|
||||
)}
|
||||
{tubeGeometry && (
|
||||
<mesh name="MeasurementReference" geometry={tubeGeometry}>
|
||||
<meshBasicMaterial color="yellow" />
|
||||
</mesh>
|
||||
)}
|
||||
|
||||
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||
const distance = start.distanceTo(end);
|
||||
|
||||
const radius = THREE.MathUtils.clamp(
|
||||
distance * 0.02,
|
||||
MIN_RADIUS,
|
||||
MAX_RADIUS
|
||||
{startConePosition && endConePosition && (
|
||||
<Html
|
||||
scale={THREE.MathUtils.clamp(
|
||||
startConePosition.distanceTo(endConePosition) * 0.25,
|
||||
0,
|
||||
10
|
||||
)}
|
||||
position={[
|
||||
(startConePosition.x + endConePosition.x) / 2,
|
||||
(startConePosition.y + endConePosition.y) / 2,
|
||||
(startConePosition.z + endConePosition.z) / 2,
|
||||
]}
|
||||
// class
|
||||
wrapperClass="distance-text-wrapper"
|
||||
className="distance-text"
|
||||
// other
|
||||
zIndexRange={[1, 0]}
|
||||
prepend
|
||||
sprite
|
||||
>
|
||||
<div>
|
||||
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
|
||||
</div>
|
||||
</Html>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
const coneRadius = THREE.MathUtils.clamp(
|
||||
distance * 0.05,
|
||||
MIN_CONE_RADIUS,
|
||||
MAX_CONE_RADIUS
|
||||
);
|
||||
const coneHeight = THREE.MathUtils.clamp(
|
||||
distance * 0.2,
|
||||
MIN_CONE_HEIGHT,
|
||||
MAX_CONE_HEIGHT
|
||||
);
|
||||
|
||||
setConeSize({ radius: coneRadius, height: coneHeight });
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
|
||||
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
|
||||
|
||||
let tubeStart = start.clone().add(offset);
|
||||
let tubeEnd = end.clone().sub(offset);
|
||||
|
||||
tubeStart.y = Math.max(tubeStart.y, 0);
|
||||
tubeEnd.y = Math.max(tubeEnd.y, 0);
|
||||
|
||||
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
|
||||
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
|
||||
|
||||
setStartConePosition(tubeStart);
|
||||
setEndConePosition(tubeEnd);
|
||||
setStartConeQuaternion(getArrowOrientation(start, end));
|
||||
setEndConeQuaternion(getArrowOrientation(end, start));
|
||||
};
|
||||
|
||||
const resetMeasurement = () => {
|
||||
setTubeGeometry(null);
|
||||
setStartConePosition(null);
|
||||
setEndConePosition(null);
|
||||
};
|
||||
|
||||
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||
const direction = new THREE.Vector3()
|
||||
.subVectors(end, start)
|
||||
.normalize()
|
||||
.negate();
|
||||
const quaternion = new THREE.Quaternion();
|
||||
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
||||
return quaternion;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (points.length === 2) {
|
||||
console.log(points[0].distanceTo(points[1]));
|
||||
}
|
||||
}, [points]);
|
||||
|
||||
return (
|
||||
<group ref={groupRef} name="MeasurementGroup">
|
||||
{startConePosition && (
|
||||
<mesh
|
||||
name="MeasurementReference"
|
||||
position={startConePosition}
|
||||
quaternion={startConeQuaternion}
|
||||
>
|
||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||
<meshBasicMaterial color="yellow" />
|
||||
</mesh>
|
||||
)}
|
||||
{endConePosition && (
|
||||
<mesh
|
||||
name="MeasurementReference"
|
||||
position={endConePosition}
|
||||
quaternion={endConeQuaternion}
|
||||
>
|
||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||
<meshBasicMaterial color="yellow" />
|
||||
</mesh>
|
||||
)}
|
||||
{tubeGeometry && (
|
||||
<mesh name="MeasurementReference" geometry={tubeGeometry}>
|
||||
<meshBasicMaterial color="yellow" />
|
||||
</mesh>
|
||||
)}
|
||||
|
||||
{startConePosition && endConePosition && (
|
||||
<Html
|
||||
scale={THREE.MathUtils.clamp(
|
||||
startConePosition.distanceTo(endConePosition) * 0.25,
|
||||
0,
|
||||
10
|
||||
)}
|
||||
position={[
|
||||
(startConePosition.x + endConePosition.x) / 2,
|
||||
(startConePosition.y + endConePosition.y) / 2,
|
||||
(startConePosition.z + endConePosition.z) / 2,
|
||||
]}
|
||||
// class
|
||||
wrapperClass="distance-text-wrapper"
|
||||
className="distance-text"
|
||||
// other
|
||||
zIndexRange={[1, 0]}
|
||||
prepend
|
||||
sprite
|
||||
>
|
||||
<div>
|
||||
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
|
||||
</div>
|
||||
</Html>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
export default MeasurementTool;
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
////////// Three and React Three Fiber Imports //////////
|
||||
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
|
||||
////////// Component Imports //////////
|
||||
|
||||
import DistanceText from "../../builder/geomentries/lines/distanceText/distanceText";
|
||||
import ReferenceDistanceText from "../../builder/geomentries/lines/distanceText/referenceDistanceText";
|
||||
|
||||
////////// Assests Imports //////////
|
||||
|
||||
import arch from "../../../assets/gltf-glb/arch.glb";
|
||||
import door from "../../../assets/gltf-glb/door.glb";
|
||||
import Window from "../../../assets/gltf-glb/window.glb";
|
||||
|
||||
////////// Zustand State Imports //////////
|
||||
|
||||
import {
|
||||
useToggleView,
|
||||
useDeletePointOrLine,
|
||||
useMovePoint,
|
||||
useActiveLayer,
|
||||
useSocketStore,
|
||||
useWallVisibility,
|
||||
useRoofVisibility,
|
||||
useShadows,
|
||||
useUpdateScene,
|
||||
useWalls,
|
||||
useToolMode,
|
||||
useRefTextUpdate,
|
||||
useRenderDistance,
|
||||
useLimitDistance,
|
||||
} from "../../../store/store";
|
||||
|
||||
////////// 3D Function Imports //////////
|
||||
|
||||
import loadWalls from "../../builder/geomentries/walls/loadWalls";
|
||||
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
import SocketResponses from "../../collaboration/socketResponses.dev";
|
||||
import FloorItemsGroup from "../../builder/groups/floorItemsGroup";
|
||||
import FloorPlanGroup from "../../builder/groups/floorPlanGroup";
|
||||
import FloorGroup from "../../builder/groups/floorGroup";
|
||||
import FloorGroupAilse from "../../builder/groups/floorGroupAisle";
|
||||
import Draw from "../../builder/functions/draw";
|
||||
import WallsAndWallItems from "../../builder/groups/wallsAndWallItems";
|
||||
import Ground from "../environment/ground";
|
||||
// import ZoneGroup from "../groups/zoneGroup1";
|
||||
import { findEnvironment } from "../../../services/factoryBuilder/environment/findEnvironment";
|
||||
import Layer2DVisibility from "../../builder/geomentries/layers/layer2DVisibility";
|
||||
import DrieHtmlTemp from "../mqttTemp/drieHtmlTemp";
|
||||
import ZoneGroup from "../../builder/groups/zoneGroup";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import NavMeshCreator from "../../builder/agv/navMeshCreator";
|
||||
|
||||
export default function World() {
|
||||
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
||||
const csg = useRef(); // Reference for CSG object, used for 3D modeling.
|
||||
const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
|
||||
const scene = useRef() as Types.RefScene; // Reference to the scene.
|
||||
const camera = useRef() as Types.RefCamera; // Reference to the camera object.
|
||||
const controls = useRef<any>(); // Reference to the controls object.
|
||||
const raycaster = useRef() as Types.RefRaycaster; // Reference for raycaster used for detecting objects being pointed at in the scene.
|
||||
const dragPointControls = useRef() as Types.RefDragControl; // Reference for drag point controls, an array for drag control.
|
||||
|
||||
// Assigning the scene and camera from the Three.js state to the references.
|
||||
|
||||
scene.current = state.scene;
|
||||
camera.current = state.camera;
|
||||
controls.current = state.controls;
|
||||
raycaster.current = state.raycaster;
|
||||
|
||||
const plane = useRef<THREE.Mesh>(null); // Reference for a plane object for raycaster reference.
|
||||
const grid = useRef() as any; // Reference for a grid object for raycaster reference.
|
||||
const snappedPoint = useRef() as Types.RefVector3; // Reference for storing a snapped point at the (end = isSnapped) and (start = ispreSnapped) of the line.
|
||||
const isSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (end).
|
||||
const anglesnappedPoint = useRef() as Types.RefVector3; // Reference for storing an angle-snapped point when the line is in 90 degree etc...
|
||||
const isAngleSnapped = useRef(false) as Types.RefBoolean; // Boolean to indicate if angle snapping is active.
|
||||
const isSnappedUUID = useRef() as Types.RefString; // UUID reference to identify the snapped point.
|
||||
const ispreSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (start).
|
||||
const tempLoader = useRef() as Types.RefMesh; // Reference for a temporary loader for the floor items.
|
||||
const isTempLoader = useRef() as Types.RefBoolean; // Reference to check if a temporary loader is active.
|
||||
const Tube = useRef() as Types.RefTubeGeometry; // Reference for tubes used for reference line creation and updation.
|
||||
const line = useRef([]) as Types.RefLine; // Reference for line which stores the current line that is being drawn.
|
||||
const lines = useRef([]) as Types.RefLines; // Reference for lines which stores all the lines that are ever drawn.
|
||||
const onlyFloorline = useRef<Types.OnlyFloorLine>([]); // Reference for floor lines which does not have walls or roof and have only floor used to store the current line that is being drawn.
|
||||
const onlyFloorlines = useRef<Types.OnlyFloorLines>([]); // Reference for all the floor lines that are ever drawn.
|
||||
const ReferenceLineMesh = useRef() as Types.RefMesh; // Reference for storing the mesh of the reference line for moving it during draw.
|
||||
const LineCreated = useRef(false) as Types.RefBoolean; // Boolean to track whether the reference line is created or not.
|
||||
const referencePole = useRef() as Types.RefMesh; // Reference for a pole that is used as the reference for the user to show where it is placed.
|
||||
const itemsGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the floor items (Gltf).
|
||||
const floorGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the roofs and the floors.
|
||||
const AttachedObject = useRef() as Types.RefMesh; // Reference for an object that is attached using dbl click for transform controls rotation.
|
||||
const floorPlanGroup = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines group and the points group.
|
||||
const floorPlanGroupLine = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines that are drawn.
|
||||
const floorPlanGroupPoint = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the points that are created.
|
||||
const floorGroupAisle = useRef() as Types.RefGroup;
|
||||
const zoneGroup = useRef() as Types.RefGroup;
|
||||
const currentLayerPoint = useRef([]) as Types.RefMeshArray; // Reference for points that re in the current layer used to update the points in drag controls.
|
||||
const hoveredDeletablePoint = useRef() as Types.RefMesh; // Reference for the currently hovered point that can be deleted.
|
||||
const hoveredDeletableLine = useRef() as Types.RefMesh; // Reference for the currently hovered line that can be deleted.
|
||||
const hoveredDeletableFloorItem = useRef() as Types.RefMesh; // Reference for the currently hovered floor item that can be deleted.
|
||||
const hoveredDeletableWallItem = useRef() as Types.RefMesh; // Reference for the currently hovered wall item that can be deleted.
|
||||
const hoveredDeletablePillar = useRef() as Types.RefMesh; // Reference for the currently hovered pillar that can be deleted.
|
||||
const currentWallItem = useRef() as Types.RefMesh; // Reference for the currently selected wall item that can be scaled, dragged etc...
|
||||
|
||||
const cursorPosition = new THREE.Vector3(); // 3D vector for storing the cursor position.
|
||||
|
||||
const [selectedItemsIndex, setSelectedItemsIndex] = useState<Types.Number | null>(null); // State for tracking the index of the selected item.
|
||||
const { activeLayer, setActiveLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
|
||||
const { toggleView, setToggleView } = useToggleView(); // State for toggling between 2D and 3D.
|
||||
const { toolMode, setToolMode } = useToolMode();
|
||||
const { movePoint, setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
|
||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { socket } = useSocketStore();
|
||||
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
|
||||
const { wallVisibility, setWallVisibility } = useWallVisibility();
|
||||
const { shadows, setShadows } = useShadows();
|
||||
const { renderDistance, setRenderDistance } = useRenderDistance();
|
||||
const { limitDistance, setLimitDistance } = useLimitDistance();
|
||||
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||
const { walls, setWalls } = useWalls();
|
||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
// const loader = new GLTFLoader();
|
||||
// const dracoLoader = new DRACOLoader();
|
||||
|
||||
// dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
// loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
////////// Assest Configuration Values //////////
|
||||
|
||||
const AssetConfigurations: Types.AssetConfigurations = {
|
||||
arch: {
|
||||
modelUrl: arch,
|
||||
scale: [0.75, 0.75, 0.75],
|
||||
csgscale: [2, 4, 0.5],
|
||||
csgposition: [0, 2, 0],
|
||||
positionY: () => 0,
|
||||
type: "Fixed-Move",
|
||||
},
|
||||
door: {
|
||||
modelUrl: door,
|
||||
scale: [0.75, 0.75, 0.75],
|
||||
csgscale: [2, 4, 0.5],
|
||||
csgposition: [0, 2, 0],
|
||||
positionY: () => 0,
|
||||
type: "Fixed-Move",
|
||||
},
|
||||
window: {
|
||||
modelUrl: Window,
|
||||
scale: [0.75, 0.75, 0.75],
|
||||
csgscale: [5, 3, 0.5],
|
||||
csgposition: [0, 1.5, 0],
|
||||
positionY: (intersectionPoint) => intersectionPoint.point.y,
|
||||
type: "Free-Move",
|
||||
},
|
||||
};
|
||||
|
||||
////////// All Toggle's //////////
|
||||
|
||||
useEffect(() => {
|
||||
setRefTextUpdate((prevUpdate: number) => prevUpdate - 1);
|
||||
if (dragPointControls.current) {
|
||||
dragPointControls.current.enabled = false;
|
||||
}
|
||||
if (toggleView) {
|
||||
Layer2DVisibility(
|
||||
activeLayer,
|
||||
floorPlanGroup,
|
||||
floorPlanGroupLine,
|
||||
floorPlanGroupPoint,
|
||||
currentLayerPoint,
|
||||
dragPointControls
|
||||
);
|
||||
} else {
|
||||
setToolMode(null);
|
||||
setDeletePointOrLine(false);
|
||||
setMovePoint(false);
|
||||
loadWalls(lines, setWalls);
|
||||
setUpdateScene(true);
|
||||
line.current = [];
|
||||
}
|
||||
}, [toggleView]);
|
||||
|
||||
useEffect(() => {
|
||||
THREE.Cache.clear();
|
||||
THREE.Cache.enabled = true;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
async function fetchVisibility() {
|
||||
const visibility = await findEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!
|
||||
);
|
||||
if (visibility) {
|
||||
setRoofVisibility(visibility.roofVisibility);
|
||||
setWallVisibility(visibility.wallVisibility);
|
||||
setShadows(visibility.shadowVisibility);
|
||||
setRenderDistance(visibility.renderDistance);
|
||||
setLimitDistance(visibility.limitDistance);
|
||||
}
|
||||
}
|
||||
fetchVisibility();
|
||||
}, []);
|
||||
|
||||
////////// UseFrame is Here //////////
|
||||
|
||||
useFrame(() => {
|
||||
if (toolMode) {
|
||||
Draw(
|
||||
state,
|
||||
plane,
|
||||
cursorPosition,
|
||||
floorPlanGroupPoint,
|
||||
floorPlanGroupLine,
|
||||
snappedPoint,
|
||||
isSnapped,
|
||||
isSnappedUUID,
|
||||
line,
|
||||
lines,
|
||||
ispreSnapped,
|
||||
floorPlanGroup,
|
||||
ReferenceLineMesh,
|
||||
LineCreated,
|
||||
setRefTextUpdate,
|
||||
Tube,
|
||||
anglesnappedPoint,
|
||||
isAngleSnapped,
|
||||
toolMode
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
////////// Return //////////
|
||||
|
||||
return (
|
||||
<>
|
||||
<Ground grid={grid} plane={plane} />
|
||||
|
||||
<DistanceText key={toggleView} />
|
||||
|
||||
<ReferenceDistanceText
|
||||
key={refTextupdate}
|
||||
line={ReferenceLineMesh.current}
|
||||
/>
|
||||
|
||||
<SocketResponses
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
lines={lines}
|
||||
floorGroup={floorGroup}
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
scene={scene}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
AssetConfigurations={AssetConfigurations}
|
||||
itemsGroup={itemsGroup}
|
||||
isTempLoader={isTempLoader}
|
||||
tempLoader={tempLoader}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
zoneGroup={zoneGroup}
|
||||
dragPointControls={dragPointControls}
|
||||
/>
|
||||
|
||||
<WallsAndWallItems
|
||||
CSGGroup={CSGGroup}
|
||||
AssetConfigurations={AssetConfigurations}
|
||||
setSelectedItemsIndex={setSelectedItemsIndex}
|
||||
selectedItemsIndex={selectedItemsIndex}
|
||||
currentWallItem={currentWallItem}
|
||||
csg={csg}
|
||||
lines={lines}
|
||||
hoveredDeletableWallItem={hoveredDeletableWallItem}
|
||||
/>
|
||||
|
||||
<FloorItemsGroup
|
||||
itemsGroup={itemsGroup}
|
||||
hoveredDeletableFloorItem={hoveredDeletableFloorItem}
|
||||
AttachedObject={AttachedObject}
|
||||
floorGroup={floorGroup}
|
||||
tempLoader={tempLoader}
|
||||
isTempLoader={isTempLoader}
|
||||
plane={plane}
|
||||
/>
|
||||
|
||||
<FloorGroup
|
||||
floorGroup={floorGroup}
|
||||
lines={lines}
|
||||
referencePole={referencePole}
|
||||
hoveredDeletablePillar={hoveredDeletablePillar}
|
||||
/>
|
||||
|
||||
<FloorPlanGroup
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorGroup={floorGroup}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
hoveredDeletablePoint={hoveredDeletablePoint}
|
||||
hoveredDeletableLine={hoveredDeletableLine}
|
||||
plane={plane}
|
||||
line={line}
|
||||
lines={lines}
|
||||
onlyFloorline={onlyFloorline}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
|
||||
{/* <ZoneGroup
|
||||
zoneGroup={zoneGroup}
|
||||
plane={plane}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
line={line}
|
||||
lines={lines}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/> */}
|
||||
|
||||
<ZoneGroup />
|
||||
|
||||
<FloorGroupAilse
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
plane={plane}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
line={line}
|
||||
lines={lines}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
|
||||
{/* <DrieHtmlTemp itemsGroup={itemsGroup} /> */}
|
||||
|
||||
<NavMeshCreator lines={lines} />
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
14
app/src/modules/simulation/conveyor/conveyor.tsx
Normal file
14
app/src/modules/simulation/conveyor/conveyor.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import ConveyorInstances from './instances/conveyorInstances'
|
||||
|
||||
function Conveyor() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<ConveyorInstances />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Conveyor
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
function ConveyorInstance() {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConveyorInstance
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import ConveyorInstance from './conveyorInstance/conveyorInstance'
|
||||
|
||||
function ConveyorInstances() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<ConveyorInstance />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConveyorInstances
|
||||
@@ -0,0 +1,148 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useEventsStore } from '../../../../../store/simulation/useEventsStore';
|
||||
import useModuleStore from '../../../../../store/useModuleStore';
|
||||
import { TransformControls } from '@react-three/drei';
|
||||
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
|
||||
|
||||
function PointsCreator() {
|
||||
const { events, updatePoint, getPointByUuid } = useEventsStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const transformRef = useRef<any>(null);
|
||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||
const [selectedPoint, setSelectedPoint] = useState<THREE.Mesh | null>(null);
|
||||
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
|
||||
|
||||
useEffect(() => {
|
||||
setTransformMode(null);
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(e);
|
||||
if (!selectedPoint) 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);
|
||||
}, [selectedPoint]);
|
||||
|
||||
const updatePointToState = (selectedPoint: THREE.Mesh) => {
|
||||
let point = JSON.parse(JSON.stringify(getPointByUuid(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid)));
|
||||
if (point) {
|
||||
point.position = [selectedPoint.position.x, selectedPoint.position.y, selectedPoint.position.z];
|
||||
updatePoint(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid, point)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeModule === 'simulation' &&
|
||||
<>
|
||||
<group name='EventPointsGroup' >
|
||||
{events.map((event, i) => {
|
||||
if (event.type === 'transfer') {
|
||||
return (
|
||||
<group key={i} position={new THREE.Vector3(...event.position)}>
|
||||
{event.points.map((point, j) => (
|
||||
<mesh
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
}}
|
||||
key={`${i}-${j}`}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === 'vehicle') {
|
||||
return (
|
||||
<group key={i} position={new THREE.Vector3(...event.position)}>
|
||||
<mesh
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[event.point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="blue" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === 'roboticArm') {
|
||||
return (
|
||||
<group key={i} position={new THREE.Vector3(...event.position)}>
|
||||
<mesh
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[event.point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="green" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (event.type === 'machine') {
|
||||
return (
|
||||
<group key={i} position={new THREE.Vector3(...event.position)}>
|
||||
<mesh
|
||||
uuid={event.point.uuid}
|
||||
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedPoint(sphereRefs.current[event.point.uuid]);
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
setSelectedPoint(null);
|
||||
}}
|
||||
position={new THREE.Vector3(...event.point.position)}
|
||||
userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="purple" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</group>
|
||||
{(selectedPoint && transformMode) &&
|
||||
<TransformControls ref={transformRef} object={selectedPoint} mode={transformMode} onMouseUp={(e) => { updatePointToState(selectedPoint) }} />
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PointsCreator;
|
||||
12
app/src/modules/simulation/events/points/points.tsx
Normal file
12
app/src/modules/simulation/events/points/points.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import PointsCreator from './creator/pointsCreator'
|
||||
|
||||
function Points() {
|
||||
return (
|
||||
<>
|
||||
<PointsCreator />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Points
|
||||
47
app/src/modules/simulation/events/points/pointsCalculator.ts
Normal file
47
app/src/modules/simulation/events/points/pointsCalculator.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as THREE from 'three';
|
||||
import { Group } from '../../../../types/world/worldTypes';
|
||||
|
||||
function PointsCalculator(
|
||||
type: string,
|
||||
model: Group,
|
||||
rotation: THREE.Vector3 = new THREE.Vector3()
|
||||
): { points?: THREE.Vector3[] } | null {
|
||||
if (!model) return null;
|
||||
|
||||
const box = new THREE.Box3().setFromObject(model);
|
||||
|
||||
const size = new THREE.Vector3();
|
||||
box.getSize(size);
|
||||
const center = new THREE.Vector3();
|
||||
box.getCenter(center);
|
||||
|
||||
const rotationMatrix = new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(rotation.x, rotation.y, rotation.z));
|
||||
|
||||
const localTopMiddle = new THREE.Vector3(0, size.y / 2, 0);
|
||||
const worldTopMiddle = localTopMiddle.clone().applyMatrix4(rotationMatrix).add(center);
|
||||
|
||||
if (type === 'Conveyor') {
|
||||
const isWidthLonger = size.x > size.z;
|
||||
const longerSize = isWidthLonger ? size.x : size.z;
|
||||
const shorterSize = isWidthLonger ? size.z : size.x;
|
||||
const halfLongerSize = longerSize / 2;
|
||||
const halfShorterSize = shorterSize / 2;
|
||||
|
||||
const localEndPoint1 = new THREE.Vector3(isWidthLonger ? -halfLongerSize + halfShorterSize : 0, size.y / 2, isWidthLonger ? 0 : -halfLongerSize + halfShorterSize);
|
||||
|
||||
const localEndPoint2 = new THREE.Vector3(isWidthLonger ? halfLongerSize - halfShorterSize : 0, size.y / 2, isWidthLonger ? 0 : halfLongerSize - halfShorterSize);
|
||||
|
||||
const worldEndPoint1 = localEndPoint1.applyMatrix4(rotationMatrix).add(center);
|
||||
const worldEndPoint2 = localEndPoint2.applyMatrix4(rotationMatrix).add(center);
|
||||
|
||||
return {
|
||||
points: [worldEndPoint1, worldTopMiddle, worldEndPoint2]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
points: [worldTopMiddle]
|
||||
};
|
||||
}
|
||||
|
||||
export default PointsCalculator;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
function MaterialAnimator() {
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialAnimator
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
function MaterialInstance() {
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialInstance
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
import MaterialInstance from './instance/materialInstance'
|
||||
import MaterialAnimator from './animator/materialAnimator'
|
||||
|
||||
function MaterialInstances() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<MaterialInstance />
|
||||
|
||||
<MaterialAnimator />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialInstances
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user