first commit

This commit is contained in:
2025-06-10 15:28:23 +05:30
commit e22a2dc275
699 changed files with 100382 additions and 0 deletions

View File

@@ -0,0 +1,221 @@
import * as THREE from "three";
import { useMemo, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
import { useDeleteTool } from "../../../../store/builder/store";
interface ConnectionLine {
id: string;
startPointUuid: string;
endPointUuid: string;
trigger: TriggerSchema;
}
export function Arrows({ connections }: { readonly connections: ConnectionLine[] }) {
const [hoveredLineKey, setHoveredLineKey] = useState<string | null>(null);
const groupRef = useRef<THREE.Group>(null);
const { scene } = useThree();
const { deleteTool } = useDeleteTool();
const getWorldPositionFromScene = (uuid: string): THREE.Vector3 | null => {
const obj = scene.getObjectByProperty("uuid", uuid);
if (!obj) return null;
const pos = new THREE.Vector3();
obj.getWorldPosition(pos);
return pos;
};
const createArrow = (
key: string,
fullCurve: THREE.QuadraticBezierCurve3,
centerT: number,
segmentSize: number,
scale: number,
reverse = false
) => {
const t1 = Math.max(0, centerT - segmentSize / 2);
const t2 = Math.min(1, centerT + segmentSize / 2);
const subCurve = getSubCurve(fullCurve, t1, t2, reverse);
const shaftGeometry = new THREE.TubeGeometry(subCurve, 8, 0.01 * scale, 8, false);
const end = subCurve.getPoint(1);
const tangent = subCurve.getTangent(1).normalize();
const arrowHeadLength = 0.15 * scale;
const arrowRadius = 0.01 * scale;
const arrowHeadRadius = arrowRadius * 2.5;
const headGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8);
headGeometry.translate(0, arrowHeadLength / 2, 0);
const rotation = new THREE.Quaternion().setFromUnitVectors(
new THREE.Vector3(0, 1, 0),
tangent
);
return (
<group key={key}>
<mesh
geometry={shaftGeometry}
onPointerOver={() => setHoveredLineKey(key)}
onPointerOut={() => setHoveredLineKey(null)}
>
<meshStandardMaterial
color={deleteTool && hoveredLineKey === key ? "red" : "#42a5f5"}
/>
</mesh>
<mesh
position={end}
quaternion={rotation}
geometry={headGeometry}
onPointerOver={() => setHoveredLineKey(key)}
onPointerOut={() => setHoveredLineKey(null)}
>
<meshStandardMaterial
color={deleteTool && hoveredLineKey === key ? "red" : "#42a5f5"}
/>
</mesh>
</group>
);
};
const getSubCurve = (
curve: THREE.Curve<THREE.Vector3>,
t1: number,
t2: number,
reverse = false
) => {
const divisions = 10;
const subPoints = Array.from({ length: divisions + 1 }, (_, i) => {
const t = THREE.MathUtils.lerp(t1, t2, i / divisions);
return curve.getPoint(t);
});
if (reverse) subPoints.reverse();
return new THREE.CatmullRomCurve3(subPoints);
};
const arrowGroups = connections.flatMap((connection) => {
const start = getWorldPositionFromScene(connection.startPointUuid);
const end = getWorldPositionFromScene(connection.endPointUuid);
if (!start || !end) return [];
const isBidirectional = connections.some(
(other) =>
other.startPointUuid === connection.endPointUuid &&
other.endPointUuid === connection.startPointUuid
);
if (isBidirectional && connection.startPointUuid < connection.endPointUuid) {
const distance = start.distanceTo(end);
const heightFactor = Math.max(0.5, distance * 0.2);
const control = new THREE.Vector3(
(start.x + end.x) / 2,
Math.max(start.y, end.y) + heightFactor,
(start.z + end.z) / 2
);
const curve = new THREE.QuadraticBezierCurve3(start, control, end);
const scale = THREE.MathUtils.clamp(distance * 0.75, 0.5, 3);
return [
createArrow(connection.id + "-fwd", curve, 0.33, 0.25, scale, true),
createArrow(connection.id + "-bwd", curve, 0.66, 0.25, scale, false),
];
}
if (!isBidirectional) {
const distance = start.distanceTo(end);
const heightFactor = Math.max(0.5, distance * 0.2);
const control = new THREE.Vector3(
(start.x + end.x) / 2,
Math.max(start.y, end.y) + heightFactor,
(start.z + end.z) / 2
);
const curve = new THREE.QuadraticBezierCurve3(start, control, end);
const scale = THREE.MathUtils.clamp(distance * 0.75, 0.5, 5);
return [
createArrow(connection.id, curve, 0.5, 0.3, scale)
];
}
return [];
});
return <group ref={groupRef} name="connectionArrows">{arrowGroups}</group>;
}
export function ArrowOnQuadraticBezier({
start,
mid,
end,
color = "#42a5f5",
}: {
start: number[];
mid: number[];
end: number[];
color?: string;
}) {
const minScale = 0.5;
const maxScale = 5;
const segmentSize = 0.3;
const startVec = useMemo(() => new THREE.Vector3(...start), [start]);
const midVec = useMemo(() => new THREE.Vector3(...mid), [mid]);
const endVec = useMemo(() => new THREE.Vector3(...end), [end]);
const fullCurve = useMemo(
() => new THREE.QuadraticBezierCurve3(startVec, midVec, endVec),
[startVec, midVec, endVec]
);
const distance = useMemo(() => startVec.distanceTo(endVec), [startVec, endVec]);
const scale = useMemo(() => THREE.MathUtils.clamp(distance * 0.75, minScale, maxScale), [distance]);
const arrowHeadLength = 0.15 * scale;
const arrowRadius = 0.01 * scale;
const arrowHeadRadius = arrowRadius * 2.5;
const subCurve = useMemo(() => {
const centerT = 0.5;
const t1 = Math.max(0, centerT - segmentSize / 2);
const t2 = Math.min(1, centerT + segmentSize / 2);
const divisions = 10;
const subPoints = Array.from({ length: divisions + 1 }, (_, i) => {
const t = THREE.MathUtils.lerp(t1, t2, i / divisions);
return fullCurve.getPoint(t);
});
return new THREE.CatmullRomCurve3(subPoints);
}, [fullCurve, segmentSize]);
const tubeGeometry = useMemo(
() => new THREE.TubeGeometry(subCurve, 20, arrowRadius, 8, false),
[subCurve, arrowRadius]
);
const arrowPosition = useMemo(() => subCurve.getPoint(1), [subCurve]);
const arrowTangent = useMemo(() => subCurve.getTangent(1).normalize(), [subCurve]);
const arrowRotation = useMemo(() => {
return new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), arrowTangent);
}, [arrowTangent]);
const coneGeometry = useMemo(() => {
const geom = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8);
geom.translate(0, arrowHeadLength / 2, 0);
return geom;
}, [arrowHeadRadius, arrowHeadLength]);
return (
<group name="ArrowWithTube">
<mesh name="ArrowWithTube" geometry={tubeGeometry}>
<meshStandardMaterial color={color} />
</mesh>
<mesh name="ArrowWithTube" position={arrowPosition} quaternion={arrowRotation} geometry={coneGeometry}>
<meshStandardMaterial color={color} />
</mesh>
</group>
);
}

