changed marketplace assets

This commit is contained in:
2025-04-01 14:27:08 +05:30
92 changed files with 4152 additions and 2071 deletions

View File

@@ -9,6 +9,8 @@ import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { Socket } from 'socket.io-client';
import * as CONSTANTS from '../../../../types/world/worldConstants';
import { getAssetEventType } from '../../../../services/simulation/getAssetEventType';
import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
async function addAssetModel(
raycaster: THREE.Raycaster,
@@ -58,6 +60,7 @@ async function addAssetModel(
if (intersectPoint.y < 0) {
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
}
console.log('selectedItem: ', selectedItem);
const cachedModel = THREE.Cache.get(selectedItem.id);
if (cachedModel) {
// console.log(`[Cache] Fetching ${selectedItem.name}`);
@@ -136,51 +139,88 @@ async function handleModelLoad(
modelname: selectedItem.name,
modelfileID: selectedItem.id,
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems) => {
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
getAssetEventType(selectedItem.id, organization).then(async (res) => {
console.log('res: ', res);
// await setFloorItemApi(
// organization,
// newFloorItem.modeluuid,
// newFloorItem.modelname,
// newFloorItem.position,
// { "x": model.rotation.x, "y": model.rotation.y, "z": model.rotation.z },
// newFloorItem.modelfileID!,
// false,
// true,
// );
if (res.type === "Conveyor") {
const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID());
//SOCKET
const eventData: Extract<Types.FloorItemType['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: false
}],
triggers: [],
connections: {
source: { pathUUID: model.uuid, pointUUID: pointUUIDs[index] },
targets: []
}
})),
speed: 'Inherit'
};
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,
};
console.log('eventData: ', eventData);
newFloorItem.eventData = eventData;
}
socket.emit("v1:FloorItems:set", data);
setFloorItems((prevItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
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!"); } });
// 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,
// newFloorItem.eventData
// );
// 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: newFloorItem.eventData,
socketId: socket.id
};
socket.emit("v2:model-asset:add", data);
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
});
}
export default addAssetModel;

View File

