backend intgration and file export function

This commit is contained in:
2025-09-06 18:10:32 +05:30
parent 5fd1c3bb89
commit cbaad3d546
7 changed files with 516 additions and 188 deletions

View File

@@ -3,126 +3,223 @@ import { useEffect, useMemo, useRef, useCallback } from "react";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { useHeatMapStore } from "../../../store/simulation/useHeatMapStore"; import { useHeatMapStore } from "../../../store/simulation/useHeatMapStore";
import * as CONSTANTS from "../../../types/world/worldConstants"; import * as CONSTANTS from "../../../types/world/worldConstants";
import { exportHeatmapAsPNG } from "../functions/exportHeatmapAsPNG"; import { Html } from "@react-three/drei";
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
import { useSceneContext } from "../../../modules/scene/sceneContext";
// Constants
const RADIUS = 0.0025; const RADIUS = 0.0025;
const OPACITY = 0.8; const OPACITY = 0.8;
const GROWTH_RATE = 20.0; const GROWTH_RATE = 20.0;
// 🔹 React Component
const BakedHeatMap = () => { const BakedHeatMap = () => {
const { materialStore } = useSceneContext();
const { bakedPoints } = useHeatMapStore(); const { bakedPoints } = useHeatMapStore();
const materialRef = useRef<THREE.ShaderMaterial>(null); const materialRef = useRef<THREE.ShaderMaterial>(null);
const meshRef = useRef<THREE.Mesh>(null); const meshRef = useRef<THREE.Mesh>(null);
const { gl } = useThree(); const { gl } = useThree();
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const height = CONSTANTS.gridConfig.size; const height = CONSTANTS.gridConfig.size;
const width = CONSTANTS.gridConfig.size; const width = CONSTANTS.gridConfig.size;
const { materialHistory, materials } = materialStore();
const pointTexture = useMemo(() => { const createPointTexture = useCallback(
if (bakedPoints.length === 0) return null; (filteredPoints: typeof bakedPoints) => {
const data = new Float32Array(bakedPoints.length * 4); if (filteredPoints.length === 0) return null;
bakedPoints.forEach((p, i) => {
const data = new Float32Array(filteredPoints.length * 4);
filteredPoints.forEach((p, i) => {
const index = i * 4; const index = i * 4;
data[index] = (p.points.x + width / 2) / width; data[index] = (p.points.x + width / 2) / width;
data[index + 1] = (p.points.y + height / 2) / height; data[index + 1] = (p.points.y + height / 2) / height;
data[index + 2] = 0.3; data[index + 2] = 0.3;
data[index + 3] = 0.0; data[index + 3] = 0.0;
}); });
const texture = new THREE.DataTexture(data, bakedPoints.length, 1, THREE.RGBAFormat, THREE.FloatType);
const texture = new THREE.DataTexture(data, filteredPoints.length, 1, THREE.RGBAFormat, THREE.FloatType);
texture.needsUpdate = true; texture.needsUpdate = true;
return texture; return texture;
}, [bakedPoints, width, height]); },
[width, height]
);
const uniformsRef = useRef({ const uniformsRef = useRef({
u_points: { value: pointTexture }, u_points: { value: null as THREE.DataTexture | null },
u_count: { value: bakedPoints.length }, u_count: { value: 0 },
u_radius: { value: RADIUS }, u_radius: { value: RADIUS },
u_opacity: { value: OPACITY }, u_opacity: { value: OPACITY },
u_growthRate: { value: GROWTH_RATE }, u_growthRate: { value: GROWTH_RATE },
}); });
const renderHeatmapToImage = useCallback(
(type: string) => {
if (!meshRef.current) return null;
const filteredPoints = bakedPoints.filter((p) => p.type === type);
if (filteredPoints.length === 0) return null;
const pointTexture = createPointTexture(filteredPoints);
if (!pointTexture) return null;
uniformsRef.current.u_points.value = pointTexture;
uniformsRef.current.u_count.value = filteredPoints.length;
const exportCamera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0.1, 10);
exportCamera.position.set(0, 1, 0);
exportCamera.lookAt(0, 0, 0);
const renderTarget = new THREE.WebGLRenderTarget(1024, 1024, {
format: THREE.RGBAFormat,
type: THREE.UnsignedByteType,
});
const tempScene = new THREE.Scene();
tempScene.add(meshRef.current);
gl.setRenderTarget(renderTarget);
gl.render(tempScene, exportCamera);
gl.setRenderTarget(null);
const pixels = new Uint8Array(1024 * 1024 * 4);
gl.readRenderTargetPixels(renderTarget, 0, 0, 1024, 1024, pixels);
const canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 1024;
const ctx = canvas.getContext("2d");
if (ctx) {
const imageData = ctx.createImageData(1024, 1024);
imageData.data.set(pixels);
ctx.putImageData(imageData, 0, 0);
return canvas.toDataURL("image/png");
}
return null;
},
[gl, width, height, bakedPoints, createPointTexture]
);
const downloadImage = (base64: string, filename: string) => {
const link = document.createElement("a");
link.href = base64;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const exportHeatmapAsPNG = useCallback(() => {
const types = ["human", "vehicle"];
const result = types.map((type) => {
const image = renderHeatmapToImage(type);
if (image) {
downloadImage(image, `${type}-heatmap.png`);
}
return { type, image };
});
console.log("Exported Heatmaps:", result);
return result;
}, [renderHeatmapToImage]);
useEffect(() => {
if (materials.length === 0 && materialHistory.length >= 0) {
console.log("SimulationCompleted");
// exportHeatmapAsPNG();
}
}, [isPlaying, materials, materialHistory]);
const pointTexture = useMemo(() => createPointTexture(bakedPoints), [bakedPoints, createPointTexture]);
useEffect(() => { useEffect(() => {
uniformsRef.current.u_points.value = pointTexture; uniformsRef.current.u_points.value = pointTexture;
uniformsRef.current.u_count.value = bakedPoints.length; uniformsRef.current.u_count.value = bakedPoints.length;
}, [pointTexture, bakedPoints.length]); }, [pointTexture, bakedPoints.length]);
useEffect(() => {
if (meshRef.current) {
exportHeatmapAsPNG({
bakedPoints,
gl,
width: width,
height: height,
mesh: meshRef.current,
});
}
}, []);
return ( return (
<></> <>
// <mesh ref={meshRef} rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}> <mesh ref={meshRef} rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
// <planeGeometry args={[width, height]} /> <planeGeometry args={[width, height]} />
// <shaderMaterial <shaderMaterial
// ref={materialRef} ref={materialRef}
// transparent transparent
// depthWrite={false} depthWrite={false}
// blending={THREE.AdditiveBlending} blending={THREE.AdditiveBlending}
// uniforms={uniformsRef.current} uniforms={uniformsRef.current}
// side={THREE.DoubleSide} side={THREE.DoubleSide}
// vertexShader={` vertexShader={`
// varying vec2 vUv; varying vec2 vUv;
// void main() { void main() {
// vUv = uv; vUv = uv;
// gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
// } }
// `} `}
// fragmentShader={` fragmentShader={`
// uniform sampler2D u_points; uniform sampler2D u_points;
// precision highp float; precision highp float;
// uniform int u_count; uniform int u_count;
// uniform float u_radius; uniform float u_radius;
// uniform float u_opacity; uniform float u_opacity;
// uniform float u_growthRate; uniform float u_growthRate;
// varying vec2 vUv; varying vec2 vUv;
// float gauss(float dist, float radius) { float gauss(float dist, float radius) {
// return exp(-pow(dist / radius, 2.0)); return exp(-pow(dist / radius, 2.0));
// } }
// void main() { void main() {
// float intensity = 0.0; float intensity = 0.0;
// for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
// if (i >= u_count) break; if (i >= u_count) break;
// float fi = float(i) + 0.5; float fi = float(i) + 0.5;
// float u = fi / float(u_count); float u = fi / float(u_count);
// vec4 point = texture2D(u_points, vec2(u, 0.5)); vec4 point = texture2D(u_points, vec2(u, 0.5));
// vec2 pos = point.rg; vec2 pos = point.rg;
// float strength = point.b; float strength = point.b;
// float d = distance(vUv, pos); float d = distance(vUv, pos);
// intensity += strength * gauss(d, u_radius); intensity += strength * gauss(d, u_radius);
// } }
// float normalized = clamp(intensity / max(u_growthRate, 0.0001), 0.0, 1.0); float normalized = clamp(intensity / max(u_growthRate, 0.0001), 0.0, 1.0);
// vec3 color = vec3(0.0); vec3 color = vec3(0.0);
// if (normalized < 0.33) { if (normalized < 0.33) {
// color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), normalized / 0.33); color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), normalized / 0.33);
// } else if (normalized < 0.66) { } else if (normalized < 0.66) {
// color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), (normalized - 0.33) / 0.33); color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), (normalized - 0.33) / 0.33);
// } else { } else {
// color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (normalized - 0.66) / 0.34); color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (normalized - 0.66) / 0.34);
// } }
// gl_FragColor = vec4(color, normalized * u_opacity); gl_FragColor = vec4(color, normalized * u_opacity);
// } }
// `} `}
// /> />
// </mesh> </mesh>
{/* <Html>
<button
style={{
position: "absolute",
top: "10px",
left: "10px",
padding: "8px 12px",
background: "#333",
color: "white",
border: "none",
cursor: "pointer",
zIndex: 10,
}}
onClick={exportHeatmapAsPNG}
>
Export Heatmap
</button>
</Html> */}
</>
); );
}; };

