From cbaad3d54685175d0cfb50762eb56a66de2203fb Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Sat, 6 Sep 2025 18:10:32 +0530 Subject: [PATCH 1/3] backend intgration and file export function --- .../heatMapGenerator/baked/bakedHeatMap.tsx | 269 ++++++++++++------ .../functions/exportHeatmapAsPNG.ts | 181 +++++++++--- .../components/heatMapGenerator/heatMap.tsx | 2 +- .../realTime/realTimeHeatMap.tsx | 44 ++- .../simulator/SimulationHandler.tsx | 82 +++--- .../functions/generateHeatmapOutput.ts | 90 ++++++ .../simulation/products/heatMapImageApi.ts | 36 +++ 7 files changed, 516 insertions(+), 188 deletions(-) create mode 100644 app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts create mode 100644 app/src/services/simulation/products/heatMapImageApi.ts diff --git a/app/src/components/heatMapGenerator/baked/bakedHeatMap.tsx b/app/src/components/heatMapGenerator/baked/bakedHeatMap.tsx index c774943..dd89d6c 100644 --- a/app/src/components/heatMapGenerator/baked/bakedHeatMap.tsx +++ b/app/src/components/heatMapGenerator/baked/bakedHeatMap.tsx @@ -3,126 +3,223 @@ import { useEffect, useMemo, useRef, useCallback } from "react"; import { useThree } from "@react-three/fiber"; import { useHeatMapStore } from "../../../store/simulation/useHeatMapStore"; 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 OPACITY = 0.8; const GROWTH_RATE = 20.0; -// 🔹 React Component const BakedHeatMap = () => { + const { materialStore } = useSceneContext(); const { bakedPoints } = useHeatMapStore(); const materialRef = useRef(null); const meshRef = useRef(null); const { gl } = useThree(); - + const { isPlaying, setIsPlaying } = usePlayButtonStore(); const height = CONSTANTS.gridConfig.size; const width = CONSTANTS.gridConfig.size; + const { materialHistory, materials } = materialStore(); - const pointTexture = useMemo(() => { - if (bakedPoints.length === 0) return null; - const data = new Float32Array(bakedPoints.length * 4); - bakedPoints.forEach((p, i) => { - const index = i * 4; - data[index] = (p.points.x + width / 2) / width; - data[index + 1] = (p.points.y + height / 2) / height; - data[index + 2] = 0.3; - data[index + 3] = 0.0; - }); - const texture = new THREE.DataTexture(data, bakedPoints.length, 1, THREE.RGBAFormat, THREE.FloatType); - texture.needsUpdate = true; - return texture; - }, [bakedPoints, width, height]); + const createPointTexture = useCallback( + (filteredPoints: typeof bakedPoints) => { + if (filteredPoints.length === 0) return null; + + const data = new Float32Array(filteredPoints.length * 4); + filteredPoints.forEach((p, i) => { + const index = i * 4; + data[index] = (p.points.x + width / 2) / width; + data[index + 1] = (p.points.y + height / 2) / height; + data[index + 2] = 0.3; + data[index + 3] = 0.0; + }); + + const texture = new THREE.DataTexture(data, filteredPoints.length, 1, THREE.RGBAFormat, THREE.FloatType); + texture.needsUpdate = true; + return texture; + }, + [width, height] + ); const uniformsRef = useRef({ - u_points: { value: pointTexture }, - u_count: { value: bakedPoints.length }, + u_points: { value: null as THREE.DataTexture | null }, + u_count: { value: 0 }, u_radius: { value: RADIUS }, u_opacity: { value: OPACITY }, 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(() => { uniformsRef.current.u_points.value = pointTexture; uniformsRef.current.u_count.value = bakedPoints.length; }, [pointTexture, bakedPoints.length]); - useEffect(() => { - if (meshRef.current) { - exportHeatmapAsPNG({ - bakedPoints, - gl, - width: width, - height: height, - mesh: meshRef.current, - }); - } - }, []); - return ( - <> - // - // - // + + + = u_count) break; - // float fi = float(i) + 0.5; - // float u = fi / float(u_count); + 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; + 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 d = distance(vUv, pos); + 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); - // 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); - // } + 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); - // } - // `} - // /> - // + gl_FragColor = vec4(color, normalized * u_opacity); + } + `} + /> + + + {/* + + */} + ); }; diff --git a/app/src/components/heatMapGenerator/functions/exportHeatmapAsPNG.ts b/app/src/components/heatMapGenerator/functions/exportHeatmapAsPNG.ts index 1084f10..678e774 100644 --- a/app/src/components/heatMapGenerator/functions/exportHeatmapAsPNG.ts +++ b/app/src/components/heatMapGenerator/functions/exportHeatmapAsPNG.ts @@ -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 OPACITY = 0.8; 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 }) { - const createPointTexture = (filteredPoints: typeof bakedPoints) => { +interface BakedPoint { + 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; const data = new Float32Array(filteredPoints.length * 4); @@ -13,82 +36,154 @@ export function exportHeatmapAsPNG({ bakedPoints, gl, width, height, mesh }: { b const index = i * 4; data[index] = (p.points.x + width / 2) / width; 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; }); - 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; return texture; }; - 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); + // Create heatmap rendering shader material + const createHeatmapMaterial = (pointsTexture: DataTexture, count: number): ShaderMaterial => { + return new ShaderMaterial({ + transparent: true, + depthWrite: false, + blending: AdditiveBlending, + side: DoubleSide, + uniforms: { + u_points: { value: pointsTexture }, + u_count: { value: count }, + u_radius: { value: RADIUS }, + u_opacity: { value: OPACITY }, + 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 renderHeatmapToImage = (type: string) => { + /** + * 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 pointTexture = createPointTexture(filteredPoints); - if (!pointTexture) return null; + const pointsTexture = createPointTexture(filteredPoints); + if (!pointsTexture) return null; - const uniforms = { - u_points: { value: pointTexture }, - u_count: { value: filteredPoints.length }, - u_radius: { value: RADIUS }, - u_opacity: { value: OPACITY }, - u_growthRate: { value: GROWTH_RATE }, - }; + const material = createHeatmapMaterial(pointsTexture, filteredPoints.length); - const exportCamera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0.1, 10); + 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.lookAt(0, 0, 0); - const renderTarget = new THREE.WebGLRenderTarget(1024, 1024, { - format: THREE.RGBAFormat, - type: THREE.UnsignedByteType, + const renderTarget = new WebGLRenderTarget(1024, 1024, { + format: RGBAFormat, + type: UnsignedByteType, }); - const tempScene = new THREE.Scene(); + const tempScene = new Scene(); tempScene.add(mesh); + // Render heatmap gl.setRenderTarget(renderTarget); gl.render(tempScene, exportCamera); gl.setRenderTarget(null); + // Read pixels const pixels = new Uint8Array(1024 * 1024 * 4); gl.readRenderTargetPixels(renderTarget, 0, 0, 1024, 1024, pixels); + // Convert to Blob 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"); + if (!ctx) return null; + + const imageData = ctx.createImageData(1024, 1024); + imageData.data.set(pixels); + ctx.putImageData(imageData, 0, 0); + + // 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 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; + return types.map((type) => ({ + type, + image: renderHeatmapToImage(type), + })); } diff --git a/app/src/components/heatMapGenerator/heatMap.tsx b/app/src/components/heatMapGenerator/heatMap.tsx index bda354a..199e32c 100644 --- a/app/src/components/heatMapGenerator/heatMap.tsx +++ b/app/src/components/heatMapGenerator/heatMap.tsx @@ -14,7 +14,7 @@ function HeatMap() { <> - + {/* */} ); } diff --git a/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx b/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx index 760dcde..1bb118d 100644 --- a/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx +++ b/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx @@ -48,7 +48,7 @@ const RealTimeHeatMap = () => { u_radius: { value: RADIUS }, u_opacity: { value: OPACITY }, u_debugMode: { value: debugModeMap[debugMode] }, - u_growthRate: { value: GROWTH_TIME_MULTIPLIER }, + u_growthRate: { value: GROWTH_TIME_MULTIPLIER }, }); useEffect(() => { @@ -59,10 +59,34 @@ const RealTimeHeatMap = () => { } }, [isReset, isPlaying]); - useEffect(() => { - addMonitoringVehicle("26770368-55e8-4d40-87f7-8eacb48dc236"); - addMonitoringHuman("264a51e7-d8b9-4093-95ac-fa7e2dc49cfa"); - }, []); + // useEffect(() => { + // addMonitoringVehicle("26770368-55e8-4d40-87f7-8eacb48dc236"); + // 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(() => { const selectedProductData = getProductById(selectedProduct.productUuid); @@ -72,14 +96,8 @@ const RealTimeHeatMap = () => { 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); - } + if (event.type === "human" || event.type === "vehicle") { + newEvents.push(event); } }); }); diff --git a/app/src/modules/simulation/simulator/SimulationHandler.tsx b/app/src/modules/simulation/simulator/SimulationHandler.tsx index 3cd7f8d..f109707 100644 --- a/app/src/modules/simulation/simulator/SimulationHandler.tsx +++ b/app/src/modules/simulation/simulator/SimulationHandler.tsx @@ -1,22 +1,22 @@ -import React, { useEffect } from 'react'; -import { useSceneContext } from '../../scene/sceneContext'; -import { determineExecutionMachineSequences } from './functions/determineExecutionMachineSequences'; -import { usePlayButtonStore } from '../../../store/ui/usePlayButtonStore'; -import { useSimulationManager } from '../../../store/rough/useSimulationManagerStore'; -import { useParams } from 'react-router-dom'; +import React, { useEffect } from "react"; +import { useSceneContext } from "../../scene/sceneContext"; +import { determineExecutionMachineSequences } from "./functions/determineExecutionMachineSequences"; +import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; +import { useSimulationManager } from "../../../store/rough/useSimulationManagerStore"; +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 { activeTime: number; isActive: boolean; idleTime: number; - type: - | "roboticArm" - | "vehicle" - | "transfer" - | "storageUnit" - | "crane" - | "human" - | "machine"; + type: "roboticArm" | "vehicle" | "transfer" | "storageUnit" | "crane" | "human" | "machine"; } // Product → holds multiple usage records @@ -45,15 +45,16 @@ const SimulationHandler = () => { const { getProductById, selectedProduct } = productStore(); const { machines, getMachineById } = machineStore(); const { getHumanById } = humanStore(); - const { getCraneById, } = craneStore(); + const { getCraneById } = craneStore(); const { getStorageUnitById } = storageUnitStore(); const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { simulationData, addData } = useSimulationManager(); const { projectId } = useParams(); const { selectedVersion } = versionStore(); - - - + const { bakedPoints } = useHeatMapStore(); + const height = CONSTANTS.gridConfig.size; + const width = CONSTANTS.gridConfig.size; + const { gl } = useThree(); useEffect(() => { let checkTimer: ReturnType; @@ -65,39 +66,39 @@ const SimulationHandler = () => { if (currentProduct) { const executionSequences = await determineExecutionMachineSequences([currentProduct]); if (executionSequences?.length > 0) { - executionSequences.forEach(sequence => { - sequence.forEach(entity => { - if (entity.type === 'roboticArm') { + executionSequences.forEach((sequence) => { + sequence.forEach((entity) => { + if (entity.type === "roboticArm") { const roboticArm = getArmBotById(entity.modelUuid); if (roboticArm?.isActive) { hasActiveEntity = true; } } - if (entity.type === 'vehicle') { + if (entity.type === "vehicle") { const vehicle = getVehicleById(entity.modelUuid); if (vehicle?.isActive) { hasActiveEntity = true; } } - if (entity.type === 'machine') { + if (entity.type === "machine") { const machine = getMachineById(entity.modelUuid); if (machine?.isActive) { hasActiveEntity = true; } } - if (entity.type === 'human') { + if (entity.type === "human") { const human = getHumanById(entity.modelUuid); if (human?.isActive) { hasActiveEntity = true; } } - if (entity.type === 'crane') { + if (entity.type === "crane") { const crane = getCraneById(entity.modelUuid); if (crane?.isActive) { hasActiveEntity = true; } } - if (entity.type === 'storageUnit') { + if (entity.type === "storageUnit") { const storageUnit = getStorageUnitById(entity.modelUuid); if (storageUnit?.isActive) { hasActiveEntity = true; @@ -114,6 +115,7 @@ const SimulationHandler = () => { } if (materials.length === 0 && materialHistory.length >= 0 && !hasActiveEntity) { + let bakedResult = generateHeatmapOutput({ bakedPoints, gl, width, height, outputType: "url" }); if (executionSequences?.length > 0) { executionSequences.forEach((sequence) => { @@ -134,24 +136,14 @@ const SimulationHandler = () => { const obj = getter(entity.modelUuid); if (!obj) return; // skip if not found - addData( - projectId, - selectedVersion?.versionId || "", - selectedProduct?.productUuid, - { - activeTime: obj.activeTime ?? 0, - isActive: obj.isActive ?? false, - idleTime: obj.idleTime ?? 0, - type: entity.type as - | "roboticArm" - | "vehicle" - | "machine" - | "human" - | "crane" - | "storageUnit" - | "transfer", - } - ); + addData(projectId, selectedVersion?.versionId || "", selectedProduct?.productUuid, { + activeTime: obj.activeTime ?? 0, + isActive: obj.isActive ?? false, + idleTime: obj.idleTime ?? 0, + type: entity.type as "roboticArm" | "vehicle" | "machine" | "human" | "crane" | "storageUnit" | "transfer", + }); + + heatMapImageApi(projectId || "", selectedVersion?.versionId || "", selectedProduct?.productUuid, bakedResult); }); }); } @@ -172,6 +164,6 @@ const SimulationHandler = () => { }, [materials, materialHistory, selectedVersion, selectedProduct?.productUuid, isPlaying, armBots, vehicles, machines]); return null; -} +}; export default SimulationHandler; diff --git a/app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts b/app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts new file mode 100644 index 0000000..4f07dd5 --- /dev/null +++ b/app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts @@ -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); +} diff --git a/app/src/services/simulation/products/heatMapImageApi.ts b/app/src/services/simulation/products/heatMapImageApi.ts new file mode 100644 index 0000000..ac69da5 --- /dev/null +++ b/app/src/services/simulation/products/heatMapImageApi.ts @@ -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 ", + "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"); + } +}; From af671adfcb03cbf0eef7fa35e3b46ee425bb05dd Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Tue, 9 Sep 2025 08:59:45 +0530 Subject: [PATCH 2/3] chore: update package dependencies and improve heatmap output handling --- app/package-lock.json | 2 + app/package.json | 1 + .../realTime/realTimeHeatMap.tsx | 2 +- .../simulator/SimulationHandler.tsx | 17 ++- .../functions/generateHeatmapOutput.ts | 129 +++++++++--------- .../simulation/products/heatMapImageApi.ts | 7 +- 6 files changed, 86 insertions(+), 72 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index a1bf231..43f5220 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -27,6 +27,7 @@ "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", "@use-gesture/react": "^10.3.1", + "buffer": "^6.0.3", "chart.js": "^4.4.8", "chartjs-plugin-annotation": "^3.1.0", "dxf-parser": "^1.1.2", @@ -8405,6 +8406,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" diff --git a/app/package.json b/app/package.json index aec1d38..a0842a4 100644 --- a/app/package.json +++ b/app/package.json @@ -22,6 +22,7 @@ "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", "@use-gesture/react": "^10.3.1", + "buffer": "^6.0.3", "chart.js": "^4.4.8", "chartjs-plugin-annotation": "^3.1.0", "dxf-parser": "^1.1.2", diff --git a/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx b/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx index 1bb118d..e35577b 100644 --- a/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx +++ b/app/src/components/heatMapGenerator/realTime/realTimeHeatMap.tsx @@ -11,7 +11,7 @@ const DECAY_RATE = 0.01; const GROWTH_TIME_MULTIPLIER = 20; const RADIUS = 0.005; const OPACITY = 0.8; -const UPDATE_INTERVAL = 1; +const UPDATE_INTERVAL = 0.1; interface HeatPoint { x: number; diff --git a/app/src/modules/simulation/simulator/SimulationHandler.tsx b/app/src/modules/simulation/simulator/SimulationHandler.tsx index f109707..e7c649e 100644 --- a/app/src/modules/simulation/simulator/SimulationHandler.tsx +++ b/app/src/modules/simulation/simulator/SimulationHandler.tsx @@ -115,7 +115,8 @@ const SimulationHandler = () => { } if (materials.length === 0 && materialHistory.length >= 0 && !hasActiveEntity) { - let bakedResult = generateHeatmapOutput({ bakedPoints, gl, width, height, outputType: "url" }); + // let bakedResult = generateHeatmapOutput({ bakedPoints, gl, width, height, outputType: "url" }); + // console.log('bakedResult: ', bakedResult); if (executionSequences?.length > 0) { executionSequences.forEach((sequence) => { @@ -142,8 +143,20 @@ const SimulationHandler = () => { idleTime: obj.idleTime ?? 0, type: entity.type as "roboticArm" | "vehicle" | "machine" | "human" | "crane" | "storageUnit" | "transfer", }); + generateHeatmapOutput({ + bakedPoints, + gl, + width, + height, + outputType: "file", + download: false, + }).then((bakedResult) => { + console.log("Baked Result:", bakedResult); - heatMapImageApi(projectId || "", selectedVersion?.versionId || "", selectedProduct?.productUuid, bakedResult); + heatMapImageApi(projectId || "", selectedVersion?.versionId || "", selectedProduct?.productUuid, bakedResult); + }); + + // heatMapImageApi(projectId || "", selectedVersion?.versionId || "", selectedProduct?.productUuid, bakedResult); }); }); } diff --git a/app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts b/app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts index 4f07dd5..315abfa 100644 --- a/app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts +++ b/app/src/modules/simulation/simulator/functions/generateHeatmapOutput.ts @@ -1,90 +1,87 @@ import { WebGLRenderer } from "three"; +import { Buffer } from "buffer"; import { exportHeatmapAsPNG } from "../../../../components/heatMapGenerator/functions/exportHeatmapAsPNG"; -// Type for a single baked point +// Types interface BakedPoint { - type: string; - points: { x: number; y: number }; + 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; + type: string; + image: Blob | null; } interface HeatmapFileResult { - type: string; - file: File; + type: string; + file: HeatmapBackendFile; } -type OutputType = "url" | "file" | "blob"; +interface HeatmapBackendFile { + fieldname: string; + originalname: string; + encoding: string; + mimetype: string; + buffer: Buffer; + size: number; +} interface GenerateHeatmapOutputParams { - bakedPoints: BakedPoint[]; - gl: WebGLRenderer; - width: number; - height: number; - outputType?: OutputType; - download?: boolean; // <-- NEW PARAM + bakedPoints: BakedPoint[]; + gl: WebGLRenderer; + width: number; + height: number; + outputType?: "url" | "file" | "blob"; + download?: boolean; } -/** - * 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 }); +export async function generateHeatmapOutput({ + bakedPoints, + gl, + width, + height, + outputType = "file", + download = false, +}: GenerateHeatmapOutputParams): Promise { + const bakedResult: ExportedHeatmap[] = exportHeatmapAsPNG({ bakedPoints, gl, width, height }); - return bakedResult - .map((item) => { - if (!item.image) return null; + const fileResults = await Promise.all( + bakedResult + .map(async (item) => { + if (!item.image) return null; - const fileName = `${item.type}-heatmap.png`; + const fileName = `${item.type}-heatmap.png`; - if (outputType === "file") { - const file = new File([item.image], fileName, { type: "image/png" }); + // Convert Blob -> ArrayBuffer -> Buffer + const arrayBuffer = await item.image.arrayBuffer(); + const nodeBuffer = Buffer.from(arrayBuffer); - // 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); - } + const backendFile: HeatmapBackendFile = { + fieldname: "file", + originalname: fileName, + encoding: "7bit", + mimetype: "image/png", + buffer: nodeBuffer, + size: nodeBuffer.length, + }; - return { type: item.type, file } as HeatmapFileResult; - } else if (outputType === "url") { - const url = URL.createObjectURL(item.image); + + if (download) { + const url = URL.createObjectURL(item.image); + const link = document.createElement("a"); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + setTimeout(() => URL.revokeObjectURL(url), 1000); + } - // 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, file: backendFile }; + }) + .filter((result): result is Promise => result !== null) + ); - 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); + return fileResults; } diff --git a/app/src/services/simulation/products/heatMapImageApi.ts b/app/src/services/simulation/products/heatMapImageApi.ts index ac69da5..b098dbf 100644 --- a/app/src/services/simulation/products/heatMapImageApi.ts +++ b/app/src/services/simulation/products/heatMapImageApi.ts @@ -2,9 +2,6 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR 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", @@ -34,3 +31,7 @@ export const heatMapImageApi = async (projectId: string, versionId: string, prod echo.error("Failed to delete event data"); } }; + + + + From cefbfa49ce0d050aa31916271b9d11a5dd506b39 Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Tue, 9 Sep 2025 09:01:20 +0530 Subject: [PATCH 3/3] refactor: clean up console logs in SimulationHandler and heatMapImageApi --- .../simulation/simulator/SimulationHandler.tsx | 17 ++--------------- .../simulation/products/heatMapImageApi.ts | 4 ++-- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/app/src/modules/simulation/simulator/SimulationHandler.tsx b/app/src/modules/simulation/simulator/SimulationHandler.tsx index e7c649e..88dba01 100644 --- a/app/src/modules/simulation/simulator/SimulationHandler.tsx +++ b/app/src/modules/simulation/simulator/SimulationHandler.tsx @@ -115,9 +115,6 @@ const SimulationHandler = () => { } if (materials.length === 0 && materialHistory.length >= 0 && !hasActiveEntity) { - // let bakedResult = generateHeatmapOutput({ bakedPoints, gl, width, height, outputType: "url" }); - // console.log('bakedResult: ', bakedResult); - if (executionSequences?.length > 0) { executionSequences.forEach((sequence) => { sequence.forEach((entity) => { @@ -143,20 +140,10 @@ const SimulationHandler = () => { idleTime: obj.idleTime ?? 0, type: entity.type as "roboticArm" | "vehicle" | "machine" | "human" | "crane" | "storageUnit" | "transfer", }); - generateHeatmapOutput({ - bakedPoints, - gl, - width, - height, - outputType: "file", - download: false, - }).then((bakedResult) => { - console.log("Baked Result:", bakedResult); - + generateHeatmapOutput({ bakedPoints, gl, width, height, outputType: "file", download: false }).then((bakedResult) => { + // heatMapImageApi(projectId || "", selectedVersion?.versionId || "", selectedProduct?.productUuid, bakedResult); }); - - // heatMapImageApi(projectId || "", selectedVersion?.versionId || "", selectedProduct?.productUuid, bakedResult); }); }); } diff --git a/app/src/services/simulation/products/heatMapImageApi.ts b/app/src/services/simulation/products/heatMapImageApi.ts index b098dbf..1de7c33 100644 --- a/app/src/services/simulation/products/heatMapImageApi.ts +++ b/app/src/services/simulation/products/heatMapImageApi.ts @@ -1,7 +1,7 @@ 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); + try { const response = await fetch(`${url_Backend_dwinzo}/api/V1/SimulatedImage`, { method: "PATCH", @@ -25,7 +25,7 @@ export const heatMapImageApi = async (projectId: string, versionId: string, prod } const result = await response.json(); - console.log('result: ', result); + return result; } catch { echo.error("Failed to delete event data");