View File

@@ -0,0 +1,362 @@
import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
import { useProductStore } from "../../../../../store/simulation/useProductStore";
import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
import { TransformControls } from "@react-three/drei";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { useSelectedEventSphere, useSelectedEventData, } from "../../../../../store/simulation/useSimulationStore";
import { useThree } from "@react-three/fiber";
import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
import { useProductContext } from "../../../products/productContext";
import { useParams } from "react-router-dom";
function PointsCreator() {
const { gl, raycaster, scene, pointer, camera } = useThree();
const { subModule } = useSubModuleStore();
const { selectedProductStore } = useProductContext();
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
const { getEventByModelUuid: getEventByModelUuidFromProduct, updatePoint: updatePointFromProduct, getEventByModelUuid: getEventByModelUuidFromProduct2, getPointByUuid: getPointByUuidFromProduct } = useProductStore();
const { selectedProduct } = selectedProductStore();
const { activeModule } = useModuleStore();
const transformRef = useRef<any>(null);
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
const { isPlaying } = usePlayButtonStore();
const { projectId } = useParams();
const updateBackend = (
productName: string,
productUuid: string,
projectId: string,
eventData: EventsSchema
) => {
upsertProductOrEventApi({
productName: productName,
productUuid: productUuid,
projectId: projectId,
eventDatas: eventData
})
}
useEffect(() => {
if (selectedEventSphere) {
const eventData = getEventByModelUuid(
selectedEventSphere.userData.modelUuid
);
if (eventData) {
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
} else {
clearSelectedEventData();
}
} else {
clearSelectedEventData();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedEventSphere]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedEventSphere) 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);
}, [selectedEventSphere]);
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
let point: PointsScheme = JSON.parse(
JSON.stringify(
getPointByUuid(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid
)
)
);
if (point) {
point.position = [
selectedEventSphere.position.x,
selectedEventSphere.position.y,
selectedEventSphere.position.z,
];
point.rotation = [
selectedEventSphere.rotation.x,
selectedEventSphere.rotation.y,
selectedEventSphere.rotation.z,
];
const event = getEventByModelUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid);
if (event && selectedProduct.productUuid !== '') {
const updatedPoint = JSON.parse(
JSON.stringify(
getPointByUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid)
)
);
if (updatedPoint) {
updatedPoint.position = point.position;
updatedPoint.rotation = point.rotation;
const updatedEvent = updatePointFromProduct(
selectedProduct.productUuid,
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid,
updatedPoint
)
if (updatedEvent) {
updatePoint(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid,
point
)
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
updatedEvent
);
}
}
}
}
};
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isMouseDown = false;
const onMouseDown = () => {
isMouseDown = true;
drag = false;
};
const onMouseUp = () => {
if (selectedEventSphere && !drag) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter((intersect) => intersect.object.name === "Event-Sphere");
if (intersects.length === 0) {
clearSelectedEventSphere();
setTransformMode(null);
}
}
};
const onMouseMove = () => {
if (isMouseDown) {
drag = true;
}
};
if (subModule === "mechanics") {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [gl, subModule, selectedEventSphere]);
return (
<>
{activeModule === "simulation" && (
<>
<group name="EventPointsGroup" visible={!isPlaying}>
{events.map((event, index) => {
const updatedEvent = selectedProduct.productUuid !== ''
? getEventByModelUuidFromProduct2(selectedProduct.productUuid, event.modelUuid)
: null;
const usedEvent = updatedEvent || event;
if (usedEvent.type === "transfer") {
return (
<group
key={`${index}-${usedEvent.modelUuid}`}
position={usedEvent.position}
rotation={usedEvent.rotation}
>
{usedEvent.points.map((point, j) => (
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
key={`${index}-${point.uuid}`}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: usedEvent.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="orange" />
</mesh>
))}
</group>
);
} else if (usedEvent.type === "vehicle") {
const point = usedEvent.point;
return (
<group
key={`${index}-${usedEvent.modelUuid}`}
position={usedEvent.position}
rotation={usedEvent.rotation}
>
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: usedEvent.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="blue" />
</mesh>
</group>
);
} else if (usedEvent.type === "roboticArm") {
const point = usedEvent.point;
return (
<group
key={`${index}-${usedEvent.modelUuid}`}
position={usedEvent.position}
rotation={usedEvent.rotation}
>
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: usedEvent.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="green" />
</mesh>
</group>
);
} else if (usedEvent.type === "machine") {
const point = usedEvent.point;
return (
<group
key={`${index}-${usedEvent.modelUuid}`}
position={usedEvent.position}
rotation={usedEvent.rotation}
>
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: usedEvent.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="purple" />
</mesh>
</group>
);
} else if (usedEvent.type === "storageUnit") {
const point = usedEvent.point;
return (
<group
key={`${index}-${usedEvent.modelUuid}`}
position={usedEvent.position}
rotation={usedEvent.rotation}
>
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: usedEvent.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="red" />
</mesh>
</group>
);
} else {
return null;
}
})}
</group>
{selectedEventSphere && transformMode && (
<TransformControls
ref={transformRef}
object={selectedEventSphere}
mode={transformMode}
onMouseUp={(e) => {
updatePointToState(selectedEventSphere);
}}
/>
)}
</>
)}
</>
);
}
export default PointsCreator;