@@ -1,90 +1,143 @@
import { useEffect, useState } from "react"
import { useEffect, useState } from "react";
import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi";
import * as THREE from "three";
import { useActiveLayer, useDeletedLines, useNewLines, useToggleView } from "../../../../store/store";
import {
useActiveLayer,
useDeletedLines,
useNewLines,
useToggleView,
} from "../../../../store/store";
import objectLinesToArray from "./lineConvertions/objectLinesToArray";
import { Html } from "@react-three/drei";
import * as Types from "../../../../types/world/worldTypes";
const DistanceText = () => {
const [lines, setLines] = useState<{ distance: string; position: THREE.Vector3; userData: Types.Line; layer: string }[]>([]);
const { activeLayer } = useActiveLayer();
const { toggleView } = useToggleView();
const { newLines, setNewLines } = useNewLines();
const { deletedLines, setDeletedLines } = useDeletedLines();
const [lines, setLines] = useState<
{
distance: string;
position: THREE.Vector3;
userData: Types.Line;
layer: string;
}[]
>([]);
const { activeLayer } = useActiveLayer();
const { toggleView } = useToggleView();
const { newLines, setNewLines } = useNewLines();
const { deletedLines, setDeletedLines } = useDeletedLines();
useEffect(() => {
const email = localStorage.getItem('email')
if (!email) return;
const organization = (email.split("@")[1]).split(".")[0];
useEffect(() => {
const email = localStorage.getItem("email");
if (!email) return;
const organization = email.split("@")[1].split(".")[0];
getLines(organization).then((data) => {
data = objectLinesToArray(data);
getLines(organization).then((data) => {
data = objectLinesToArray(data);
const lines = data.filter((line: Types.Line) => line[0][2] === activeLayer)
.map((line: Types.Line) => {
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
const distance = point1.distanceTo(point2);
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
return {
distance: distance.toFixed(1),
position: midpoint,
userData: line,
layer: activeLayer,
};
});
setLines(lines)
})
}, [activeLayer])
const lines = data
.filter((line: Types.Line) => line[0][2] === activeLayer)
.map((line: Types.Line) => {
const point1 = new THREE.Vector3(
line[0][0].x,
line[0][0].y,
line[0][0].z
);
const point2 = new THREE.Vector3(
line[1][0].x,
line[1][0].y,
line[1][0].z
);
const distance = point1.distanceTo(point2);
const midpoint = new THREE.Vector3()
.addVectors(point1, point2)
.divideScalar(2);
return {
distance: distance.toFixed(1),
position: midpoint,
userData: line,
layer: activeLayer,
};
});
setLines(lines);
});
}, [activeLayer]);
useEffect(() => {
if (newLines.length > 0) {
if (newLines[0][0][2] !== activeLayer) return;
const newLinesData = newLines.map((line: Types.Line) => {
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
const distance = point1.distanceTo(point2);
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
useEffect(() => {
if (newLines.length > 0) {
if (newLines[0][0][2] !== activeLayer) return;
const newLinesData = newLines.map((line: Types.Line) => {
const point1 = new THREE.Vector3(
line[0][0].x,
line[0][0].y,
line[0][0].z
);
const point2 = new THREE.Vector3(
line[1][0].x,
line[1][0].y,
line[1][0].z
);
const distance = point1.distanceTo(point2);
const midpoint = new THREE.Vector3()
.addVectors(point1, point2)
.divideScalar(2);
return {
distance: distance.toFixed(1),
position: midpoint,
userData: line,
layer: activeLayer,
};
});
setLines((prevLines) => [...prevLines, ...newLinesData]);
setNewLines([]);
}
}, [newLines, activeLayer]);
return {
distance: distance.toFixed(1),
position: midpoint,
userData: line,
layer: activeLayer,
};
});
setLines((prevLines) => [...prevLines, ...newLinesData]);
setNewLines([]);
}
}, [newLines, activeLayer]);
useEffect(() => {
if ((deletedLines as Types.Lines).length > 0) {
setLines((prevLines) =>
prevLines.filter(
(line) =>
!deletedLines.some(
(deletedLine: any) =>
deletedLine[0][1] === line.userData[0][1] &&
deletedLine[1][1] === line.userData[1][1]
)
)
);
setDeletedLines([]);
}
}, [deletedLines]);
useEffect(() => {
if ((deletedLines as Types.Lines).length > 0) {
setLines((prevLines) =>
prevLines.filter(
(line) => !deletedLines.some((deletedLine: any) => deletedLine[0][1] === line.userData[0][1] && deletedLine[1][1] === line.userData[1][1])
)
);
setDeletedLines([]);
}
}, [deletedLines]);
return (
<>
{toggleView && (
<group name="Distance_Text">
{lines.map((text) => (
<Html
// data
key={`${text.userData[0][1]}_${text.userData[1][1]}`}
userData={text.userData}
position={[text.position.x, 1, text.position.z]}
// class
wrapperClass="distance-text-wrapper"
className="distance-text"
// other
zIndexRange={[100, 0]}
prepend
sprite
>
<div
key={`${text.userData[0][1]}_${text.userData[1][1]}`}
className={`distance line-${text.userData[0][1]}_${text.userData[1][1]}_${text.layer}`}
>
{text.distance} m
</div>
</Html>
))}
</group>
)}
</>
);
};
return (
<>
{toggleView && (
<group name='Distance_Text'>
{lines.map((text) => (
<Html key={`${text.userData[0][1]}_${text.userData[1][1]}`} transform sprite userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }} >
<div key={`${text.userData[0][1]}_${text.userData[1][1]}`} className={`Distance line-${text.userData[0][1]}_${text.userData[1][1]}_${text.layer}`} >{text.distance} m</div>
</Html>
))}
</group>
)}
</>
)
}
export default DistanceText;
export default DistanceText;

View File