View File

@@ -1,11 +1,34 @@
import * as THREE from "three"; import { AdditiveBlending, DataTexture, DoubleSide, FloatType, Mesh, OrthographicCamera, PlaneGeometry, RGBAFormat, Scene, ShaderMaterial, UnsignedByteType, WebGLRenderer, WebGLRenderTarget } from "three";
const RADIUS = 0.0025; const RADIUS = 0.0025;
const OPACITY = 0.8; const OPACITY = 0.8;
const GROWTH_RATE = 20.0; const GROWTH_RATE = 20.0;
export function exportHeatmapAsPNG({ bakedPoints, gl, width, height, mesh }: { bakedPoints: any[]; gl: THREE.WebGLRenderer; width: number; height: number; mesh: THREE.Mesh }) { interface BakedPoint {
const createPointTexture = (filteredPoints: typeof bakedPoints) => { type: string;
points: { x: number; y: number };
}
interface ExportResult {
type: string;
image: Blob | null; // Now returns a Blob
}
interface ExportHeatmapParams {
bakedPoints: BakedPoint[];
gl: WebGLRenderer;
width: number;
height: number;
}
/**
* Export heatmaps as PNG images for each type ("human", "vehicle")
*/
export function exportHeatmapAsPNG({ bakedPoints, gl, width, height }: ExportHeatmapParams): ExportResult[] {
if (!bakedPoints || bakedPoints.length === 0) return [];
// Create a DataTexture from filtered points
const createPointTexture = (filteredPoints: BakedPoint[]): DataTexture | null => {
if (filteredPoints.length === 0) return null; if (filteredPoints.length === 0) return null;
const data = new Float32Array(filteredPoints.length * 4); const data = new Float32Array(filteredPoints.length * 4);
@@ -13,82 +36,154 @@ export function exportHeatmapAsPNG({ bakedPoints, gl, width, height, mesh }: { b
const index = i * 4; const index = i * 4;
data[index] = (p.points.x + width / 2) / width; data[index] = (p.points.x + width / 2) / width;
data[index + 1] = (p.points.y + height / 2) / height; data[index + 1] = (p.points.y + height / 2) / height;
data[index + 2] = 0.3; data[index + 2] = 0.3; // intensity
data[index + 3] = 0.0; data[index + 3] = 0.0;
}); });
const texture = new THREE.DataTexture(data, filteredPoints.length, 1, THREE.RGBAFormat, THREE.FloatType); const texture = new DataTexture(data, filteredPoints.length, 1, RGBAFormat, FloatType);
texture.needsUpdate = true; texture.needsUpdate = true;
return texture; return texture;
}; };
const downloadImage = (base64: string, filename: string) => { // Create heatmap rendering shader material
const link = document.createElement("a"); const createHeatmapMaterial = (pointsTexture: DataTexture, count: number): ShaderMaterial => {
link.href = base64; return new ShaderMaterial({
link.download = filename; transparent: true,
document.body.appendChild(link); depthWrite: false,
link.click(); blending: AdditiveBlending,
document.body.removeChild(link); side: DoubleSide,
}; uniforms: {
u_points: { value: pointsTexture },
const renderHeatmapToImage = (type: string) => { u_count: { value: count },
const filteredPoints = bakedPoints.filter((p) => p.type === type);
if (filteredPoints.length === 0) return null;
const pointTexture = createPointTexture(filteredPoints);
if (!pointTexture) return null;
const uniforms = {
u_points: { value: pointTexture },
u_count: { value: filteredPoints.length },
u_radius: { value: RADIUS }, u_radius: { value: RADIUS },
u_opacity: { value: OPACITY }, u_opacity: { value: OPACITY },
u_growthRate: { value: GROWTH_RATE }, u_growthRate: { value: GROWTH_RATE },
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D u_points;
precision highp float;
uniform int u_count;
uniform float u_radius;
uniform float u_opacity;
uniform float u_growthRate;
varying vec2 vUv;
float gauss(float dist, float radius) {
return exp(-pow(dist / radius, 2.0));
}
void main() {
float intensity = 0.0;
for (int i = 0; i < 10000; i++) {
if (i >= u_count) break;
float fi = float(i) + 0.5;
float u = fi / float(u_count);
vec4 point = texture2D(u_points, vec2(u, 0.5));
vec2 pos = point.rg;
float strength = point.b;
float d = distance(vUv, pos);
intensity += strength * gauss(d, u_radius);
}
float normalized = clamp(intensity / max(u_growthRate, 0.0001), 0.0, 1.0);
vec3 color = vec3(0.0);
if (normalized < 0.33) {
color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), normalized / 0.33);
} else if (normalized < 0.66) {
color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), (normalized - 0.33) / 0.33);
} else {
color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (normalized - 0.66) / 0.34);
}
gl_FragColor = vec4(color, normalized * u_opacity);
}
`,
});
}; };
const exportCamera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0.1, 10); /**
* Renders the heatmap for a given type and returns it as a Blob
*/
const renderHeatmapToImage = (type: string): Blob | null => {
const filteredPoints = bakedPoints.filter((p) => p.type === type);
if (filteredPoints.length === 0) return null;
const pointsTexture = createPointTexture(filteredPoints);
if (!pointsTexture) return null;
const material = createHeatmapMaterial(pointsTexture, filteredPoints.length);
const mesh = new Mesh(new PlaneGeometry(width, height), material);
mesh.rotation.x = Math.PI / 2;
mesh.position.set(0, 0.025, 0);
const exportCamera = new OrthographicCamera(
width / -2,
width / 2,
height / 2,
height / -2,
0.1,
10
);
exportCamera.position.set(0, 1, 0); exportCamera.position.set(0, 1, 0);
exportCamera.lookAt(0, 0, 0); exportCamera.lookAt(0, 0, 0);
const renderTarget = new THREE.WebGLRenderTarget(1024, 1024, { const renderTarget = new WebGLRenderTarget(1024, 1024, {
format: THREE.RGBAFormat, format: RGBAFormat,
type: THREE.UnsignedByteType, type: UnsignedByteType,
}); });
const tempScene = new THREE.Scene(); const tempScene = new Scene();
tempScene.add(mesh); tempScene.add(mesh);
// Render heatmap
gl.setRenderTarget(renderTarget); gl.setRenderTarget(renderTarget);
gl.render(tempScene, exportCamera); gl.render(tempScene, exportCamera);
gl.setRenderTarget(null); gl.setRenderTarget(null);
// Read pixels
const pixels = new Uint8Array(1024 * 1024 * 4); const pixels = new Uint8Array(1024 * 1024 * 4);
gl.readRenderTargetPixels(renderTarget, 0, 0, 1024, 1024, pixels); gl.readRenderTargetPixels(renderTarget, 0, 0, 1024, 1024, pixels);
// Convert to Blob
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = 1024; canvas.width = 1024;
canvas.height = 1024; canvas.height = 1024;
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
if (ctx) { if (!ctx) return null;
const imageData = ctx.createImageData(1024, 1024); const imageData = ctx.createImageData(1024, 1024);
imageData.data.set(pixels); imageData.data.set(pixels);
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
return canvas.toDataURL("image/png");
// Convert canvas to Blob
const dataURL = canvas.toDataURL("image/png");
const byteString = atob(dataURL.split(",")[1]);
const arrayBuffer = new ArrayBuffer(byteString.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
uint8Array[i] = byteString.charCodeAt(i);
} }
return null; return new Blob([arrayBuffer], { type: "image/png" });
}; };
const types = ["human", "vehicle"]; const types = ["human", "vehicle"];
const result = types.map((type) => { return types.map((type) => ({
const image = renderHeatmapToImage(type); type,
if (image) { image: renderHeatmapToImage(type),
downloadImage(image, `${type}-heatmap.png`); }));
}
return { type, image };
});
console.log("Exported Heatmaps:", result);
return result;
} }

View File

@@ -14,7 +14,7 @@ function HeatMap() {
<> <>
<RealTimeHeatMap /> <RealTimeHeatMap />
<BakedHeatMap /> {/* <BakedHeatMap /> */}
</> </>
); );
} }

View File

@@ -59,10 +59,34 @@ const RealTimeHeatMap = () => {
} }
}, [isReset, isPlaying]); }, [isReset, isPlaying]);
useEffect(() => { // useEffect(() => {
addMonitoringVehicle("26770368-55e8-4d40-87f7-8eacb48dc236"); // addMonitoringVehicle("26770368-55e8-4d40-87f7-8eacb48dc236");
addMonitoringHuman("264a51e7-d8b9-4093-95ac-fa7e2dc49cfa"); // addMonitoringHuman("264a51e7-d8b9-4093-95ac-fa7e2dc49cfa");
}, []); // }, []);
// useEffect(() => {
// const selectedProductData = getProductById(selectedProduct.productUuid);
// const newEvents: EventsSchema[] = [];
// if (selectedProductData) {
// determineExecutionMachineSequences([selectedProductData]).then((sequences) => {
// sequences.forEach((sequence) => {
// sequence.forEach((event) => {
// if (event.type === "human") {
// if (hasHuman(event.modelUuid)) {
// newEvents.push(event);
// }
// } else if (event.type === "vehicle") {
// if (hasVehicle(event.modelUuid)) {
// newEvents.push(event);
// }
// }
// });
// });
// setEvents(newEvents);
// });
// }
// }, [selectedProduct, products, monitoringHuman, monitoringVehicle]);
useEffect(() => { useEffect(() => {
const selectedProductData = getProductById(selectedProduct.productUuid); const selectedProductData = getProductById(selectedProduct.productUuid);
@@ -72,15 +96,9 @@ const RealTimeHeatMap = () => {
determineExecutionMachineSequences([selectedProductData]).then((sequences) => { determineExecutionMachineSequences([selectedProductData]).then((sequences) => {
sequences.forEach((sequence) => { sequences.forEach((sequence) => {
sequence.forEach((event) => { sequence.forEach((event) => {
if (event.type === "human") { if (event.type === "human" || event.type === "vehicle") {
if (hasHuman(event.modelUuid)) {
newEvents.push(event); newEvents.push(event);
} }
} else if (event.type === "vehicle") {
if (hasVehicle(event.modelUuid)) {
newEvents.push(event);
}
}
}); });
}); });
setEvents(newEvents); setEvents(newEvents);

View File

@@ -1,22 +1,22 @@
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { useSceneContext } from '../../scene/sceneContext'; import { useSceneContext } from "../../scene/sceneContext";
import { determineExecutionMachineSequences } from './functions/determineExecutionMachineSequences'; import { determineExecutionMachineSequences } from "./functions/determineExecutionMachineSequences";
import { usePlayButtonStore } from '../../../store/ui/usePlayButtonStore'; import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
import { useSimulationManager } from '../../../store/rough/useSimulationManagerStore'; import { useSimulationManager } from "../../../store/rough/useSimulationManagerStore";
import { useParams } from 'react-router-dom'; import { useParams } from "react-router-dom";
import { exportHeatmapAsPNG } from "../../../components/heatMapGenerator/functions/exportHeatmapAsPNG";
import { WebGLRenderer } from "three";
import { useHeatMapStore } from "../../../store/simulation/useHeatMapStore";
import { useThree } from "@react-three/fiber";
import * as CONSTANTS from "../../../types/world/worldConstants";
import { heatMapImageApi } from "../../../services/simulation/products/heatMapImageApi";
import { generateHeatmapOutput } from "./functions/generateHeatmapOutput";
interface SimulationUsageRecord { interface SimulationUsageRecord {
activeTime: number; activeTime: number;
isActive: boolean; isActive: boolean;
idleTime: number; idleTime: number;
type: type: "roboticArm" | "vehicle" | "transfer" | "storageUnit" | "crane" | "human" | "machine";
| "roboticArm"
| "vehicle"
| "transfer"
| "storageUnit"
| "crane"
| "human"
| "machine";
} }
// Product → holds multiple usage records // Product → holds multiple usage records
@@ -45,15 +45,16 @@ const SimulationHandler = () => {
const { getProductById, selectedProduct } = productStore(); const { getProductById, selectedProduct } = productStore();
const { machines, getMachineById } = machineStore(); const { machines, getMachineById } = machineStore();
const { getHumanById } = humanStore(); const { getHumanById } = humanStore();
const { getCraneById, } = craneStore(); const { getCraneById } = craneStore();
const { getStorageUnitById } = storageUnitStore(); const { getStorageUnitById } = storageUnitStore();
const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { simulationData, addData } = useSimulationManager(); const { simulationData, addData } = useSimulationManager();
const { projectId } = useParams(); const { projectId } = useParams();
const { selectedVersion } = versionStore(); const { selectedVersion } = versionStore();
const { bakedPoints } = useHeatMapStore();
const height = CONSTANTS.gridConfig.size;
const width = CONSTANTS.gridConfig.size;
const { gl } = useThree();
useEffect(() => { useEffect(() => {
let checkTimer: ReturnType<typeof setTimeout>; let checkTimer: ReturnType<typeof setTimeout>;
@@ -65,39 +66,39 @@ const SimulationHandler = () => {
if (currentProduct) { if (currentProduct) {
const executionSequences = await determineExecutionMachineSequences([currentProduct]); const executionSequences = await determineExecutionMachineSequences([currentProduct]);
if (executionSequences?.length > 0) { if (executionSequences?.length > 0) {
executionSequences.forEach(sequence => { executionSequences.forEach((sequence) => {
sequence.forEach(entity => { sequence.forEach((entity) => {
if (entity.type === 'roboticArm') { if (entity.type === "roboticArm") {
const roboticArm = getArmBotById(entity.modelUuid); const roboticArm = getArmBotById(entity.modelUuid);
if (roboticArm?.isActive) { if (roboticArm?.isActive) {
hasActiveEntity = true; hasActiveEntity = true;
} }
} }
if (entity.type === 'vehicle') { if (entity.type === "vehicle") {
const vehicle = getVehicleById(entity.modelUuid); const vehicle = getVehicleById(entity.modelUuid);
if (vehicle?.isActive) { if (vehicle?.isActive) {
hasActiveEntity = true; hasActiveEntity = true;
} }
} }
if (entity.type === 'machine') { if (entity.type === "machine") {
const machine = getMachineById(entity.modelUuid); const machine = getMachineById(entity.modelUuid);
if (machine?.isActive) { if (machine?.isActive) {
hasActiveEntity = true; hasActiveEntity = true;
} }
} }
if (entity.type === 'human') { if (entity.type === "human") {
const human = getHumanById(entity.modelUuid); const human = getHumanById(entity.modelUuid);
if (human?.isActive) { if (human?.isActive) {
hasActiveEntity = true; hasActiveEntity = true;
} }
} }
if (entity.type === 'crane') { if (entity.type === "crane") {
const crane = getCraneById(entity.modelUuid); const crane = getCraneById(entity.modelUuid);
if (crane?.isActive) { if (crane?.isActive) {
hasActiveEntity = true; hasActiveEntity = true;
} }
} }
if (entity.type === 'storageUnit') { if (entity.type === "storageUnit") {
const storageUnit = getStorageUnitById(entity.modelUuid); const storageUnit = getStorageUnitById(entity.modelUuid);
if (storageUnit?.isActive) { if (storageUnit?.isActive) {
hasActiveEntity = true; hasActiveEntity = true;
@@ -114,6 +115,7 @@ const SimulationHandler = () => {
} }
if (materials.length === 0 && materialHistory.length >= 0 && !hasActiveEntity) { if (materials.length === 0 && materialHistory.length >= 0 && !hasActiveEntity) {
let bakedResult = generateHeatmapOutput({ bakedPoints, gl, width, height, outputType: "url" });
if (executionSequences?.length > 0) { if (executionSequences?.length > 0) {
executionSequences.forEach((sequence) => { executionSequences.forEach((sequence) => {
@@ -134,24 +136,14 @@ const SimulationHandler = () => {
const obj = getter(entity.modelUuid); const obj = getter(entity.modelUuid);
if (!obj) return; // skip if not found if (!obj) return; // skip if not found
addData( addData(projectId, selectedVersion?.versionId || "", selectedProduct?.productUuid, {
projectId,
selectedVersion?.versionId || "",
selectedProduct?.productUuid,
{
activeTime: obj.activeTime ?? 0, activeTime: obj.activeTime ?? 0,
isActive: obj.isActive ?? false, isActive: obj.isActive ?? false,
idleTime: obj.idleTime ?? 0, idleTime: obj.idleTime ?? 0,
type: entity.type as type: entity.type as "roboticArm" | "vehicle" | "machine" | "human" | "crane" | "storageUnit" | "transfer",
| "roboticArm" });
| "vehicle"
| "machine" heatMapImageApi(projectId || "", selectedVersion?.versionId || "", selectedProduct?.productUuid, bakedResult);
| "human"
| "crane"
| "storageUnit"
| "transfer",
}
);
}); });
}); });
} }
@@ -172,6 +164,6 @@ const SimulationHandler = () => {
}, [materials, materialHistory, selectedVersion, selectedProduct?.productUuid, isPlaying, armBots, vehicles, machines]); }, [materials, materialHistory, selectedVersion, selectedProduct?.productUuid, isPlaying, armBots, vehicles, machines]);
return null; return null;
} };
export default SimulationHandler; export default SimulationHandler;

View File

@@ -0,0 +1,90 @@
import { WebGLRenderer } from "three";
import { exportHeatmapAsPNG } from "../../../../components/heatMapGenerator/functions/exportHeatmapAsPNG";
// Type for a single baked point
interface BakedPoint {
type: string;
points: { x: number; y: number };
}
// Heatmap item returned by exportHeatmapAsPNG
interface ExportedHeatmap {
type: string;
image: Blob | null;
}
// Output types
interface HeatmapUrlResult {
type: string;
url: string;
}
interface HeatmapFileResult {
type: string;
file: File;
}
type OutputType = "url" | "file" | "blob";
interface GenerateHeatmapOutputParams {
bakedPoints: BakedPoint[];
gl: WebGLRenderer;
width: number;
height: number;
outputType?: OutputType;
download?: boolean; // <-- NEW PARAM
}
/**
* Generates heatmap output as either a File or a URL.
* If `download` is true, automatically triggers file download.
*/
export function generateHeatmapOutput({ bakedPoints, gl, width, height, outputType = "file", download = false }: GenerateHeatmapOutputParams): (HeatmapUrlResult | HeatmapFileResult)[] {
const bakedResult: ExportedHeatmap[] = exportHeatmapAsPNG({ bakedPoints, gl, width, height });
return bakedResult
.map((item) => {
if (!item.image) return null;
const fileName = `${item.type}-heatmap.png`;
if (outputType === "file") {
const file = new File([item.image], fileName, { type: "image/png" });
// If download flag is true, trigger download
if (download) {
const url = URL.createObjectURL(file);
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
return { type: item.type, file } as HeatmapFileResult;
} else if (outputType === "url") {
const url = URL.createObjectURL(item.image);
// If download flag is true, trigger download
if (download) {
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// We don't revoke here immediately, or the download may fail
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
return { type: item.type, url } as HeatmapUrlResult;
} else if (outputType === "blob") {
return bakedResult;
}
throw new Error("Invalid outputType. Use 'url' or 'file'.");
})
.filter((result): result is HeatmapUrlResult | HeatmapFileResult => result !== null);
}

View File

@@ -0,0 +1,36 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
export const heatMapImageApi = async (projectId: string, versionId: string, productUuid: string,heatmaps:any) => {
console.log('heatmaps: ', heatmaps);
console.log('productUuid: ', productUuid);
console.log('versionId: ', versionId);
console.log('projectId: ', projectId);
try {
const response = await fetch(`${url_Backend_dwinzo}/api/V1/SimulatedImage`, {
method: "PATCH",
headers: {
Authorization: "Bearer <access_token>",
"Content-Type": "application/json",
token: localStorage.getItem("token") || "",
refresh_token: localStorage.getItem("refreshToken") || "",
},
body: JSON.stringify({ projectId, versionId, productUuid ,heatmaps}),
});
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) {
echo.error("Failed to delete event data");
}
const result = await response.json();
console.log('result: ', result);
return result;
} catch {
echo.error("Failed to delete event data");
}
};