View File

@@ -0,0 +1,37 @@
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
interface HandleAddEventToProductParams {
event: EventsSchema | undefined;
addEvent: (productUuid: string, event: EventsSchema) => void;
selectedProduct: {
productUuid: string;
productName: string;
}
clearSelectedAsset?: () => void;
projectId: string;
}
export const handleAddEventToProduct = ({
event,
addEvent,
selectedProduct,
clearSelectedAsset,
projectId
}: HandleAddEventToProductParams) => {
if (event && selectedProduct.productUuid) {
addEvent(selectedProduct.productUuid, event);
upsertProductOrEventApi({
productName: selectedProduct.productName,
productUuid: selectedProduct.productUuid,
projectId: projectId ||'',
eventDatas: event
}).then((data) => {
// console.log(data);
})
if (clearSelectedAsset) {
clearSelectedAsset();
}
}
};

View 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;

View File

@@ -0,0 +1,12 @@
import React from 'react'
import PointsCreator from './creator/pointsCreator'
function Points() {
return (
<>
<PointsCreator />
</>
)
}
export default Points

View File

@@ -0,0 +1,526 @@
import { useEffect, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";
import { useSubModuleStore } from "../../../../store/useModuleStore";
import { useSelectedAction, useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
import { useProductStore } from "../../../../store/simulation/useProductStore";
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
import { handleAddEventToProduct } from "../points/functions/handleAddEventToProduct";
import { QuadraticBezierLine } from "@react-three/drei";
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
import { useDeleteTool } from "../../../../store/builder/store";
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
import { ArrowOnQuadraticBezier, Arrows } from "../arrows/arrows";
import { useProductContext } from "../../products/productContext";
import { useParams } from "react-router-dom";
interface ConnectionLine {
id: string;
startPointUuid: string;
endPointUuid: string;
trigger: TriggerSchema;
}
function TriggerConnector() {
const { gl, raycaster, scene, pointer, camera } = useThree();
const { subModule } = useSubModuleStore();
const { selectedProductStore } = useProductContext();
const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, removeTrigger, addEvent, getEventByModelUuid, getPointUuidByActionUuid, getProductById } = useProductStore();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
const { selectedProduct } = selectedProductStore();
const [hoveredLineKey, setHoveredLineKey] = useState<string | null>(null);
const groupRefs = useRef<Record<string, any>>({});
const [helperlineColor, setHelperLineColor] = useState<string>("red");
const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null);
const { deleteTool } = useDeleteTool();
const { isPlaying } = usePlayButtonStore();
const { selectedAction } = useSelectedAction();
const { projectId } = useParams();
const [firstSelectedPoint, setFirstSelectedPoint] = useState<{
productUuid: string;
modelUuid: string;
pointUuid: string;
actionUuid?: string;
} | null>(null);
const [connections, setConnections] = useState<ConnectionLine[]>([]);
const updateBackend = (
productName: string,
productUuid: string,
projectId: string,
eventData: EventsSchema
) => {
upsertProductOrEventApi({
productName: productName,
productUuid: productUuid,
projectId: projectId,
eventDatas: eventData
})
}
useEffect(() => {
const newConnections: ConnectionLine[] = [];
const product = getProductById(selectedProduct.productUuid);
if (!product || products.length === 0) return;
product.eventDatas.forEach(event => {
// Handle Conveyor points
if (event.type === "transfer" && 'points' in event) {
event.points.forEach(point => {
if (point.action?.triggers) {
point.action.triggers.forEach(trigger => {
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
newConnections.push({
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
startPointUuid: point.uuid,
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
trigger
});
}
});
}
});
}
// Handle Vehicle point
else if (event.type === "vehicle" && 'point' in event) {
const point = event.point;
if (point.action?.triggers) {
point.action.triggers.forEach(trigger => {
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
newConnections.push({
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
startPointUuid: point.uuid,
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
trigger
});
}
});
}
}
// Handle Robotic Arm points
else if (event.type === "roboticArm" && 'point' in event) {
const point = event.point;
point.actions?.forEach(action => {
action.triggers?.forEach(trigger => {
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
newConnections.push({
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
startPointUuid: point.uuid,
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
trigger
});
}
});
});
}
// Handle Machine point
else if (event.type === "machine" && 'point' in event) {
const point = event.point;
if (point.action?.triggers) {
point.action.triggers.forEach(trigger => {
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
newConnections.push({
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
startPointUuid: point.uuid,
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
trigger
});
}
});
}
}
// Handle StorageUnit point
else if (event.type === "storageUnit" && 'point' in event) {
const point = event.point;
if (point.action?.triggers) {
point.action.triggers.forEach(trigger => {
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
newConnections.push({
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
startPointUuid: point.uuid,
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
trigger
});
}
});
}
}
});
setConnections(newConnections);
}, [products, selectedProduct.productUuid]);
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isRightMouseDown = false;
const onMouseDown = (evt: MouseEvent) => {
if (selectedAsset) {
clearSelectedAsset();
}
if (evt.button === 2) {
isRightMouseDown = true;
drag = false;
}
};
const onMouseUp = (evt: MouseEvent) => {
if (evt.button === 2) {
isRightMouseDown = false;
}
}
const onMouseMove = () => {
if (isRightMouseDown) {
drag = true;
}
};
const handleRightClick = (evt: MouseEvent) => {
if (drag) return;
evt.preventDefault();
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter(
(intersect) =>
intersect.object.name === ('Event-Sphere')
);
if (intersects.length === 0) {
setFirstSelectedPoint(null);
return;
};
const currentObject = intersects[0].object;
if (!currentObject || currentObject.name !== 'Event-Sphere') {
setFirstSelectedPoint(null);
return;
};
const modelUuid = currentObject.userData.modelUuid;
const pointUuid = currentObject.userData.pointUuid;
if (firstSelectedPoint && firstSelectedPoint.pointUuid === pointUuid) {
setFirstSelectedPoint(null);
return;
}
if (selectedProduct && getIsEventInProduct(selectedProduct.productUuid, modelUuid)) {
const point = getPointByUuid(
selectedProduct.productUuid,
modelUuid,
pointUuid
);
const event = getEventByModelUuid(selectedProduct.productUuid, modelUuid);
const clickedPointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, selectedAction.actionId || '');
if (!point || !event) {
setFirstSelectedPoint(null);
return;
};
let actionUuid: string | undefined;
if ('action' in point && point.action) {
actionUuid = point.action.actionUuid;
} else if ('actions' in point && point.actions.length === 1) {
actionUuid = point.actions[0].actionUuid;
}
if (!firstSelectedPoint) {
if (point.uuid !== clickedPointUuid) return;
setFirstSelectedPoint({
productUuid: selectedProduct.productUuid,
modelUuid,
pointUuid,
actionUuid: selectedAction.actionId || ''
});
} else {
const trigger: TriggerSchema = {
triggerUuid: THREE.MathUtils.generateUUID(),
triggerName: `Trigger ${firstSelectedPoint.pointUuid.slice(0, 4)}${pointUuid.slice(0, 4)}`,
triggerType: "onComplete",
delay: 0,
triggeredAsset: {
triggeredModel: {
modelName: event.modelName || 'Unknown',
modelUuid: modelUuid
},
triggeredPoint: {
pointName: 'Point',
pointUuid: point.uuid
},
triggeredAction: actionUuid ? {
actionName: getActionByUuid(selectedProduct.productUuid, actionUuid)?.actionName || 'Action',
actionUuid: actionUuid
} : null
}
};
if (firstSelectedPoint.actionUuid) {
const event = addTrigger(selectedProduct.productUuid, firstSelectedPoint.actionUuid, trigger);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
}
setFirstSelectedPoint(null);
}
} else if (!getIsEventInProduct(selectedProduct.productUuid, modelUuid) && firstSelectedPoint) {
handleAddEventToProduct({
event: useEventsStore.getState().getEventByModelUuid(modelUuid),
addEvent,
selectedProduct,
projectId: projectId || ''
})
const point = getPointByUuid(
selectedProduct.productUuid,
modelUuid,
pointUuid
);
const event = getEventByModelUuid(selectedProduct.productUuid, modelUuid);
if (!point || !event) {
setFirstSelectedPoint(null);
return;
};
let actionUuid: string | undefined;
if ('action' in point && point.action) {
actionUuid = point.action.actionUuid;
} else if ('actions' in point && point.actions.length === 1) {
actionUuid = point.actions[0].actionUuid;
}
const trigger: TriggerSchema = {
triggerUuid: THREE.MathUtils.generateUUID(),
triggerName: `Trigger ${firstSelectedPoint.pointUuid.slice(0, 4)}${pointUuid.slice(0, 4)}`,
triggerType: "onComplete",
delay: 0,
triggeredAsset: {
triggeredModel: {
modelName: event.modelName || 'Unknown',
modelUuid: modelUuid
},
triggeredPoint: {
pointName: 'Point',
pointUuid: point.uuid
},
triggeredAction: actionUuid ? {
actionName: getActionByUuid(selectedProduct.productUuid, actionUuid)?.actionName || 'Action',
actionUuid: actionUuid
} : null
}
};
if (firstSelectedPoint.actionUuid) {
const event = addTrigger(selectedProduct.productUuid, firstSelectedPoint.actionUuid, trigger);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
}
setFirstSelectedPoint(null);
} else if (firstSelectedPoint) {
setFirstSelectedPoint(null);
}
};
if (subModule === 'mechanics' && !deleteTool && selectedAction.actionId && selectedAction.actionName) {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener('contextmenu', handleRightClick);
} else {
setFirstSelectedPoint(null);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener('contextmenu', handleRightClick);
};
}, [gl, subModule, selectedProduct, firstSelectedPoint, deleteTool, selectedAction]);
useFrame(() => {
if (firstSelectedPoint) {
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.name.includes("zonePlane") &&
!intersect.object.name.includes("SelectionGroup") &&
!intersect.object.name.includes("selectionAssetGroup") &&
!intersect.object.name.includes("SelectionGroupBoundingBoxLine") &&
!intersect.object.name.includes("SelectionGroupBoundingBox") &&
!intersect.object.name.includes("SelectionGroupBoundingLine") &&
intersect.object.type !== "GridHelper" &&
!intersect.object.name.includes("ArrowWithTube") &&
!intersect.object.parent?.name.includes("Zone") &&
intersect.object.type !== "Line2"
);
let point: THREE.Vector3 | null = null;
if (intersects.length > 0) {
point = intersects[0].point;
if (point.y < 0.05) {
point = new THREE.Vector3(point.x, 0.05, point.z);
}
}
const sphereIntersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.name === ('Event-Sphere'));
if (sphereIntersects.length > 0 && sphereIntersects[0].object.uuid === firstSelectedPoint.pointUuid) {
setHelperLineColor('red');
setCurrentLine(null);
return;
}
const startPoint = getWorldPositionFromScene(firstSelectedPoint.pointUuid);
if (point && startPoint) {
if (sphereIntersects.length > 0) {
point = sphereIntersects[0].object.getWorldPosition(new THREE.Vector3());
}
const distance = startPoint.distanceTo(point);
const heightFactor = Math.max(0.5, distance * 0.2);
const midPoint = new THREE.Vector3(
(startPoint.x + point.x) / 2,
Math.max(startPoint.y, point.y) + heightFactor,
(startPoint.z + point.z) / 2
);
const endPoint = point;
setCurrentLine({
start: startPoint,
mid: midPoint,
end: endPoint,
});
setHelperLineColor(sphereIntersects.length > 0 ? "#6cf542" : "red");
} else {
setCurrentLine(null);
}
} else {
setCurrentLine(null);
}
})
const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => {
const pointObj = scene.getObjectByProperty("uuid", pointUuid);
if (!pointObj) return null;
const worldPosition = new THREE.Vector3();
pointObj.getWorldPosition(worldPosition);
return worldPosition;
};
const removeConnection = (connection: ConnectionLine) => {
if (connection.trigger.triggerUuid) {
const event = removeTrigger(selectedProduct.productUuid, connection.trigger.triggerUuid);
if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
projectId || '',
event
);
}
}
};
return (
<group name="simulationConnectionGroup" visible={!isPlaying}>
{connections.map((connection) => {
const startPoint = getWorldPositionFromScene(connection.startPointUuid);
const endPoint = getWorldPositionFromScene(connection.endPointUuid);
if (!startPoint || !endPoint) return null;
const distance = startPoint.distanceTo(endPoint);
const heightFactor = Math.max(0.5, distance * 0.2);
const midPoint = new THREE.Vector3(
(startPoint.x + endPoint.x) / 2,
Math.max(startPoint.y, endPoint.y) + heightFactor,
(startPoint.z + endPoint.z) / 2
);
return (
<QuadraticBezierLine
key={connection.id}
ref={(el) => (groupRefs.current[connection.id] = el!)}
start={startPoint.toArray()}
end={endPoint.toArray()}
mid={midPoint.toArray()}
color={deleteTool && hoveredLineKey === connection.id ? "red" : "#42a5f5"}
lineWidth={4}
dashed={deleteTool && hoveredLineKey === connection.id ? false : true}
dashSize={0.75}
dashScale={20}
onPointerOver={() => setHoveredLineKey(connection.id)}
onPointerOut={() => setHoveredLineKey(null)}
onClick={() => {
if (deleteTool) {
setHoveredLineKey(null);
setCurrentLine(null);
removeConnection(connection);
}
}}
userData={connection.trigger}
/>
);
})}
<Arrows connections={connections} />
{currentLine && (
<>
<QuadraticBezierLine
start={currentLine.start.toArray()}
end={currentLine.end.toArray()}
mid={currentLine.mid.toArray()}
color={helperlineColor}
lineWidth={4}
dashed
dashSize={1}
dashScale={20}
/>
<ArrowOnQuadraticBezier
start={currentLine.start.toArray()}
mid={currentLine.mid.toArray()}
end={currentLine.end.toArray()}
color={helperlineColor}
/>
</>
)}
</group>
);
}
export default TriggerConnector;