@@ -60,6 +60,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
};
getFloorItems(organization).then((data) => {
console.log('data: ', data);
const uniqueItems = (data as Types.FloorItems).filter((item, index, self) =>
index === self.findIndex((t) => t.modelfileID === item.modelfileID)
);
@@ -305,7 +306,10 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject
};
}, [deleteModels, transformMode, controls, selectedItem, state.camera, state.pointer, activeTool, activeModule]);
useEffect(() => {
console.log('floorItems: ', floorItems);
}, [floorItems])
useFrame(() => {
if (controls)

View File

@@ -434,9 +434,9 @@ const ZoneGroup: React.FC = () => {
const point2 = new THREE.Vector3(nextPoint[0], nextPoint[1], nextPoint[2]);
const planeWidth = point1.distanceTo(point2);
const planeHeight = CONSTANTS.wallConfig.height;
const planeHeight = CONSTANTS.zoneConfig.height;
const midpoint = new THREE.Vector3((point1.x + point2.x) / 2, (CONSTANTS.wallConfig.height / 2) + ((zone.layer - 1) * CONSTANTS.wallConfig.height), (point1.z + point2.z) / 2);
const midpoint = new THREE.Vector3((point1.x + point2.x) / 2, (CONSTANTS.zoneConfig.height / 2) + ((zone.layer - 1) * CONSTANTS.zoneConfig.height), (point1.z + point2.z) / 2);
const angle = Math.atan2(point2.z - point1.z, point2.x - point1.x);

View File

@@ -81,7 +81,8 @@ export default function SocketResponses({
// console.log('data: ', data);
})
socket.on('FloorItemsUpdateResponse', async (data: any) => {
socket.on('model-asset:response:updates', async (data: any) => {
console.log('data: ', data);
if (socket.id === data.socketId) {
return
}

View File

@@ -1,59 +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;
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

@@ -1,26 +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];
}
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

@@ -1,10 +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;
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

@@ -42,6 +42,8 @@ const SelectionControls: React.FC = () => {
itemsGroupRef.current = itemsGroup;
let isSelecting = false;
let isRightClick = false;
let rightClickMoved = false;
let isCtrlSelecting = false;
const helper = new SelectionHelper(gl);
@@ -52,16 +54,23 @@ const SelectionControls: React.FC = () => {
}
const onPointerDown = (event: PointerEvent) => {
if (event.button !== 0) return
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);
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);
@@ -69,6 +78,14 @@ const SelectionControls: React.FC = () => {
};
const onPointerUp = (event: PointerEvent) => {
if (event.button === 2) {
isRightClick = false;
if (!rightClickMoved) {
clearSelection();
}
return;
}
if (isSelecting && isCtrlSelecting) {
isCtrlSelecting = false;
isSelecting = false;
@@ -94,10 +111,13 @@ const SelectionControls: React.FC = () => {
}
};
const onContextMenu = (event: MouseEvent) => {
event.preventDefault();
clearSelection();
}
if (!rightClickMoved) {
clearSelection();
}
};
if (!toggleView && activeModule === "builder") {
helper.enabled = true;

View File

@@ -1,9 +1,9 @@
import * as THREE from 'three';
import { useToggleView } from '../../../store/store';
import * as CONSTANTS from '../../../types/world/worldConstants';
const Ground = ({ grid, plane }: any) => {
const { toggleView, setToggleView } = useToggleView();
const { toggleView } = useToggleView();
const savedTheme: string | null = localStorage.getItem('theme');
return (
@@ -19,4 +19,4 @@ const Ground = ({ grid, plane }: any) => {
)
}
export default Ground;
export default Ground;

View File

@@ -1,11 +1,11 @@
import { useMemo, useState } from "react";
import { useMemo } from "react";
import { Canvas } from "@react-three/fiber";
import { Environment, 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 PostProcessing from "./postProcessing/postProcessing";
import Sun from "./environment/sky";
import CamModelsGroup from "../collaboration/collabCams";
import Shadows from "./environment/shadow";
@@ -15,33 +15,31 @@ import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr";
import SelectionControls from "./controls/selection/selectionControls";
import MeasurementTool from "./tools/measurementTool";
import Simulation from "../simulation/simulation";
import DroppedObjects from "../../components/ui/componets/DroppedFloatingWidgets";
// import Simulation from "./simulationtemp/simulation";
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() {
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 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");
return (
<KeyboardControls map={map}>
<Canvas
// style={{ width: "100vw", height: "100vh" }}
eventPrefix="client"
gl={{ powerPreference: "high-performance", antialias: true }}
onContextMenu={(e) => {
e.preventDefault();
}}
>
<Dropped3dWidgets />
<Controls />
@@ -52,7 +50,7 @@ export default function Scene() {
<ZoneCentreTarget />
<Simulation />
<PostProcessing />
<Sun />
{savedTheme !== "dark" && <Sun />}
<Shadows />
<CamModelsGroup />
<MqttEvents />

View File

@@ -364,7 +364,7 @@ export default function World() {
anglesnappedPoint={anglesnappedPoint}
/>
<DrieHtmlTemp itemsGroup={itemsGroup} />
{/* <DrieHtmlTemp itemsGroup={itemsGroup} /> */}
<Agv lines={lines} plane={plane} />
</>

View File

@@ -67,7 +67,7 @@ function Behaviour() {
point: {
uuid: pointUUID,
position: [pointPosition.x, pointPosition.y, pointPosition.z],
actions: { uuid: THREE.MathUtils.generateUUID(), name: 'Action 1', type: 'Start', start: '', hitCount: 1, end: '', buffer: 0 },
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,
},

View File

@@ -96,24 +96,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
};
const existingTargets = path.point.connections.targets || [];
// Check if we're trying to add a connection to a Conveyor
// Check if target is 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");
if (toPath?.type !== 'Conveyor') {
console.log("Vehicle can only connect to Conveyors");
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");
// Check if already has a connection
if (existingTargets.length >= 1) {
console.log("Vehicle can have only one connection");
return path;
}
@@ -141,24 +133,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
};
const existingTargets = path.point.connections.targets || [];
// Check if we're receiving a connection from a Conveyor
// Check if source is 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");
if (fromPath?.type !== 'Conveyor') {
console.log("Vehicle can only connect to Conveyors");
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");
// Check if already has a connection
if (existingTargets.length >= 1) {
console.log("Vehicle can have only one connection");
return path;
}
@@ -212,6 +196,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
drag = true;
}
};
const onContextMenu = (evt: MouseEvent) => {
evt.preventDefault();
if (drag || evt.button === 0) return;
@@ -282,7 +267,16 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
return;
}
// For Vehicles, skip the "already connected" check since they can have multiple connections
// For Vehicles, check if they're already connected to anything
if (intersected.userData.path.type === 'Vehicle') {
const vehicleConnections = intersected.userData.path.point.connections.targets.length;
if (vehicleConnections >= 1) {
console.log("Vehicle can only have one connection");
return;
}
}
// For non-Vehicle paths, check if already connected
if (intersected.userData.path.type !== 'Vehicle') {
const isAlreadyConnected = simulationPaths.some(path => {
if (path.type === 'Conveyor') {
@@ -300,48 +294,14 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
}
}
// 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");
// Check if trying to connect Vehicle to non-Conveyor
if ((firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') ||
(secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor')) {
console.log("Vehicle can only connect to Conveyors");
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.");
@@ -478,28 +438,19 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
return false;
});
// Check vehicle connection limits
// Check vehicle connection rules
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';
}));
pathData.point.connections.targets.length >= 1;
const isVehicleConnectingToNonConveyor =
(firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') ||
(secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor');
if (
!isDuplicateConnection &&
!isVehicleToVehicle &&
!isNonVehicleAlreadyConnected &&
!isVehicleAtMaxConnections &&
!isVehicleConveyorConflict &&
!isVehicleConnectingToNonConveyor &&
firstSelected.sphereUUID !== sphereUUID &&
firstSelected.pathUUID !== pathUUID &&
(firstSelected.isCorner || isConnectable)

View File

@@ -1,8 +1,8 @@
import * as THREE from 'three';
import * as Types from '../../../types/world/worldTypes';
import { useRef, useState, useEffect } from 'react';
import { useRef, useState, useEffect, useMemo } from 'react';
import { Sphere, TransformControls } from '@react-three/drei';
import { useIsConnecting, useRenderDistance, useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../../store/store';
import { useEditingPoint, useEyeDropMode, useIsConnecting, usePreviewPosition, useRenderDistance, useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../../store/store';
import { useFrame, useThree } from '@react-three/fiber';
import { useSubModuleStore } from '../../../store/useModuleStore';
@@ -10,13 +10,18 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
const { renderDistance } = useRenderDistance();
const { setSubModule } = useSubModuleStore();
const { setSelectedActionSphere, selectedActionSphere } = useSelectedActionSphere();
const { eyeDropMode, setEyeDropMode } = useEyeDropMode();
const { editingPoint, setEditingPoint } = useEditingPoint();
const { previewPosition, setPreviewPosition } = usePreviewPosition();
const { raycaster, camera, pointer, gl } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { setSelectedPath } = useSelectedPath();
const { simulationPaths, setSimulationPaths } = useSimulationPaths();
const { isConnecting } = useIsConnecting();
const { camera } = useThree();
const groupRefs = useRef<{ [key: string]: THREE.Group }>({});
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
const isMovingRef = useRef(false);
const transformRef = useRef<any>(null);
const [transformMode, setTransformMode] = useState<'translate' | 'rotate' | null>(null);
@@ -77,6 +82,83 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
setSimulationPaths(updatedPaths);
};
useFrame(() => {
if (eyeDropMode) {
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
setPreviewPosition({ x: point.x, y: point.z });
}
} else {
setPreviewPosition(null);
}
});
useEffect(() => {
if (!camera) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
const onPointerDown = () => {
isMovingRef.current = false;
};
const onPointerMove = () => {
isMovingRef.current = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMovingRef.current && eyeDropMode && event.button === 0 && previewPosition) {
event.preventDefault();
if (editingPoint) {
handlePointUpdate(editingPoint, previewPosition.x, previewPosition.y);
setEditingPoint(null);
setEyeDropMode(false);
}
}
};
if (eyeDropMode) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
};
}, [eyeDropMode, editingPoint, previewPosition]);
const handlePointUpdate = (pointType: 'start' | 'end', x: number, z: number) => {
if (!selectedActionSphere?.point?.uuid) return;
const updatedPaths = simulationPaths.map((path) => {
if (path.type === "Vehicle" && path.point.uuid === selectedActionSphere.point.uuid) {
return {
...path,
point: {
...path.point,
actions: {
...path.point.actions,
[pointType]: {
...path.point.actions[pointType],
x: x,
y: z
}
}
}
};
}
return path;
});
setSimulationPaths(updatedPaths);
};
return (
<group name='simulation-simulationPaths-group' ref={pathsGroupRef}>
@@ -92,7 +174,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
position={path.assetPosition}
rotation={path.assetRotation}
onClick={(e) => {
if (isConnecting) return;
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({ path, group: groupRefs.current[path.modeluuid] });
setSelectedActionSphere(null);
@@ -100,6 +182,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
setSubModule('mechanics');
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule('properties');
}}
@@ -113,7 +196,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
name='events-sphere'
ref={el => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
if (isConnecting) return;
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
@@ -124,6 +207,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
}}
userData={{ point, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule('properties');
setSelectedActionSphere(null);
}}
@@ -155,7 +239,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
ref={el => (groupRefs.current[path.modeluuid] = el!)}
position={path.assetPosition}
onClick={(e) => {
if (isConnecting) return;
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({ path, group: groupRefs.current[path.modeluuid] });
setSelectedActionSphere(null);
@@ -163,6 +247,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
setSubModule('mechanics');
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule('properties');
}}
@@ -175,7 +260,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
name='events-sphere'
ref={el => (sphereRefs.current[path.point.uuid] = el!)}
onClick={(e) => {
if (isConnecting) return;
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
@@ -186,6 +271,7 @@ function PathCreation({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject
}}
userData={{ point: path.point, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule('properties');
setSelectedActionSphere(null);
}}

View File

@@ -1,6 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef, useMemo } from 'react';
import { useSelectedActionSphere, useSelectedPath, useSimulationPaths } from '../../store/store';
import { useThree } from '@react-three/fiber';
import * as THREE from 'three';
import Behaviour from './behaviour/behaviour';
import PathCreation from './path/pathCreation';
@@ -14,7 +13,6 @@ function Simulation() {
const [processes, setProcesses] = useState([]);
useEffect(() => {
console.log('simulationPaths: ', simulationPaths);
}, [simulationPaths]);
// useEffect(() => {
@@ -29,9 +27,10 @@ function Simulation() {
// }
// }, [selectedPath]);
return (
<>
<Behaviour/>
<Behaviour />
{activeModule === 'simulation' && (
<>
<PathCreation pathsGroupRef={pathsGroupRef} />

View File

@@ -1,74 +1,89 @@
import { saveTemplateApi } from "../../services/realTimeVisulization/zoneData/saveTempleteApi";
import { Template } from "../../store/useTemplateStore";
import { captureVisualization } from "./captureVisualization";
type HandleSaveTemplateProps = {
addTemplate: (template: Template) => void;
floatingWidget: []; // Updated type from `[]` to `any[]` for clarity
widgets3D: []; // Updated type from `[]` to `any[]` for clarity
selectedZone: {
panelOrder: string[]; // Adjust the type based on actual data structure
widgets: any[]; // Replace `any` with the actual widget type
panelOrder: string[];
widgets: any[];
};
templates?: Template[];
};
// Generate a unique ID (placeholder function)
// Generate a unique ID
const generateUniqueId = (): string => {
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
};
// Refactored function
export const handleSaveTemplate = async ({
addTemplate,
floatingWidget,
widgets3D,
selectedZone,
templates = [],
}: HandleSaveTemplateProps): Promise<void> => {
try {
// Check if the selected zone has any widgets
if (!selectedZone.widgets || selectedZone.widgets.length === 0) {
console.warn("Cannot save an empty template.");
console.warn("No widgets found in the selected zone.");
return;
}
// Check if the template already exists
const isDuplicate = templates.some((template) => {
const isSamePanelOrder =
const isDuplicate = templates.some(
(template) =>
JSON.stringify(template.panelOrder) ===
JSON.stringify(selectedZone.panelOrder);
const isSameWidgets =
JSON.stringify(selectedZone.panelOrder) &&
JSON.stringify(template.widgets) ===
JSON.stringify(selectedZone.widgets);
return isSamePanelOrder && isSameWidgets;
});
JSON.stringify(selectedZone.widgets)
);
if (isDuplicate) {
console.warn("This template already exists.");
return;
}
// Capture visualization snapshot
const snapshot = await captureVisualization();
if (!snapshot) {
console.error("Failed to capture visualization snapshot.");
return;
}
console.log("snapshot: ", snapshot);
// if (!snapshot) {
// return;
// }
// Create a new template
const newTemplate: Template = {
id: generateUniqueId(),
name: `Template ${Date.now()}`,
name: `Template ${new Date().toISOString()}`, // Better name formatting
panelOrder: selectedZone.panelOrder,
widgets: selectedZone.widgets,
snapshot,
floatingWidget,
widgets3D,
};
console.log("Saving template:", newTemplate);
// Extract organization from email
const email = localStorage.getItem("email") || "";
const organization = email.includes("@")
? email.split("@")[1]?.split(".")[0]
: "";
if (!organization) {
console.error("Organization could not be determined from email.");
return;
}
// Save the template
try {
const response = await saveTemplateApi(organization, newTemplate);
console.log("Save API Response:", response);
// Add template only if API call succeeds
addTemplate(newTemplate);
} catch (error) {
console.error("Failed to add template:", error);
} catch (apiError) {
console.error("Error saving template to API:", apiError);
}
} catch (error) {
console.error("Failed to save template:", error);
console.error("Error in handleSaveTemplate:", error);
}
};