added donut and pole area chart and added its iot data

This commit is contained in:
2025-03-31 19:22:37 +05:30
121 changed files with 4527 additions and 1424 deletions

View File

@@ -1,24 +1,17 @@
import PolygonGenerator from "./polygonGenerator";
import { useThree } from "@react-three/fiber";
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import * as THREE from "three";
import * as Types from "../../../types/world/worldTypes";
import PathNavigator from "./pathNavigator";
import NavMeshDetails from "./navMeshDetails";
const Agv = ({
lines,
plane,
}: {
lines: Types.RefLines;
plane: Types.RefMesh;
}) => {
let pathPoints = [
const Agv = ({ lines, plane }: { lines: Types.RefLines; plane: Types.RefMesh; }) => {
const pathPoints = useMemo(() => [
[
{ x: 8.477161935339709, y: 0, z: 17.41343083550102 },
{ x: 9.175416491482693, y: 0, z: -12.361001232663693 },
],
,
// [
// { x: 13.508213355232144, y: 0, z: -15.456970649652018 },
// { x: -30.464866520869617, y: 0, z: 9.779806557688929 },
@@ -27,7 +20,8 @@ const Agv = ({
{ x: 16.792040856420844, y: 0, z: 15.86281907549489 },
{ x: -42.77173264503395, y: 0, z: -15.821322764400804 },
],
];
], []);
let groupRef = useRef() as Types.RefGroup;
const [navMesh, setNavMesh] = useState();

View File

@@ -17,7 +17,7 @@ async function Draw(
floorPlanGroup: Types.RefGroup,
ReferenceLineMesh: Types.RefMesh,
LineCreated: Types.RefBoolean,
setRefTextUpdate: Types.NumberIncrementState,
setRefTextUpdate: any,
Tube: Types.RefTubeGeometry,
anglesnappedPoint: Types.RefVector3,
isAngleSnapped: Types.RefBoolean,

View File

@@ -17,7 +17,7 @@ function addRoofToScene(
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.roofConfig.defaultColor, side: THREE.DoubleSide, transparent: true, depthWrite: false });
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.roofConfig.defaultColor, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = CONSTANTS.wallConfig.height + floor;
mesh.castShadow = true;

View File

@@ -193,7 +193,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
}
const Mode = transformMode;
if (Mode !== null || activeTool === "Cursor") {
if (Mode !== null || activeTool === "cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
@@ -225,7 +225,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
const Mode = transformMode;
if (Mode !== null || activeTool === "Cursor") {
if (Mode !== null || activeTool === "cursor") {
if (!itemsGroup.current) return;
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {

View File

@@ -1,21 +1,20 @@
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 { Text, Html } from '@react-three/drei';
import CollabUserIcon from './collabUserIcon';
import image from '../../assets/image/userImage.png';
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";
const CamModelsGroup = () => {
let navigate = useNavigate();
const groupRef = useRef<THREE.Group>(null);
const email = localStorage.getItem('email');
const email = localStorage.getItem("email");
const { activeUsers, setActiveUsers } = useActiveUsers();
const { socket } = useSocketStore();
const loader = new GLTFLoader();
@@ -23,93 +22,147 @@ const CamModelsGroup = () => {
const [cams, setCams] = useState<any[]>([]);
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler }>>({});
dracoLoader.setDecoderPath('three/examples/jsm/libs/draco/gltf/');
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
useEffect(() => {
if (!email) {
navigate('/');
navigate("/");
}
if (!socket) return;
const organization = email!.split('@')[1].split('.')[0];
const organization = email!.split("@")[1].split(".")[0];
socket.on('userConnectRespones', (data: any) => {
socket.on("userConnectRespones", (data: any) => {
if (!groupRef.current) return;
if (data.data.userData.email === email) return
if (socket.id === data.socketId || organization !== data.organization) 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);
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.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) => [...prev, newModel]);
setActiveUsers([...activeUsers, data.data.userData]);
});
});
socket.on('userDisConnectRespones', (data: any) => {
socket.on("userDisConnectRespones", (data: any) => {
if (!groupRef.current) return;
if (socket.id === data.socketId || organization !== data.organization) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
setCams((prev) => prev.filter((cam) => cam.uuid !== data.data.userData._id));
setActiveUsers(activeUsers.filter((user: any) => user._id !== data.data.userData._id));
setCams((prev) =>
prev.filter((cam) => cam.uuid !== data.data.userData._id)
);
setActiveUsers(
activeUsers.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;
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),
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('userConnectRespones');
socket.off('userDisConnectRespones');
socket.off('cameraUpdateResponse');
socket.off("userConnectRespones");
socket.off("userDisConnectRespones");
socket.off("cameraUpdateResponse");
};
}, [socket, activeUsers]);
useFrame(() => {
if (!groupRef.current) return;
Object.keys(models).forEach((uuid) => {
const model = groupRef.current!.getObjectByProperty('uuid', 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);
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];
const organization = email!.split("@")[1].split(".")[0];
getActiveUsersData(organization).then((data) => {
const filteredData = data.cameraDatas.filter((camera: any) => camera.userData.email !== email);
const filteredData = data.cameraDatas.filter(
(camera: any) => camera.userData.email !== email
);
let a:any = [];
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.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;
setActiveUsers([...activeUsers, cam.userData]);
a.push(cam.userData);
return newModel;
});
setActiveUsers(a);
setCams((prev) => [...prev, ...newCams]);
});
}
@@ -119,7 +172,7 @@ const CamModelsGroup = () => {
return (
<group ref={groupRef} name="Cam-Model-Group">
{cams.map((cam, index) => (
<primitive key={index} object={cam} >
<primitive key={index} object={cam}>
<Html
as="div"
center
@@ -130,8 +183,14 @@ const CamModelsGroup = () => {
textAlign: "center",
fontFamily: "Arial, sans-serif",
}}
position={[-0.015, 0, 0.7]}>
<CollabUserIcon color={"#ff0000"} userImage={image} userName={cam.userData.userName} />
position={[-0.015, 0, 0.7]}
>
<CollabUserIcon
userImage={""}
userName={cam.userData.userName}
index={index}
color={getAvatarColor(index)}
/>
</Html>
</primitive>
))}

View File

@@ -1,53 +1,33 @@
import React from "react";
import CustomAvatar from "./users/Avatar";
interface CollabUserIconProps {
color: string;
userImage: string;
userName: string;
userName: string;
userImage?: string;
index?: number;
color: string;
}
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
color,
userImage,
userName,
userImage,
userName,
index = 0,
color,
}) => {
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
gap: "6px",
// transform:"translate(-20%, 0%)",
}}
>
<img
src={userImage}
alt={userName}
style={{
width: "30px",
height: "30px",
outline: `2px solid ${color}`,
borderRadius: "50%",
objectFit: 'cover'
}}
/>
<div
style={{
display: 'flex',
padding: "3px 5px",
backgroundColor: color,
borderRadius: "6px",
color: "white",
fontSize: "14px",
fontWeight: 400
}}
>
{userName}
</div>
</div>
);
return (
<div className="collab-user-live-container">
<div className="user-image-container">
{userImage ? (
<img className="user-image" src={userImage} alt={userName} />
) : (
<CustomAvatar name={userName} index={index} />
)}
</div>
<div className="user-name" style={{ backgroundColor: color }}>
{userName}
</div>
</div>
);
};
export default CollabUserIcon;

View File

@@ -0,0 +1,59 @@
import React, { useEffect, useState } from "react";
import { getInitials } from "./functions/getInitials";
import { getAvatarColor } from "./functions/getAvatarColor";
interface AvatarProps {
name: string; // Name can be a full name or initials
size?: number;
index?: number;
textColor?: string;
}
const CustomAvatar: React.FC<AvatarProps> = ({
name,
size = 100,
index = 0,
textColor = "#ffffff",
}) => {
const [imageSrc, setImageSrc] = useState<string | null>(null);
useEffect(() => {
const canvas = document.createElement("canvas"); // Create an offscreen canvas
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
if (ctx) {
const initials = getInitials(name); // Convert name to initials if needed
// Draw background
ctx.fillStyle = getAvatarColor(index);
ctx.fillRect(0, 0, size, size);
// Draw initials
ctx.fillStyle = textColor;
ctx.font = `bold ${size / 2}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(initials, size / 2, size / 2);
// Generate image source
const dataURL = canvas.toDataURL("image/png");
setImageSrc(dataURL);
}
}, [name, size, textColor]);
if (!imageSrc) {
return null; // Return null while the image is being generated
}
return (
<img
className="user-image"
src={imageSrc}
alt="User Avatar"
style={{ width: "100%", height: "100%" }}
/>
);
};
export default CustomAvatar;

View File

@@ -0,0 +1,26 @@
const avatarColors: string[] = [
"#FF5733", // Red Orange
"#48ac2a", // Leaf Green
"#0050eb", // Royal Blue
"#FF33A1", // Hot Pink
"#FF8C33", // Deep Orange
"#8C33FF", // Violet
"#FF3333", // Bright Red
"#43c06d", // Emerald Green
"#A133FF", // Amethyst Purple
"#C70039", // Crimson
"#900C3F", // Maroon
"#581845", // Plum
"#3498DB", // Sky Blue
"#2ECC71", // Green Mint
"#E74C3C", // Tomato Red
"#00adff", // Azure
"#DBAD05", // Amber Yellow
"#FF5733", // Red Orange
"#FF33A1", // Hot Pink
"#900C3F", // Maroon
];
export function getAvatarColor(index: number): string {
return avatarColors[index % avatarColors.length];
}

View File

@@ -0,0 +1,10 @@
export const getInitials = (fullName: string): string => {
// Extract initials from the name
const words = fullName.split(" ");
const initials = words
.map((word) => word[0])
.slice(0, 2)
.join("")
.toUpperCase();
return initials;
};

View File

@@ -9,10 +9,11 @@ import * as THREE from "three";
// Define the shape of the selected card
interface SelectedCard {
assetName: string;
uploadedOn: string;
uploadedOn: number;
price: number;
rating: number;
views: number;
description: string;
}
// Define the props type for AssetPreview
@@ -24,7 +25,7 @@ interface AssetPreviewProps {
function Ui() {
return (
<Text color="#6f42c1" anchorX="center" anchorY="middle" scale={0.3}>
Loading your model...
Loading preview...
</Text>
);
}
@@ -93,19 +94,7 @@ const AssetPreview: React.FC<AssetPreviewProps> = ({
<div className="asset-details">
<div className="asset-name">{selectedCard.assetName}</div>
<div className="asset-description">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Doloremque nisi beatae facilis architecto quaerat delectus velit
aliquid assumenda cumque vitae! Tempore quibusdam ab natus in
minima voluptates, aliquid corrupti excepturi consectetur
distinctio sequi beatae odit autem? Distinctio ab, voluptatem
omnis quibusdam, incidunt eum ipsa aliquid enim eaque eveniet nisi
autem, accusantium vel! Laborum in iste voluptates ad! Harum eum
amet pariatur fugit laudantium dolorem maxime voluptates atque
molestiae modi inventore quidem maiores dolore numquam, natus
quisquam optio distinctio eveniet aliquam, aut eligendi laboriosam
eaque! Porro cumque cum distinctio ullam debitis, dolorum
similique! Harum cupiditate perferendis voluptatum molestiae,
fugiat quisquam assumenda!
{`${selectedCard.assetName} is used in factories to improve efficiency and production speed It is designed to handle heavy workloads and perform repetitive tasks with precision. Many industries rely on this machine to manufacture products quickly and accurately. It reduces human effort and minimizes errors in the production process. Regular maintenance is required to keep the machine in good working condition.With advanced technology, this machine continues to enhance industrial operations and increase productivity.`}
</div>
<div className="asset-review">
<div className="asset-rating">

View File

@@ -12,17 +12,19 @@ import { getAssetDownload } from "../../services/marketplace/getAssetDownload";
interface CardProps {
assetName: string;
uploadedOn: string;
uploadedOn: number;
price: number;
rating: number;
views: number;
image: string;
description: string;
onSelectCard: (cardData: {
assetName: string;
uploadedOn: string;
uploadedOn: number;
price: number;
rating: number;
views: number;
description: string;
}) => void;
}
@@ -33,10 +35,11 @@ const Card: React.FC<CardProps> = ({
rating,
views,
image,
description,
onSelectCard,
}) => {
const handleCardSelect = () => {
onSelectCard({ assetName, uploadedOn, price, rating, views });
onSelectCard({ assetName, uploadedOn, price, rating, views, description });
};
return (

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import Card from "./Card";
import AssetPreview from "./AssetPreview";
import RenderOverlay from "../../components/templates/Overlay";
import { fetchAssets } from "../../services/marketplace/fetchAssets";
interface ModelData {
CreatedBy: string;
animated: string | null;
@@ -23,18 +22,20 @@ interface ModelsProps {
const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
const [selectedCard, setSelectedCard] = useState<{
assetName: string;
uploadedOn: string;
uploadedOn: number;
price: number;
rating: number;
views: number;
description: string;
} | null>(null);
const handleCardSelect = (cardData: {
assetName: string;
uploadedOn: string;
uploadedOn: number;
price: number;
rating: number;
views: number;
description: string;
}) => {
setSelectedCard(cardData);
};
@@ -48,12 +49,13 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
<Card
key={assetDetail._id}
assetName={assetDetail?.filename}
uploadedOn={assetDetail.uploadDate.toString()}
uploadedOn={assetDetail.uploadDate}
price={36500}
rating={4.5}
views={800}
onSelectCard={handleCardSelect}
image={assetDetail.thumbnail}
description={assetDetail.description}
/>
))}
{/* <RenderOverlay> */}

View File

@@ -29,11 +29,10 @@ const FilterSearch: React.FC<ModelsProps> = ({
filteredModels,
}) => {
const [activeOption, setActiveOption] = useState("Sort by"); // State for active option
console.log("filteredModels: ", filteredModels);
const handleSelect = (option: string) => {
setActiveOption(option);
console.log("option: ", option);
// Alphabet ascending
// Alphabet descending
// All

View File

@@ -21,6 +21,7 @@ import DroppedObjects from "../../components/ui/componets/DroppedFloatingWidgets
import ZoneCentreTarget from "../../components/ui/componets/zoneCameraTarget";
import ProductionCapacity from "../../components/layout/3D-cards/cards/ProductionCapacity";
import Dropped3dWidgets from "../../components/ui/componets/Dropped3dWidget";
import { useWidgetSubOption } from "../../store/store";
export default function Scene() {
@@ -31,9 +32,6 @@ export default function Scene() {
{ name: "right", keys: ["ArrowRight", "d", "D"] },
], [])
return (
<KeyboardControls map={map}>
<Canvas
@@ -45,7 +43,7 @@ export default function Scene() {
}}
>
<Dropped3dWidgets/>
<Dropped3dWidgets />
<Controls />
<TransformControl />
<SelectionControls />

View File

@@ -36,7 +36,7 @@ const MeasurementTool = () => {
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.type === "GridHelper"));
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();
@@ -83,7 +83,7 @@ const MeasurementTool = () => {
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.type === "GridHelper"));
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);

View File

@@ -29,6 +29,7 @@ import {
useUpdateScene,
useWalls,
useToolMode,
useRefTextUpdate,
} from "../../../store/store";
////////// 3D Function Imports //////////
@@ -118,7 +119,7 @@ export default function World() {
const { shadows, setShadows } = useShadows();
const { updateScene, setUpdateScene } = useUpdateScene();
const { walls, setWalls } = useWalls();
const [RefTextupdate, setRefTextUpdate] = useState(-1000);
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
// const loader = new GLTFLoader();
// const dracoLoader = new DRACOLoader();
@@ -158,7 +159,7 @@ export default function World() {
////////// All Toggle's //////////
useEffect(() => {
setRefTextUpdate((prevUpdate) => prevUpdate - 1);
setRefTextUpdate((prevUpdate: number) => prevUpdate - 1);
if (dragPointControls.current) {
dragPointControls.current.enabled = false;
}
@@ -241,7 +242,7 @@ export default function World() {
<DistanceText key={toggleView} />
<ReferenceDistanceText
key={RefTextupdate}
key={refTextupdate}
line={ReferenceLineMesh.current}
/>

View File

@@ -52,7 +52,7 @@ function Behaviour() {
],
assetPosition: [...item.position],
assetRotation: [item.rotation.x, item.rotation.y, item.rotation.z],
speed: 1,
speed: 'Inherit',
};
newPaths.push(newPath);
@@ -67,12 +67,11 @@ function Behaviour() {
point: {
uuid: pointUUID,
position: [pointPosition.x, pointPosition.y, pointPosition.z],
actions: [{ uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: THREE.MathUtils.generateUUID(), hitCount: 1, end: THREE.MathUtils.generateUUID(), buffer: 0, isUsed: false }],
triggers: [],
actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: '', hitCount: 1, end: '', buffer: 0 },
connections: { source: { pathUUID: item.modeluuid, pointUUID: pointUUID }, targets: [] },
speed: 2,
},
assetPosition: [...item.position],
speed: 2,
};
newPaths.push(newVehiclePath);

View File

@@ -86,6 +86,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
};
}
}
// In the updatePathConnections function, modify the Vehicle handling section:
else if (path.type === 'Vehicle') {
// Handle outgoing connections from Vehicle
if (path.modeluuid === fromPathUUID && path.point.uuid === fromPointUUID) {
@@ -95,6 +96,27 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
};
const existingTargets = path.point.connections.targets || [];
// Check if we're trying to add a connection to a Conveyor
const toPath = simulationPaths.find(p => p.modeluuid === toPathUUID);
const isConnectingToConveyor = toPath?.type === 'Conveyor';
// Count existing connections
if (existingTargets.length >= 2) {
console.log("Vehicle can have maximum 2 connections");
return path;
}
// Check if we already have a Conveyor connection and trying to add another
const hasConveyorConnection = existingTargets.some(target => {
const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID);
return targetPath?.type === 'Conveyor';
});
if (hasConveyorConnection && isConnectingToConveyor) {
console.log("Vehicle can only have one connection to a Conveyor");
return path;
}
if (!existingTargets.some(target =>
target.pathUUID === newTarget.pathUUID &&
target.pointUUID === newTarget.pointUUID
@@ -119,6 +141,27 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
};
const existingTargets = path.point.connections.targets || [];
// Check if we're receiving a connection from a Conveyor
const fromPath = simulationPaths.find(p => p.modeluuid === fromPathUUID);
const isConnectingFromConveyor = fromPath?.type === 'Conveyor';
// Count existing connections
if (existingTargets.length >= 2) {
console.log("Vehicle can have maximum 2 connections");
return path;
}
// Check if we already have a Conveyor connection and trying to add another
const hasConveyorConnection = existingTargets.some(target => {
const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID);
return targetPath?.type === 'Conveyor';
});
if (hasConveyorConnection && isConnectingFromConveyor) {
console.log("Vehicle can only have one connection to a Conveyor");
return path;
}
if (!existingTargets.some(target =>
target.pathUUID === reverseTarget.pathUUID &&
target.pointUUID === reverseTarget.pointUUID
@@ -135,6 +178,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
};
}
}
return path;
}
return path;
});
@@ -168,7 +212,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
drag = true;
}
};
const onContextMenu = (evt: MouseEvent) => {
evt.preventDefault();
if (drag || evt.button === 0) return;
@@ -200,29 +243,126 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected?.pathUUID);
const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID);
// Prevent vehicle-to-vehicle connections
if (firstPath && secondPath && firstPath.type === 'Vehicle' && secondPath.type === 'Vehicle') {
console.log("Cannot connect two vehicle paths together");
return;
}
const isAlreadyConnected = simulationPaths.some(path => {
if (path.type === 'Conveyor') {
return path.points.some(point =>
point.uuid === sphereUUID &&
point.connections.targets.length > 0
);
} else if (path.type === 'Vehicle') {
return path.point.uuid === sphereUUID &&
path.point.connections.targets.length > 0;
}
return false;
});
if (isAlreadyConnected) {
console.log("Sphere is already connected. Ignoring.");
// Prevent conveyor middle point to conveyor connections
if (firstPath && secondPath &&
firstPath.type === 'Conveyor' &&
secondPath.type === 'Conveyor' &&
!firstSelected?.isCorner) {
console.log("Conveyor middle points can only connect to non-conveyor paths");
return;
}
if (!firstSelected) {
// Check if this specific connection already exists
const isDuplicateConnection = firstSelected
? simulationPaths.some(path => {
if (path.modeluuid === firstSelected.pathUUID) {
if (path.type === 'Conveyor') {
const point = path.points.find(p => p.uuid === firstSelected.sphereUUID);
return point?.connections.targets.some(t =>
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
);
} else if (path.type === 'Vehicle') {
return path.point.connections.targets.some(t =>
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
);
}
}
return false;
})
: false;
if (isDuplicateConnection) {
console.log("These points are already connected. Ignoring.");
return;
}
// For Vehicles, skip the "already connected" check since they can have multiple connections
if (intersected.userData.path.type !== 'Vehicle') {
const isAlreadyConnected = simulationPaths.some(path => {
if (path.type === 'Conveyor') {
return path.points.some(point =>
point.uuid === sphereUUID &&
point.connections.targets.length > 0
);
}
return false;
});
if (isAlreadyConnected) {
console.log("Conveyor point is already connected. Ignoring.");
return;
}
}
// Check vehicle connection limits
const checkVehicleConnections = (pathUUID: string) => {
const path = simulationPaths.find(p => p.modeluuid === pathUUID);
if (path?.type === 'Vehicle') {
return path.point.connections.targets.length >= 2;
}
return false;
};
if (firstSelected) {
// Check if either selected point is from a Vehicle with max connections
if (checkVehicleConnections(firstSelected.pathUUID) ||
checkVehicleConnections(pathUUID)) {
console.log("Vehicle already has maximum connections");
return;
}
// Check if we're trying to add a second Conveyor connection to a Vehicle
if (firstPath?.type === 'Vehicle' && secondPath?.type === 'Conveyor') {
const hasConveyorConnection = firstPath.point.connections.targets.some(target => {
const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID);
return targetPath?.type === 'Conveyor';
});
if (hasConveyorConnection) {
console.log("Vehicle can only have one connection to a Conveyor");
return;
}
}
if (secondPath?.type === 'Vehicle' && firstPath?.type === 'Conveyor') {
const hasConveyorConnection = secondPath.point.connections.targets.some(target => {
const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID);
return targetPath?.type === 'Conveyor';
});
if (hasConveyorConnection) {
console.log("Vehicle can only have one connection to a Conveyor");
return;
}
}
// Prevent same-path connections
if (firstSelected.pathUUID === pathUUID) {
console.log("Cannot connect spheres on the same path.");
return;
}
// At least one must be start/end point
if (!firstSelected.isCorner && !isStartOrEnd) {
console.log("At least one of the selected spheres must be a start or end point.");
return;
}
// All checks passed - make the connection
handleAddConnection(
firstSelected.pathUUID,
firstSelected.sphereUUID,
pathUUID,
sphereUUID
);
} else {
// First selection - just store it
setFirstSelected({
pathUUID,
sphereUUID,
@@ -230,28 +370,11 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
isCorner: isStartOrEnd
});
setIsConnecting(true);
} else {
if (firstSelected.sphereUUID === sphereUUID) return;
if (firstSelected.pathUUID === pathUUID) {
console.log("Cannot connect spheres on the same path.");
return;
}
if (!firstSelected.isCorner && !isStartOrEnd) {
console.log("At least one of the selected spheres must be a start or end point.");
return;
}
handleAddConnection(
firstSelected.pathUUID,
firstSelected.sphereUUID,
pathUUID,
sphereUUID
);
}
}
}
} else {
// Clicked outside - cancel connection
setFirstSelected(null);
setCurrentLine(null);
setIsConnecting(false);
@@ -294,7 +417,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
if (intersects.length > 0) {
point = intersects[0].point;
if (point.y < 0.05) {
point = new THREE.Vector3(point.x, 0.05, point.z);
}
@@ -316,28 +438,68 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID);
const isVehicleToVehicle = firstPath?.type === 'Vehicle' && secondPath?.type === 'Vehicle';
// Inside the useFrame hook, where we check for snapped spheres:
const isConnectable = (pathData.type === 'Vehicle' ||
(pathData.points.length > 0 && (
sphereUUID === pathData.points[0].uuid ||
sphereUUID === pathData.points[pathData.points.length - 1].uuid
))) && !isVehicleToVehicle;
))) &&
!isVehicleToVehicle &&
!(firstPath?.type === 'Conveyor' &&
pathData.type === 'Conveyor' &&
!firstSelected.isCorner);
const isAlreadyConnected = simulationPaths.some(path => {
if (path.type === 'Conveyor') {
return path.points.some(point =>
point.uuid === sphereUUID &&
point.connections.targets.length > 0
);
} else if (path.type === 'Vehicle') {
return path.point.uuid === sphereUUID &&
path.point.connections.targets.length > 0;
// Check for duplicate connection (regardless of path type)
const isDuplicateConnection = simulationPaths.some(path => {
if (path.modeluuid === firstSelected.pathUUID) {
if (path.type === 'Conveyor') {
const point = path.points.find(p => p.uuid === firstSelected.sphereUUID);
return point?.connections.targets.some(t =>
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
);
} else if (path.type === 'Vehicle') {
return path.point.connections.targets.some(t =>
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
);
}
}
return false;
});
// For non-Vehicle paths, check if already connected
const isNonVehicleAlreadyConnected = pathData.type !== 'Vehicle' &&
simulationPaths.some(path => {
if (path.type === 'Conveyor') {
return path.points.some(point =>
point.uuid === sphereUUID &&
point.connections.targets.length > 0
);
}
return false;
});
// Check vehicle connection limits
const isVehicleAtMaxConnections = pathData.type === 'Vehicle' &&
pathData.point.connections.targets.length >= 2;
const isVehicleConveyorConflict =
(firstPath?.type === 'Vehicle' && secondPath?.type === 'Conveyor' &&
firstPath.point.connections.targets.some(t => {
const targetPath = simulationPaths.find(p => p.modeluuid === t.pathUUID);
return targetPath?.type === 'Conveyor';
})) ||
(secondPath?.type === 'Vehicle' && firstPath?.type === 'Conveyor' &&
secondPath.point.connections.targets.some(t => {
const targetPath = simulationPaths.find(p => p.modeluuid === t.pathUUID);
return targetPath?.type === 'Conveyor';
}));
if (
!isAlreadyConnected &&
!isDuplicateConnection &&
!isVehicleToVehicle &&
!isNonVehicleAlreadyConnected &&
!isVehicleAtMaxConnections &&
!isVehicleConveyorConflict &&
firstSelected.sphereUUID !== sphereUUID &&
firstSelected.pathUUID !== pathUUID &&
(firstSelected.isCorner || isConnectable)
@@ -371,13 +533,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
end: point,
mid: midPoint,
});
console.log({
start: firstSelected.position,
end: point,
mid: midPoint,
});
// setIsConnecting(true);
if (sphereIntersects.length > 0) {
setHelperLineColor(isInvalidConnection ? 'red' : '#6cf542');

View File

@@ -1,27 +1,10 @@
import * as THREE from 'three';
import * as Types from '../../../types/world/worldTypes';
import { useRef, useState, useEffect } from 'react';
import { Sphere, TransformControls } from '@react-three/drei';
import { useIsConnecting, useRenderDistance, useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../../store/store';
import { useFrame, useThree } from '@react-three/fiber';
import { useSubModuleStore } from '../../../store/useModuleStore';
import { point } from '@turf/helpers';
interface ConveyorEventsSchema {
modeluuid: string;
modelName: string;
type: 'Conveyor';
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | [];
triggers: { uuid: string; name: string; type: string; isUsed: boolean }[] | [];
connections: { source: { pathUUID: string; pointUUID: string }; targets: { pathUUID: string; pointUUID: string }[] };
}[];
assetPosition: [number, number, number];
assetRotation: [number, number, number];
speed: number;
}
function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject<THREE.Group> }) {
const { renderDistance } = useRenderDistance();
@@ -89,7 +72,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
};
}
return path;
}) as ConveyorEventsSchema[];
}) as Types.ConveyorEventsSchema[];
setSimulationPaths(updatedPaths);
};

View File

@@ -14,7 +14,7 @@ function Simulation() {
const [processes, setProcesses] = useState([]);
useEffect(() => {
console.log('simulationPaths: ', simulationPaths);
}, [simulationPaths]);
// useEffect(() => {

View File

@@ -93,6 +93,7 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn
intersects = intersects.filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper")
@@ -146,6 +147,7 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn
const intersects = raycaster.intersectObjects(scene.children, true).filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper")
@@ -262,6 +264,7 @@ const PathCreator = ({ simulationPaths, setSimulationPaths, connections, setConn
const intersects = raycaster.intersectObjects(scene.children, true).filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper")