Merge branch 'main-dev' of http://185.100.212.76:7778/Dwinzo-Beta/Dwinzo_Demo into main-dev
This commit is contained in:
@@ -3,6 +3,7 @@ import React, { useState, useRef, useEffect } from "react";
|
||||
import { dataModelManager } from "./data/dataModel";
|
||||
import ControlPanel from "./ControlPanel";
|
||||
import SwapModal from "./SwapModal";
|
||||
import { Block } from "../../types/exportedTypes";
|
||||
import DataModelPanel from "./components/models/DataModelPanel";
|
||||
|
||||
import { useSceneContext } from "../../modules/scene/sceneContext";
|
||||
@@ -77,23 +78,35 @@ const DashboardEditor: React.FC = () => {
|
||||
const currentBlock = blocks.find((b) => b.blockUuid === selectedBlock);
|
||||
const currentElement = currentBlock?.elements.find((el) => el.elementUuid === selectedElement);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("blocks: ", blocks);
|
||||
}, [blocks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
// getDashBoardBlocksApi(projectId, selectedVersion.versionId).then((data) => {
|
||||
// if (data.data && data.data.blocks) {
|
||||
// console.log("data: ", data);
|
||||
// setBlocks(data.data.blocks);
|
||||
// }
|
||||
// });
|
||||
getDashBoardBlocksApi(projectId, selectedVersion.versionId).then((data) => {
|
||||
if (data.data?.blocks) {
|
||||
console.log("data.data.blocks: ", data.data.blocks);
|
||||
setBlocks(data.data.blocks);
|
||||
}
|
||||
});
|
||||
}, [projectId, selectedVersion]);
|
||||
|
||||
const updateBackend = (blocks: Block[]) => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
|
||||
upsetDashBoardBlocksApi({ projectId, versionId: selectedVersion.versionId, blocks }).then((data) => {
|
||||
if (data.data?.blocks) {
|
||||
console.log("data.data.blocks: ", data.data.blocks);
|
||||
setBlocks(data.data.blocks);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = subscribe(() => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
|
||||
// upsetDashBoardBlocksApi({ projectId, versionId: selectedVersion.versionId, blocks }).then((data) => {
|
||||
// console.log("data: ", data);
|
||||
// });
|
||||
updateBackend(blocks);
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -103,10 +116,11 @@ const DashboardEditor: React.FC = () => {
|
||||
|
||||
useCallBackOnKey(
|
||||
() => {
|
||||
console.log(blocks);
|
||||
if (!projectId || !selectedVersion) return;
|
||||
updateBackend(blocks);
|
||||
},
|
||||
"Ctrl+S",
|
||||
{ dependencies: [blocks], noRepeat: true, allowOnInput: true }
|
||||
{ dependencies: [blocks, projectId, selectedVersion], noRepeat: true, allowOnInput: true }
|
||||
);
|
||||
|
||||
// Subscribe to data model changes
|
||||
@@ -118,27 +132,27 @@ const DashboardEditor: React.FC = () => {
|
||||
const keys = dataModelManager.getAvailableKeys();
|
||||
const subscriptions: Array<[string, () => void]> = [];
|
||||
|
||||
keys.forEach((key) => {
|
||||
for (const key of keys) {
|
||||
const callback = () => handleDataChange();
|
||||
dataModelManager.subscribe(key, callback);
|
||||
subscriptions.push([key, callback]);
|
||||
});
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const currentKeys = dataModelManager.getAvailableKeys();
|
||||
const newKeys = currentKeys.filter((key) => !keys.includes(key));
|
||||
|
||||
newKeys.forEach((key) => {
|
||||
for (const key of newKeys) {
|
||||
const callback = () => handleDataChange();
|
||||
dataModelManager.subscribe(key, callback);
|
||||
subscriptions.push([key, callback]);
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach(([key, callback]) => {
|
||||
for (const [key, callback] of subscriptions) {
|
||||
dataModelManager.unsubscribe(key, callback);
|
||||
});
|
||||
}
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
@@ -239,7 +253,7 @@ const DashboardEditor: React.FC = () => {
|
||||
const deltaY = e.clientY - resizeStart.y;
|
||||
|
||||
const currentBlock = blocks.find((b) => b.blockUuid === resizingBlock);
|
||||
const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 300, height: 200 };
|
||||
const minSize = currentBlock ? calculateMinBlockSize(currentBlock) : { width: 100, height: 50 };
|
||||
|
||||
const newWidth = Math.max(minSize.width, resizeStart.width + deltaX);
|
||||
const newHeight = Math.max(minSize.height, resizeStart.height + deltaY);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Block } from "../../../../types/exportedTypes";
|
||||
|
||||
export const calculateMinBlockSize = (block: Block): Size => {
|
||||
let minWidth = 300;
|
||||
let minHeight = 200;
|
||||
let minWidth = 100;
|
||||
let minHeight = 50;
|
||||
|
||||
block.elements.forEach((element) => {
|
||||
if (element.positionType === "absolute") {
|
||||
|
||||
@@ -30,7 +30,6 @@ export const handleBlockDragStart = (
|
||||
setDraggingBlock: (blockId: string | null) => void,
|
||||
setBlockDragOffset: (offset: Position) => void // Change to specific offset
|
||||
): void => {
|
||||
console.log("Block drag start:", blockId);
|
||||
setDraggingBlock(blockId);
|
||||
const element = event.currentTarget as HTMLElement;
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
import * as THREE from "three";
|
||||
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 { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
|
||||
const RADIUS = 0.0025;
|
||||
const OPACITY = 0.8;
|
||||
const GROWTH_RATE = 20.0;
|
||||
|
||||
const BakedHeatMap = () => {
|
||||
const { materialStore } = useSceneContext();
|
||||
const { bakedPoints } = useHeatMapStore();
|
||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const { gl } = useThree();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const height = CONSTANTS.gridConfig.size;
|
||||
const width = CONSTANTS.gridConfig.size;
|
||||
const { materialHistory, materials } = materialStore();
|
||||
|
||||
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: 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);
|
||||
// console.log("image: ", type, image);
|
||||
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");
|
||||
const getImage = exportHeatmapAsPNG();
|
||||
// console.log("getImage: ", getImage);
|
||||
}
|
||||
}, [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]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<mesh ref={meshRef} rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
|
||||
<planeGeometry args={[width, height]} />
|
||||
<shaderMaterial
|
||||
ref={materialRef}
|
||||
transparent
|
||||
depthWrite={false}
|
||||
blending={THREE.AdditiveBlending}
|
||||
uniforms={uniformsRef.current}
|
||||
side={THREE.DoubleSide}
|
||||
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);
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</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> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BakedHeatMap;
|
||||
@@ -1,14 +1,11 @@
|
||||
import RealTimeHeatMap from "./realTime/realTimeHeatMap";
|
||||
import BakedHeatMap from "./baked/bakedHeatMap";
|
||||
import { useIsComparing } from "../../store/builder/store";
|
||||
import HeatMapRenderer from "./heatMapRenderer";
|
||||
|
||||
function HeatMap() {
|
||||
return (
|
||||
<>
|
||||
<RealTimeHeatMap />
|
||||
const { isComparing } = useIsComparing();
|
||||
|
||||
<BakedHeatMap />
|
||||
</>
|
||||
);
|
||||
return <>{isComparing ? <HeatMapRenderer /> : <RealTimeHeatMap />}</>;
|
||||
}
|
||||
|
||||
export default HeatMap;
|
||||
|
||||
68
app/src/components/heatMapGenerator/heatMapRenderer.tsx
Normal file
68
app/src/components/heatMapGenerator/heatMapRenderer.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { useSceneContext } from "../../modules/scene/sceneContext";
|
||||
import { useSimulationManager } from "../../store/rough/useSimulationManagerStore";
|
||||
import { useSimulationState } from "../../store/simulation/useSimulationStore";
|
||||
import HeatmapPreview from "./heatmapPreview";
|
||||
|
||||
const HeatMapRenderer = () => {
|
||||
const { versionStore, layout } = useSceneContext();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { simulationRecords } = useSimulationManager();
|
||||
const { mainScene, comparisonScene } = useSimulationState();
|
||||
|
||||
const { mainSceneHeatmaps, comparisonSceneHeatmaps } = useMemo(() => {
|
||||
const getHeatmaps = (scene: any) => {
|
||||
const heatmaps: Array<{ image: string | Blob; type: string }> = [];
|
||||
if (!scene) return heatmaps;
|
||||
|
||||
simulationRecords.forEach((project) =>
|
||||
project.versions.forEach((version) =>
|
||||
version.products.forEach((product) =>
|
||||
product.simulateData.forEach((simulateDataItem) => {
|
||||
const isTargetScene =
|
||||
product.productId === scene.product.productUuid &&
|
||||
version.versionId === scene.version.versionUuid &&
|
||||
selectedVersion?.versionId;
|
||||
if (!isTargetScene) return;
|
||||
|
||||
product.heatMaps?.forEach((heatMap) => {
|
||||
if (heatMap.type !== simulateDataItem.type) return;
|
||||
const img = heatMap.image;
|
||||
|
||||
if (typeof img === "string") {
|
||||
if (/\.(png|jpg)$/i.test(img)) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
} else if (img instanceof Blob) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return heatmaps;
|
||||
};
|
||||
|
||||
return {
|
||||
mainSceneHeatmaps: getHeatmaps(mainScene),
|
||||
comparisonSceneHeatmaps: getHeatmaps(comparisonScene),
|
||||
};
|
||||
}, [simulationRecords, mainScene, comparisonScene, selectedVersion]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{layout === "Main Layout" &&
|
||||
mainSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview key={`main-${idx}`} image={heatMap.image} type={heatMap.type} />
|
||||
))}
|
||||
{layout === "Comparison Layout" &&
|
||||
comparisonSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview key={`comp-${idx}`} image={heatMap.image} type={heatMap.type} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeatMapRenderer;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { TextureLoader, Texture, DoubleSide } from "three";
|
||||
import * as CONSTANTS from "../../types/world/worldConstants";
|
||||
import { useHeatmapTypeStore } from "../../store/builder/store";
|
||||
|
||||
interface HeatmapPreviewProps {
|
||||
image: string | Blob | null;
|
||||
@@ -9,6 +10,7 @@ interface HeatmapPreviewProps {
|
||||
|
||||
const HeatmapPreview: React.FC<HeatmapPreviewProps> = ({ image, type }) => {
|
||||
const [texture, setTexture] = useState<Texture | null>(null);
|
||||
const { selectedTypes } = useHeatmapTypeStore();
|
||||
|
||||
const width = CONSTANTS.gridConfig.size;
|
||||
const height = CONSTANTS.gridConfig.size;
|
||||
@@ -64,16 +66,16 @@ const HeatmapPreview: React.FC<HeatmapPreviewProps> = ({ image, type }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{type === "human" && (
|
||||
{type === "human" && selectedTypes.human && (
|
||||
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0.035, 0]}>
|
||||
<planeGeometry args={[width, height]} />
|
||||
<meshStandardMaterial map={texture} transparent side={DoubleSide} />
|
||||
<meshStandardMaterial map={texture.clone()} transparent side={DoubleSide} />
|
||||
</mesh>
|
||||
)}
|
||||
{type === "vehicle" && (
|
||||
{type === "vehicle" && selectedTypes.vehicle && (
|
||||
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0.025, 0]}>
|
||||
<planeGeometry args={[width, height]} />
|
||||
<meshStandardMaterial map={texture} transparent side={DoubleSide} />
|
||||
<meshStandardMaterial map={texture.clone()} transparent side={DoubleSide} />
|
||||
</mesh>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -33,16 +33,8 @@ const RealTimeHeatMap = () => {
|
||||
const debugModeMap = { solid: 0, grayscale: 1, normal: 2 } as const;
|
||||
const { productStore } = useSceneContext();
|
||||
const { getProductById, products, selectedProduct } = productStore();
|
||||
const {
|
||||
bakedPoints,
|
||||
hasHuman,
|
||||
hasVehicle,
|
||||
monitoringHuman,
|
||||
monitoringVehicle,
|
||||
addMonitoringHuman,
|
||||
addMonitoringVehicle,
|
||||
addBakedPoint,
|
||||
} = useHeatMapStore();
|
||||
const { monitoringHuman, monitoringVehicle, clearBakedPoints, addBakedPoint } =
|
||||
useHeatMapStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
@@ -65,13 +57,14 @@ const RealTimeHeatMap = () => {
|
||||
u_growthRate: { value: GROWTH_TIME_MULTIPLIER },
|
||||
});
|
||||
|
||||
// useEffect(() => {
|
||||
// if (isReset || !isPlaying) {
|
||||
// setPoints([]);
|
||||
// lastFrameTime.current = null;
|
||||
// lastUpdateTime.current = 0;
|
||||
// }
|
||||
// }, [isReset, isPlaying]);
|
||||
useEffect(() => {
|
||||
if (isReset || !isPlaying) {
|
||||
clearBakedPoints();
|
||||
setPoints([]);
|
||||
lastFrameTime.current = null;
|
||||
lastUpdateTime.current = 0;
|
||||
}
|
||||
}, [isReset, isPlaying]);
|
||||
|
||||
// Added human or vehicle
|
||||
// useEffect(() => {
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
useLoadingProgress,
|
||||
useIsComparing,
|
||||
useCreateNewWindow,
|
||||
useLimitDistance,
|
||||
useRenderDistance,
|
||||
} from "../../../store/builder/store";
|
||||
import { useSimulationState } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
||||
@@ -17,7 +19,9 @@ import { useParams } from "react-router-dom";
|
||||
import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi";
|
||||
import { calculateSimulationData } from "./functions/calculateSimulationData";
|
||||
import NewWindowScene from "../../ui/compareVersion/NewWindowScene";
|
||||
import Button from "../../ui/compareVersion/Button";
|
||||
import ComparisonToolbar from "../../ui/compareVersion/ComparisonToolbar";
|
||||
import { findEnvironment } from "../../../services/builder/environment/findEnvironment";
|
||||
|
||||
type AssetData = {
|
||||
activeTime: number;
|
||||
idleTime: number;
|
||||
@@ -56,7 +60,7 @@ function ComparisonScene() {
|
||||
const { productStore, versionStore } = useSceneContext();
|
||||
const { versionHistory, selectedVersion, setSelectedVersion } = versionStore();
|
||||
const { products, selectedProduct } = productStore();
|
||||
const { isComparing } = useIsComparing();
|
||||
const { isComparing, setIsComparing } = useIsComparing();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { mainScene, comparisonScene, setComparisonState } = useSimulationState();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
@@ -65,8 +69,10 @@ function ComparisonScene() {
|
||||
const { setCompareProductsData } = useCompareProductDataStore();
|
||||
const [shouldShowComparisonResult, setShouldShowComparisonResult] = useState(false);
|
||||
const { addSimulationRecord } = useSimulationManager();
|
||||
const { createNewWindow } = useCreateNewWindow();
|
||||
|
||||
const { createNewWindow, setCreateNewWindow } = useCreateNewWindow();
|
||||
const { clearComparisonState } = useSimulationState();
|
||||
const { setRenderDistance } = useRenderDistance();
|
||||
const { setLimitDistance } = useLimitDistance();
|
||||
const handleSelectVersion = (option: string) => {
|
||||
const version = versionHistory.find((version) => version.versionName === option);
|
||||
if (version) {
|
||||
@@ -178,6 +184,17 @@ function ComparisonScene() {
|
||||
simulationRecords,
|
||||
]);
|
||||
|
||||
const handleExit = () => {
|
||||
setIsComparing(false);
|
||||
setCreateNewWindow(false);
|
||||
clearComparisonState();
|
||||
if (!projectId) return;
|
||||
findEnvironment(projectId).then((data) => {
|
||||
if (!data) return;
|
||||
setRenderDistance(data.renderDistance);
|
||||
setLimitDistance(data.limitDistance);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isComparing && activeModule === "simulation" && selectedProduct && (
|
||||
@@ -208,9 +225,10 @@ function ComparisonScene() {
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<Button />
|
||||
<ComparisonToolbar />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CompareLayOut />
|
||||
|
||||
{createNewWindow && <NewWindowScene />}
|
||||
|
||||
24
app/src/components/layout/scenes/functions/handleExit.ts
Normal file
24
app/src/components/layout/scenes/functions/handleExit.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { findEnvironment } from "../../../../services/builder/environment/findEnvironment";
|
||||
import { useCreateNewWindow, useIsComparing, useLimitDistance, useRenderDistance } from "../../../../store/builder/store";
|
||||
import { useSimulationState } from "../../../../store/simulation/useSimulationStore";
|
||||
|
||||
export function handleExit(projectId?: string) {
|
||||
const setIsComparing = useIsComparing.getState().setIsComparing;
|
||||
const setCreateNewWindow = useCreateNewWindow.getState().setCreateNewWindow;
|
||||
const setRenderDistance = useRenderDistance.getState().setRenderDistance;
|
||||
const setLimitDistance = useLimitDistance.getState().setLimitDistance;
|
||||
const clearComparisonState = useSimulationState.getState().clearComparisonState;
|
||||
// Reset UI comparison state
|
||||
setIsComparing(false);
|
||||
setCreateNewWindow(false);
|
||||
clearComparisonState();
|
||||
|
||||
if (!projectId) return;
|
||||
|
||||
// 🔹 Call async part separately (no async/await in this function)
|
||||
findEnvironment(projectId).then((data) => {
|
||||
if (!data) return;
|
||||
setRenderDistance(data.renderDistance);
|
||||
setLimitDistance(data.limitDistance);
|
||||
});
|
||||
}
|
||||
@@ -217,16 +217,6 @@ export const RenderInNewWindow: React.FC<NewWindowProps> = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
childWindowRef.current?.dispatchEvent(new Event("resize"));
|
||||
};
|
||||
childWindowRef.current?.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
childWindowRef.current?.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const w = childWindowRef.current;
|
||||
if (w && !w.closed) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import useRestStates from "../../../hooks/useResetStates";
|
||||
import { getVersionHistoryApi } from "../../../services/builder/versionControl/getVersionHistoryApi";
|
||||
import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi";
|
||||
import { useSimulationManager } from "../../../store/rough/useSimulationManagerStore";
|
||||
import { handleExit } from "../../layout/scenes/functions/handleExit";
|
||||
|
||||
const CompareLayOut = () => {
|
||||
const { clearComparisonState, comparisonScene, setComparisonState } = useSimulationState();
|
||||
@@ -46,9 +47,7 @@ const CompareLayOut = () => {
|
||||
const { limitDistance, setLimitDistance } = useLimitDistance();
|
||||
const { simulationRecords } = useSimulationManager();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log("simulationRecords: ", simulationRecords);
|
||||
}, [simulationRecords]);
|
||||
useEffect(() => {}, [simulationRecords]);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
resetStates();
|
||||
@@ -225,7 +224,7 @@ const CompareLayOut = () => {
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={() => handleExit(projectId)}>Exit</button>
|
||||
{width !== "0px" &&
|
||||
!selectedVersion?.versionId && ( // Show only if no layout selected
|
||||
<div className="chooseLayout-wrapper">
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
import React, { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
useCreateNewWindow,
|
||||
useHeatmapTypeStore,
|
||||
useIsComparing,
|
||||
useLimitDistance,
|
||||
useRenderDistance,
|
||||
} from "../../../store/builder/store";
|
||||
import { useSimulationState } from "../../../store/simulation/useSimulationStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { findEnvironment } from "../../../services/builder/environment/findEnvironment";
|
||||
import { handleExit } from "../../layout/scenes/functions/handleExit";
|
||||
|
||||
const Button = () => {
|
||||
const { isComparing, setIsComparing } = useIsComparing();
|
||||
const ComparisonToolbar = () => {
|
||||
const { isComparing } = useIsComparing();
|
||||
const { createNewWindow, setCreateNewWindow } = useCreateNewWindow();
|
||||
const { clearComparisonState } = useSimulationState();
|
||||
const { projectId } = useParams();
|
||||
const { setRenderDistance } = useRenderDistance();
|
||||
const { setLimitDistance } = useLimitDistance();
|
||||
const { selectedTypes, toggleType } = useHeatmapTypeStore();
|
||||
|
||||
const handleExit = () => {
|
||||
setIsComparing(false);
|
||||
setCreateNewWindow(false);
|
||||
clearComparisonState();
|
||||
if (!projectId) return;
|
||||
findEnvironment(projectId).then((data) => {
|
||||
if (!data) return;
|
||||
setRenderDistance(data.renderDistance);
|
||||
setLimitDistance(data.limitDistance);
|
||||
});
|
||||
};
|
||||
const { projectId } = useParams();
|
||||
|
||||
const handleOpenInNewWindow = () => {
|
||||
setCreateNewWindow(true);
|
||||
@@ -46,7 +27,7 @@ const Button = () => {
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{isComparing && <button onClick={handleExit}>Exit</button>}
|
||||
{isComparing && <button onClick={() => handleExit(projectId)}>Exit</button>}
|
||||
{isComparing && !createNewWindow && (
|
||||
<button onClick={handleOpenInNewWindow}>Open in New Window</button>
|
||||
)}
|
||||
@@ -57,7 +38,7 @@ const Button = () => {
|
||||
checked={selectedTypes.vehicle}
|
||||
onChange={() => toggleType("vehicle")}
|
||||
/>
|
||||
Vehicle
|
||||
<p>Vehicle</p>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
@@ -66,10 +47,10 @@ const Button = () => {
|
||||
checked={selectedTypes.human}
|
||||
onChange={() => toggleType("human")}
|
||||
/>
|
||||
Human
|
||||
<p>Human</p>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
export default ComparisonToolbar;
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Suspense } from "react";
|
||||
import { useCreateNewWindow, useLoadingProgress } from "../../../store/builder/store";
|
||||
import { RenderInNewWindow } from "../../templates/CreateNewWindow";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
import { useCreateNewWindow, useLoadingProgress } from "../../../store/builder/store";
|
||||
import Scene from "../../../modules/scene/scene";
|
||||
import ComparisonResult from "./ComparisonResult";
|
||||
import Button from "./Button";
|
||||
import ComparisonToolbar from "./ComparisonToolbar";
|
||||
import LoadingPage from "../../templates/LoadingPage";
|
||||
|
||||
const NewWindowScene = () => {
|
||||
@@ -22,7 +22,6 @@ const NewWindowScene = () => {
|
||||
title="3D Viewer"
|
||||
onClose={() => setCreateNewWindow(false)}
|
||||
>
|
||||
{/* Wait a tick to access child window */}
|
||||
<Scene layout="Comparison Layout" />
|
||||
{loadingProgress > 0 && (
|
||||
<LoadingPage
|
||||
@@ -39,7 +38,7 @@ const NewWindowScene = () => {
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button />
|
||||
<ComparisonToolbar />
|
||||
</div>
|
||||
{!loadingProgress && <ComparisonResult />}
|
||||
</RenderInNewWindow>
|
||||
|
||||
@@ -32,6 +32,7 @@ const TreeNode = ({
|
||||
onDrop,
|
||||
onToggleExpand,
|
||||
onOptionClick,
|
||||
onRename,
|
||||
}: {
|
||||
item: AssetGroupChild;
|
||||
level?: number;
|
||||
@@ -43,6 +44,7 @@ const TreeNode = ({
|
||||
onClick: (e: React.MouseEvent, selectedItem: AssetGroupChild) => void;
|
||||
onToggleExpand: (groupUuid: string, newExpanded: boolean) => void;
|
||||
onOptionClick: (option: string, item: AssetGroupChild) => void;
|
||||
onRename: (item: AssetGroupChild, newName: string) => void;
|
||||
}) => {
|
||||
const { assetGroupStore, assetStore } = useSceneContext();
|
||||
const { hasSelectedAsset, selectedAssets } = assetStore();
|
||||
@@ -136,7 +138,13 @@ const TreeNode = ({
|
||||
|
||||
<div className="node-icon">{isGroupNode ? <FolderIcon isOpen={isExpanded} /> : <CubeIcon />}</div>
|
||||
|
||||
<RenameInput value={itemName} onRename={() => {}} canEdit={true} />
|
||||
<RenameInput
|
||||
value={itemName}
|
||||
onRename={(newName) => {
|
||||
onRename(item, newName);
|
||||
}}
|
||||
canEdit={true}
|
||||
/>
|
||||
|
||||
<div className="node-controls">
|
||||
<button className="control-button" title={isVisible ? "Visible" : "Hidden"} onClick={(e) => handleOptionClick(e, "visibility")}>
|
||||
@@ -171,6 +179,7 @@ const TreeNode = ({
|
||||
onDrop={onDrop}
|
||||
onToggleExpand={onToggleExpand}
|
||||
onOptionClick={onOptionClick}
|
||||
onRename={onRename}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -195,6 +204,7 @@ export const AssetOutline = () => {
|
||||
const {
|
||||
groupHierarchy,
|
||||
isGroup,
|
||||
getItemId,
|
||||
getGroupsContainingAsset,
|
||||
getFlatGroupChildren,
|
||||
setGroupExpanded,
|
||||
@@ -207,6 +217,8 @@ export const AssetOutline = () => {
|
||||
toggleSelectedGroup,
|
||||
clearSelectedGroups,
|
||||
hasSelectedGroup,
|
||||
toggleGroupVisibility,
|
||||
toggleGroupLock,
|
||||
} = assetGroupStore();
|
||||
const { projectId } = useParams();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
@@ -232,6 +244,77 @@ export const AssetOutline = () => {
|
||||
return flattened;
|
||||
}, [groupHierarchy, isGroup]);
|
||||
|
||||
const handleAssetRenameUpdate = async (asset: Asset | null, newName: string) => {
|
||||
if (!asset) return;
|
||||
|
||||
if (!builderSocket?.connected) {
|
||||
setAssetsApi({
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: newName,
|
||||
assetId: asset.assetId,
|
||||
position: asset.position,
|
||||
rotation: asset.rotation,
|
||||
scale: asset.scale,
|
||||
isCollidable: asset.isCollidable,
|
||||
opacity: asset.opacity,
|
||||
isLocked: asset.isLocked,
|
||||
isVisible: asset.isVisible,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
projectId: projectId || "",
|
||||
})
|
||||
.then((data) => {
|
||||
if (!data.message || !data.data) {
|
||||
echo.error(`Error renaming asset: ${asset.modelName}`);
|
||||
return;
|
||||
}
|
||||
if (data.message === "Model updated successfully" && data.data) {
|
||||
const model: Asset = {
|
||||
modelUuid: data.data.modelUuid,
|
||||
modelName: data.data.modelName,
|
||||
assetId: data.data.assetId,
|
||||
position: data.data.position,
|
||||
rotation: data.data.rotation,
|
||||
scale: data.data.scale,
|
||||
isLocked: data.data.isLocked,
|
||||
isVisible: data.data.isVisible,
|
||||
isCollidable: data.data.isCollidable,
|
||||
opacity: data.data.opacity,
|
||||
...(data.data.eventData ? { eventData: data.data.eventData } : {}),
|
||||
};
|
||||
|
||||
updateAssetInScene(model, () => {
|
||||
echo.info(`Renamed asset to: ${model.modelName}`);
|
||||
});
|
||||
} else {
|
||||
echo.error(`Error renaming asset: ${asset.modelName}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
echo.error(`Error renaming asset: ${asset.modelName}`);
|
||||
});
|
||||
} else {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: asset.modelUuid,
|
||||
modelName: newName,
|
||||
assetId: asset.assetId,
|
||||
position: asset.position,
|
||||
rotation: asset.rotation,
|
||||
scale: asset.scale,
|
||||
isCollidable: asset.isCollidable,
|
||||
opacity: asset.opacity,
|
||||
isLocked: asset.isLocked,
|
||||
isVisible: asset.isVisible,
|
||||
socketId: builderSocket?.id,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
projectId,
|
||||
userId,
|
||||
};
|
||||
|
||||
builderSocket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssetVisibilityUpdate = async (asset: Asset | null) => {
|
||||
if (!asset) return;
|
||||
|
||||
@@ -556,11 +639,6 @@ export const AssetOutline = () => {
|
||||
|
||||
if (!scene.current) return;
|
||||
|
||||
// Helper to get item ID
|
||||
const getItemId = (i: AssetGroupChild): string => {
|
||||
return isGroup(i) ? i.groupUuid : i.modelUuid;
|
||||
};
|
||||
|
||||
const itemId = getItemId(item);
|
||||
const flattened = getFlattenedHierarchy();
|
||||
const clickedIndex = flattened.findIndex((flatItem) => getItemId(flatItem) === itemId);
|
||||
@@ -674,14 +752,57 @@ export const AssetOutline = () => {
|
||||
]
|
||||
);
|
||||
|
||||
const handleRename = useCallback(
|
||||
(item: AssetGroupChild, newName: string) => {
|
||||
if (isGroup(item)) {
|
||||
console.log("Rename group:", item.groupUuid, "to:", newName);
|
||||
} else {
|
||||
const asset = getAssetById(item.modelUuid);
|
||||
if (!asset) return;
|
||||
const oldName = asset.modelName;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: { ...asset, modelName: oldName },
|
||||
newData: { ...asset, modelName: newName },
|
||||
timeStap: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (assetsToUpdate.length > 0) {
|
||||
if (assetsToUpdate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: assetsToUpdate[0],
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Update",
|
||||
assets: assetsToUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: "Scene",
|
||||
actions: undoActions,
|
||||
});
|
||||
}
|
||||
|
||||
handleAssetRenameUpdate(asset, newName);
|
||||
}
|
||||
},
|
||||
[selectedVersion, builderSocket, projectId, userId, organization]
|
||||
);
|
||||
|
||||
const handleOptionClick = useCallback(
|
||||
(option: string, item: AssetGroupChild) => {
|
||||
const getItemId = (i: AssetGroupChild): string => {
|
||||
return isGroup(i) ? i.groupUuid : i.modelUuid;
|
||||
};
|
||||
|
||||
if (option === "visibility") {
|
||||
if (isGroup(item)) {
|
||||
toggleGroupVisibility(item.groupUuid);
|
||||
} else {
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
@@ -724,6 +845,7 @@ export const AssetOutline = () => {
|
||||
}
|
||||
} else if (option === "lock") {
|
||||
if (isGroup(item)) {
|
||||
toggleGroupLock(item.groupUuid);
|
||||
} else {
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
@@ -790,7 +912,7 @@ export const AssetOutline = () => {
|
||||
|
||||
const handleAddGroup = useCallback(() => {}, [assetGroupStore, clearSelectedGroups, addSelectedGroup]);
|
||||
|
||||
const handleExpandAll = useCallback(() => {}, [assetGroupStore, setGroupExpanded]);
|
||||
const handleCollapseAll = useCallback(() => {}, [assetGroupStore, setGroupExpanded]);
|
||||
|
||||
// Selection statistics
|
||||
const selectionStats = useMemo(() => {
|
||||
@@ -824,7 +946,7 @@ export const AssetOutline = () => {
|
||||
<button className="toolbar-button" title="Add Group" onClick={handleAddGroup}>
|
||||
<AddIcon />
|
||||
</button>
|
||||
<button className="toolbar-button" title="Expand All" onClick={handleExpandAll}>
|
||||
<button className="toolbar-button" title="Collapse All" onClick={handleCollapseAll}>
|
||||
<CollapseAllIcon />
|
||||
</button>
|
||||
<button className="close-button" onClick={() => setIsOpen(!isOpen)}>
|
||||
@@ -847,6 +969,7 @@ export const AssetOutline = () => {
|
||||
onClick={handleClick}
|
||||
onToggleExpand={handleToggleExpand}
|
||||
onOptionClick={handleOptionClick}
|
||||
onRename={handleRename}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -27,8 +27,9 @@ export function useModelEventHandlers({ boundingBox, groupRef, asset }: { boundi
|
||||
const { selectedEventData, setSelectedEventData } = useSelectedEventData();
|
||||
const { setSelectedAction, selectedAction } = useSelectedAction();
|
||||
const { builderSocket, simulationSocket } = useSocketStore();
|
||||
const { eventStore, productStore, undoRedo3DStore, versionStore, assetStore } = useSceneContext();
|
||||
const { eventStore, productStore, undoRedo3DStore, versionStore, assetStore, assetGroupStore } = useSceneContext();
|
||||
const { toggleSelectedAsset, selectedAssets, clearSelectedAssets } = assetStore();
|
||||
const { isAssetVisible } = assetGroupStore();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
|
||||
const { resourceManagementId, setResourceManagementId } = useResourceManagementId();
|
||||
@@ -95,7 +96,9 @@ export function useModelEventHandlers({ boundingBox, groupRef, asset }: { boundi
|
||||
(toolMode === "cursor" || toolMode === "Move-Asset" || toolMode === "Rotate-Asset") &&
|
||||
boundingBox &&
|
||||
groupRef.current &&
|
||||
(activeModule === "builder" || (activeModule === "simulation" && resourceManagementId))
|
||||
(activeModule === "builder" || (activeModule === "simulation" && resourceManagementId)) &&
|
||||
asset.isVisible &&
|
||||
isAssetVisible(asset.modelUuid)
|
||||
) {
|
||||
zoomMeshes([asset.modelUuid]);
|
||||
|
||||
|
||||
@@ -13,17 +13,14 @@ import { getAssetFieldApi } from "../../../../../services/builder/asset/floorAss
|
||||
import { ModelAnimator } from "./animator/modelAnimator";
|
||||
import { useModelEventHandlers } from "./eventHandlers/useModelEventHandlers";
|
||||
|
||||
function Model({
|
||||
asset,
|
||||
isRendered,
|
||||
loader,
|
||||
}: Readonly<{ asset: Asset; isRendered: boolean; loader: GLTFLoader }>) {
|
||||
function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendered: boolean; loader: GLTFLoader }>) {
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
const savedTheme: string = localStorage.getItem("theme") || "light";
|
||||
const { toolMode } = useToolMode();
|
||||
const { toggleView } = useToggleView();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { assetStore, layout } = useSceneContext();
|
||||
const { assetStore, assetGroupStore } = useSceneContext();
|
||||
const { isAssetVisible } = assetGroupStore();
|
||||
const { resetAnimation, hasSelectedAsset, updateSelectedAsset, selectedAssets } = assetStore();
|
||||
const { setDeletableFloorAsset } = useBuilderStore();
|
||||
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
|
||||
@@ -56,12 +53,7 @@ function Model({
|
||||
}, [activeModule, toolMode, selectedAssets]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
groupRef.current &&
|
||||
selectedAssets.length === 1 &&
|
||||
selectedAssets[0].userData.modelUuid === asset.modelUuid &&
|
||||
hasSelectedAsset(asset.modelUuid)
|
||||
) {
|
||||
if (groupRef.current && selectedAssets.length === 1 && selectedAssets[0].userData.modelUuid === asset.modelUuid && hasSelectedAsset(asset.modelUuid)) {
|
||||
updateSelectedAsset(groupRef.current);
|
||||
}
|
||||
}, [isRendered, selectedAssets, asset]);
|
||||
@@ -139,10 +131,7 @@ function Model({
|
||||
logModelStatus(assetId, "backend-loaded");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
`[Backend] Error storing/loading ${asset.modelName}:`,
|
||||
error
|
||||
);
|
||||
console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error);
|
||||
});
|
||||
},
|
||||
undefined,
|
||||
@@ -156,8 +145,7 @@ function Model({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } =
|
||||
useModelEventHandlers({ boundingBox, groupRef, asset });
|
||||
const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef, asset });
|
||||
|
||||
return (
|
||||
<group
|
||||
@@ -205,30 +193,14 @@ function Model({
|
||||
<>
|
||||
{isRendered ? (
|
||||
<>
|
||||
<primitive visible={asset.isVisible} object={gltfScene} />
|
||||
<primitive visible={asset.isVisible && isAssetVisible(asset.modelUuid)} object={gltfScene} />
|
||||
|
||||
<ModelAnimator asset={asset} gltfScene={gltfScene} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{!hasSelectedAsset(asset.modelUuid) && (
|
||||
<AssetBoundingBox
|
||||
name="Asset Fallback"
|
||||
boundingBox={boundingBox}
|
||||
color="gray"
|
||||
lineWidth={2.5}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{hasSelectedAsset(asset.modelUuid) && (
|
||||
<AssetBoundingBox
|
||||
name="Asset BBox"
|
||||
boundingBox={boundingBox}
|
||||
color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"}
|
||||
lineWidth={2.7}
|
||||
/>
|
||||
<>{!hasSelectedAsset(asset.modelUuid) && <AssetBoundingBox name="Asset Fallback" boundingBox={boundingBox} color="gray" lineWidth={2.5} />}</>
|
||||
)}
|
||||
{hasSelectedAsset(asset.modelUuid) && <AssetBoundingBox name="Asset BBox" boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />}
|
||||
</>
|
||||
)}
|
||||
</group>
|
||||
|
||||
@@ -24,9 +24,10 @@ const SelectionControls3D: React.FC = () => {
|
||||
const { toolMode } = useToolMode();
|
||||
const { builderSocket, simulationSocket } = useSocketStore();
|
||||
const { contextAction, setContextAction } = useContextActionStore();
|
||||
const { assetStore, productStore, undoRedo3DStore, versionStore } = useSceneContext();
|
||||
const { assetStore, productStore, undoRedo3DStore, versionStore, assetGroupStore } = useSceneContext();
|
||||
const { selectedDecal, selectedWall, selectedAisle, selectedFloor, selectedWallAsset } = useBuilderStore();
|
||||
const { push3D, undoStack, redoStack } = undoRedo3DStore();
|
||||
const { isAssetVisible } = assetGroupStore();
|
||||
const { selectedProduct, updateEvent, deleteEvent } = productStore();
|
||||
const {
|
||||
assets,
|
||||
@@ -253,7 +254,13 @@ const SelectionControls3D: React.FC = () => {
|
||||
let currentObject: THREE.Object3D | null = object;
|
||||
while (currentObject) {
|
||||
// if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType) {
|
||||
if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType && currentObject.userData.isVisible && currentObject.visible) {
|
||||
if (
|
||||
currentObject.userData.modelUuid &&
|
||||
!currentObject.userData.wallAssetType &&
|
||||
currentObject.userData.isVisible &&
|
||||
currentObject.visible &&
|
||||
isAssetVisible(currentObject.userData.modelUuid)
|
||||
) {
|
||||
Objects.add(currentObject);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,25 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { KeyboardControls } from "@react-three/drei";
|
||||
|
||||
import { useSceneContext } from "./sceneContext";
|
||||
import useModuleStore from "../../store/ui/useModuleStore";
|
||||
import { useCreateNewWindow, useLoadingProgress } from "../../store/builder/store";
|
||||
import { useSocketStore } from "../../store/socket/useSocketStore";
|
||||
|
||||
import Builder from "../builder/builder";
|
||||
import Visualization from "../visualization/visualization";
|
||||
import Setup from "./setup/setup";
|
||||
import Simulation from "../simulation/simulation";
|
||||
import Collaboration from "../collaboration/collaboration";
|
||||
import useModuleStore from "../../store/ui/useModuleStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { getUserData } from "../../functions/getUserData";
|
||||
import {
|
||||
useCreateNewWindow,
|
||||
useHeatmapTypeStore,
|
||||
useIsComparing,
|
||||
useLoadingProgress,
|
||||
} from "../../store/builder/store";
|
||||
import { useSocketStore } from "../../store/socket/useSocketStore";
|
||||
import { Color, SRGBColorSpace } from "three";
|
||||
import { compressImage } from "../../utils/compressImage";
|
||||
import { ALPHA_ORG } from "../../pages/Dashboard";
|
||||
import HeatmapPreview from "../../components/heatMapGenerator/heatmapPreview";
|
||||
import { useSimulationManager } from "../../store/rough/useSimulationManagerStore";
|
||||
import { useSimulationState } from "../../store/simulation/useSimulationStore";
|
||||
|
||||
export default function Scene({
|
||||
layout,
|
||||
}: {
|
||||
readonly layout: "Main Layout" | "Comparison Layout";
|
||||
}) {
|
||||
export default function Scene({ layout }: { readonly layout: "Main Layout" | "Comparison Layout" }) {
|
||||
const map = useMemo(
|
||||
() => [
|
||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||
@@ -40,7 +30,7 @@ export default function Scene({
|
||||
],
|
||||
[]
|
||||
);
|
||||
const { assetStore, layoutType, versionStore } = useSceneContext();
|
||||
const { assetStore, layoutType } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
@@ -48,19 +38,11 @@ export default function Scene({
|
||||
const { activeModule } = useModuleStore();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { setWindowRendered } = useCreateNewWindow();
|
||||
const { simulationRecords } = useSimulationManager();
|
||||
const { isComparing } = useIsComparing();
|
||||
const { mainScene, comparisonScene } = useSimulationState();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { selectedTypes } = useHeatmapTypeStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || loadingProgress !== 0) return;
|
||||
const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0];
|
||||
if (
|
||||
!canvas ||
|
||||
!(layoutType === "default" || (layoutType === "useCase" && organization === ALPHA_ORG))
|
||||
)
|
||||
return;
|
||||
if (!canvas || !(layoutType === "default" || (layoutType === "useCase" && organization === ALPHA_ORG))) return;
|
||||
compressImage(canvas.toDataURL("image/png")).then((screenshotDataUrl) => {
|
||||
const updateProjects = {
|
||||
projectId,
|
||||
@@ -72,85 +54,8 @@ export default function Scene({
|
||||
projectSocket.emit("v1:project:update", updateProjects);
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [activeModule, assets, loadingProgress, projectId, layoutType]);
|
||||
// Prepare heatmaps per scene
|
||||
|
||||
const mainSceneHeatmaps = useMemo(() => {
|
||||
const heatmaps: Array<{ image: string | Blob; type: string }> = [];
|
||||
simulationRecords.forEach((project) =>
|
||||
project.versions.forEach((version) =>
|
||||
version.products.forEach((product) =>
|
||||
product.simulateData.forEach((simulateDataItem) => {
|
||||
const isMainScene =
|
||||
product.productId === mainScene.product.productUuid &&
|
||||
version.versionId === mainScene.version.versionUuid &&
|
||||
selectedVersion?.versionId;
|
||||
if (!isMainScene) return;
|
||||
|
||||
product.heatMaps?.forEach((heatMap) => {
|
||||
if (heatMap.type !== simulateDataItem.type) return;
|
||||
|
||||
// Filter by selected types
|
||||
if (
|
||||
(heatMap.type === "vehicle" && !selectedTypes.vehicle) ||
|
||||
(heatMap.type === "human" && !selectedTypes.human)
|
||||
)
|
||||
return;
|
||||
|
||||
const img = heatMap.image;
|
||||
if (typeof img === "string") {
|
||||
if (/\.(png|jpg)$/i.test(img)) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
} else if (img instanceof Blob) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
return heatmaps;
|
||||
}, [simulationRecords, mainScene, selectedVersion, selectedTypes]);
|
||||
|
||||
const comparisonSceneHeatmaps = useMemo(() => {
|
||||
const heatmaps: Array<{ image: string | Blob; type: string }> = [];
|
||||
simulationRecords.forEach((project) =>
|
||||
project.versions.forEach((version) =>
|
||||
version.products.forEach((product) =>
|
||||
product.simulateData.forEach((simulateDataItem) => {
|
||||
const isComparisonScene =
|
||||
product.productId === comparisonScene?.product.productUuid &&
|
||||
version.versionId === comparisonScene?.version.versionUuid &&
|
||||
selectedVersion?.versionId;
|
||||
if (!isComparisonScene) return;
|
||||
|
||||
product.heatMaps?.forEach((heatMap) => {
|
||||
if (heatMap.type !== simulateDataItem.type) return;
|
||||
|
||||
// Filter by selected types
|
||||
if (
|
||||
(heatMap.type === "vehicle" && !selectedTypes.vehicle) ||
|
||||
(heatMap.type === "human" && !selectedTypes.human)
|
||||
)
|
||||
return;
|
||||
|
||||
const img = heatMap.image;
|
||||
if (typeof img === "string") {
|
||||
if (/\.(png|jpg)$/i.test(img)) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
} else if (img instanceof Blob) {
|
||||
heatmaps.push({ image: img, type: heatMap.type });
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
return heatmaps;
|
||||
}, [simulationRecords, comparisonScene, selectedVersion, selectedTypes]);
|
||||
return (
|
||||
<KeyboardControls map={map}>
|
||||
<Canvas
|
||||
@@ -163,7 +68,7 @@ export default function Scene({
|
||||
}}
|
||||
resize={{ polyfill: ResizeObserver }}
|
||||
style={{ width: "100vw", height: "100vh", background: "#202020" }}
|
||||
performance={{ min: 0.9, max: 1.0 }}
|
||||
performance={{ min: 0.9, max: 1 }}
|
||||
onCreated={(e) => {
|
||||
e.scene.background = layout === "Main Layout" ? null : new Color(0x19191d);
|
||||
}}
|
||||
@@ -180,27 +85,6 @@ export default function Scene({
|
||||
<Setup />
|
||||
<Collaboration />
|
||||
<Builder />
|
||||
{isComparing && (selectedTypes.vehicle || selectedTypes.human) && (
|
||||
<>
|
||||
{layout === "Main Layout" &&
|
||||
mainSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview
|
||||
key={`main-${idx}`}
|
||||
image={heatMap.image}
|
||||
type={heatMap.type}
|
||||
/>
|
||||
))}
|
||||
{layout === "Comparison Layout" &&
|
||||
comparisonSceneHeatmaps.map((heatMap, idx) => (
|
||||
<HeatmapPreview
|
||||
key={`comp-${idx}`}
|
||||
image={heatMap.image}
|
||||
type={heatMap.type}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Simulation />
|
||||
<Visualization />
|
||||
</Canvas>
|
||||
|
||||
@@ -15,14 +15,12 @@ import useModuleStore from "../../store/ui/useModuleStore";
|
||||
import SimulationAnalysis from "./analysis/simulationAnalysis";
|
||||
import { useSceneContext } from "../scene/sceneContext";
|
||||
import HeatMap from "../../components/heatMapGenerator/heatMap";
|
||||
import { useIsComparing } from "../../store/builder/store";
|
||||
|
||||
function Simulation() {
|
||||
const { activeModule } = useModuleStore();
|
||||
const { eventStore, productStore } = useSceneContext();
|
||||
const { events } = eventStore();
|
||||
const { products } = productStore();
|
||||
const { isComparing } = useIsComparing();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('events: ', events);
|
||||
@@ -62,7 +60,7 @@ function Simulation() {
|
||||
|
||||
<SimulationAnalysis />
|
||||
|
||||
{!isComparing && <HeatMap />}
|
||||
<HeatMap />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WebGLRenderer } from "three";
|
||||
import { Buffer } from "buffer";
|
||||
import { exportHeatmapAsPNG } from "../../../../components/heatMapGenerator/functions/exportHeatmapAsPNG";
|
||||
import { exportHeatmapAsPNG } from "./exportHeatmapAsPNG";
|
||||
|
||||
// Types
|
||||
interface BakedPoint {
|
||||
@@ -32,6 +32,7 @@ interface GenerateHeatmapOutputParams {
|
||||
gl: WebGLRenderer;
|
||||
width: number;
|
||||
height: number;
|
||||
version: string;
|
||||
outputType?: "url" | "file" | "blob";
|
||||
download?: boolean;
|
||||
}
|
||||
@@ -40,6 +41,7 @@ export async function generateHeatmapOutput({
|
||||
bakedPoints,
|
||||
gl,
|
||||
width,
|
||||
version,
|
||||
height,
|
||||
outputType = "file",
|
||||
download = false,
|
||||
@@ -48,7 +50,6 @@ export async function generateHeatmapOutput({
|
||||
|
||||
const fileResults = await Promise.all(
|
||||
bakedResult.map(async (item) => {
|
||||
console.log("item: ", item);
|
||||
|
||||
let imageBlob: Blob;
|
||||
|
||||
@@ -62,7 +63,7 @@ export async function generateHeatmapOutput({
|
||||
imageBlob = item.image;
|
||||
}
|
||||
|
||||
const fileName = `${item.type}-heatmap.png`;
|
||||
const fileName = `${item.type}-${version}-heatmap.png`;
|
||||
|
||||
// Convert Blob -> ArrayBuffer -> Buffer
|
||||
const arrayBuffer = await imageBlob.arrayBuffer();
|
||||
|
||||
@@ -90,7 +90,7 @@ const SimulationHandler = () => {
|
||||
async function handleSaveSimulationData(data: any) {
|
||||
try {
|
||||
await saveSimulationDataApi(data);
|
||||
console.log("Simulation data saved successfully");
|
||||
// console.log("Simulation data saved successfully");
|
||||
setIsPlaying(false);
|
||||
} catch (err) {
|
||||
console.error("Failed to save simulation data:", err);
|
||||
@@ -169,8 +169,10 @@ const SimulationHandler = () => {
|
||||
gl,
|
||||
width,
|
||||
height,
|
||||
version: selectedVersion.versionId,
|
||||
download: true,
|
||||
}).then((bakedResult) => {
|
||||
console.log("bakedResult: ", bakedResult);
|
||||
heatMapImageApi(
|
||||
projectId || "",
|
||||
selectedVersion?.versionId || "",
|
||||
|
||||
@@ -20,6 +20,7 @@ function Simulator() {
|
||||
const { selectedVersion } = versionStore();
|
||||
const { setIsComparing } = useIsComparing();
|
||||
const { addSimulationRecords } = useSimulationManager();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlaying || isReset || !selectedProduct.productUuid) return;
|
||||
|
||||
@@ -74,11 +75,7 @@ function Simulator() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedProduct, projectId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SimulationHandler />
|
||||
</>
|
||||
);
|
||||
return <SimulationHandler />;
|
||||
}
|
||||
|
||||
export default Simulator;
|
||||
|
||||
@@ -77,13 +77,10 @@ const Project: React.FC = () => {
|
||||
}, [projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (token && refreshToken && projectId) {
|
||||
if (projectId) {
|
||||
echo.warn("Validating token");
|
||||
initializeBuilderSocket(token, refreshToken, projectId);
|
||||
initializeSimulationSocket(token, refreshToken, projectId);
|
||||
initializeBuilderSocket(projectId);
|
||||
initializeSimulationSocket(projectId);
|
||||
echo.success("Builder socket initialized");
|
||||
} else {
|
||||
navigate("/");
|
||||
@@ -100,11 +97,8 @@ const Project: React.FC = () => {
|
||||
}, [projectId, builderSocket]);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (token && refreshToken && projectId) {
|
||||
initializeVisualizationSocket(token, refreshToken, projectId);
|
||||
if (projectId) {
|
||||
initializeVisualizationSocket(projectId);
|
||||
echo.success("Visualization socket initialized");
|
||||
} else {
|
||||
navigate("/");
|
||||
@@ -121,11 +115,8 @@ const Project: React.FC = () => {
|
||||
}, [projectId, visualizationSocket]);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (token && refreshToken && projectId) {
|
||||
initializeThreadSocket(token, refreshToken, projectId);
|
||||
if (projectId) {
|
||||
initializeThreadSocket(projectId);
|
||||
echo.success("Thread socket initialized");
|
||||
} else {
|
||||
navigate("/");
|
||||
@@ -142,11 +133,8 @@ const Project: React.FC = () => {
|
||||
}, [projectId, threadSocket]);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (token && refreshToken && projectId) {
|
||||
initializeSimulationSocket(token, refreshToken, projectId);
|
||||
if (projectId) {
|
||||
initializeSimulationSocket(projectId);
|
||||
echo.success("Simulation socket initialized");
|
||||
} else {
|
||||
navigate("/");
|
||||
|
||||
@@ -487,8 +487,8 @@ interface HeatmapTypeState {
|
||||
}
|
||||
export const useHeatmapTypeStore = create<HeatmapTypeState>((set) => ({
|
||||
selectedTypes: {
|
||||
vehicle: false,
|
||||
human: false,
|
||||
vehicle: true,
|
||||
human: true,
|
||||
},
|
||||
toggleType: (type) =>
|
||||
set((state) => ({
|
||||
|
||||
@@ -27,8 +27,9 @@ interface AssetGroupStore {
|
||||
|
||||
// Group properties
|
||||
setGroupName: (groupUuid: string, newName: string) => void;
|
||||
setGroupVisibility: (groupUuid: string, isVisible: boolean) => void;
|
||||
setGroupLock: (groupUuid: string, isLocked: boolean) => void;
|
||||
setGroupVisibility: (groupUuid: string, isVisible: boolean) => void;
|
||||
toggleGroupLock: (groupUuid: string) => void;
|
||||
toggleGroupVisibility: (groupUuid: string) => void;
|
||||
setGroupExpanded: (groupUuid: string, isExpanded: boolean) => void;
|
||||
|
||||
@@ -46,7 +47,12 @@ interface AssetGroupStore {
|
||||
getParentGroup: (item: AssetGroupChild) => string | null;
|
||||
hasGroup: (groupUuid: string) => boolean;
|
||||
isGroup: (item: AssetGroupChild) => item is AssetGroupHierarchyNode;
|
||||
getItemId: (i: AssetGroupChild) => string;
|
||||
isEmptyGroup: (groupUuid: string) => boolean;
|
||||
isAssetGroupVisible: (groupUuid: string) => boolean;
|
||||
isAssetGroupLocked: (groupUuid: string) => boolean;
|
||||
isAssetVisible: (assetUuid: string) => boolean;
|
||||
isAssetLocked: (assetUuid: string) => boolean;
|
||||
}
|
||||
|
||||
export const createAssetGroupStore = () => {
|
||||
@@ -228,6 +234,15 @@ export const createAssetGroupStore = () => {
|
||||
});
|
||||
},
|
||||
|
||||
setGroupLock: (groupUuid, isLocked) => {
|
||||
set((state) => {
|
||||
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
|
||||
if (group) {
|
||||
group.isLocked = isLocked;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setGroupVisibility: (groupUuid, isVisible) => {
|
||||
set((state) => {
|
||||
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
|
||||
@@ -237,11 +252,11 @@ export const createAssetGroupStore = () => {
|
||||
});
|
||||
},
|
||||
|
||||
setGroupLock: (groupUuid, isLocked) => {
|
||||
toggleGroupLock: (groupUuid) => {
|
||||
set((state) => {
|
||||
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
|
||||
if (group) {
|
||||
group.isLocked = isLocked;
|
||||
group.isLocked = !group.isLocked;
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -455,10 +470,94 @@ export const createAssetGroupStore = () => {
|
||||
return "children" in item;
|
||||
},
|
||||
|
||||
getItemId: (i: AssetGroupChild): string => {
|
||||
return get().isGroup(i) ? i.groupUuid : i.modelUuid;
|
||||
},
|
||||
|
||||
isEmptyGroup: (groupUuid) => {
|
||||
const group = get().assetGroups.find((g) => g.groupUuid === groupUuid);
|
||||
return !group || group.children.length === 0;
|
||||
},
|
||||
|
||||
isAssetGroupVisible: (groupUuid: string) => {
|
||||
const state = get();
|
||||
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
|
||||
if (!group) return true; // Default to visible if group not found
|
||||
|
||||
// If this group is not visible, return false regardless of parents
|
||||
if (!group.isVisible) return false;
|
||||
|
||||
// Check parent groups recursively
|
||||
const parentGroups = state.getGroupsContainingGroup(groupUuid);
|
||||
for (const parentGroup of parentGroups) {
|
||||
if (!state.isAssetGroupVisible(parentGroup.groupUuid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isAssetGroupLocked: (groupUuid: string) => {
|
||||
const state = get();
|
||||
const group = state.assetGroups.find((g) => g.groupUuid === groupUuid);
|
||||
if (!group) return false; // Default to unlocked if group not found
|
||||
|
||||
// If this group is locked, return true regardless of parents
|
||||
if (group.isLocked) return true;
|
||||
|
||||
// Check parent groups recursively
|
||||
const parentGroups = state.getGroupsContainingGroup(groupUuid);
|
||||
for (const parentGroup of parentGroups) {
|
||||
if (state.isAssetGroupLocked(parentGroup.groupUuid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
isAssetVisible: (assetUuid: string) => {
|
||||
const state = get();
|
||||
|
||||
// Check if asset is in any groups
|
||||
const parentGroups = state.getGroupsContainingAsset(assetUuid);
|
||||
|
||||
// If asset is not in any groups, it's always visible
|
||||
if (parentGroups.length === 0) return true;
|
||||
|
||||
// Asset is visible only if ALL parent groups are visible
|
||||
for (const parentGroup of parentGroups) {
|
||||
if (!state.isAssetGroupVisible(parentGroup.groupUuid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isAssetLocked: (assetUuid: string) => {
|
||||
const state = get();
|
||||
|
||||
// Check if asset is in any groups
|
||||
const parentGroups = state.getGroupsContainingAsset(assetUuid);
|
||||
|
||||
// If asset is not in any groups, check its own locked state
|
||||
if (parentGroups.length === 0) {
|
||||
// You might need access to the assets store to get the asset's own locked state
|
||||
// For now, returning false as default
|
||||
return false;
|
||||
}
|
||||
|
||||
// Asset is locked if ANY parent group is locked
|
||||
for (const parentGroup of parentGroups) {
|
||||
if (state.isAssetGroupLocked(parentGroup.groupUuid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,12 +7,12 @@ interface SimulationUsageRecord {
|
||||
type: "roboticArm" | "vehicle" | "transfer" | "storageUnit" | "crane" | "human" | "machine";
|
||||
assetId: string;
|
||||
}
|
||||
|
||||
type image = string | Blob | null;
|
||||
// Product → holds multiple usage records
|
||||
interface ProductSimulation {
|
||||
productId: string;
|
||||
simulateData: SimulationUsageRecord[];
|
||||
heatMaps?: { type: string; image: string | Blob | null }[];
|
||||
heatMaps?: { type: string; image: image }[];
|
||||
}
|
||||
|
||||
// Version → holds multiple products
|
||||
@@ -29,7 +29,6 @@ interface ProjectSimulation {
|
||||
|
||||
interface SimulationManagerStore {
|
||||
simulationRecords: ProjectSimulation[];
|
||||
setSimulationRecords: (simulateData: ProjectSimulation[]) => void;
|
||||
|
||||
addSimulationRecord: (
|
||||
projectId: string | undefined,
|
||||
@@ -42,7 +41,7 @@ interface SimulationManagerStore {
|
||||
versionId: string,
|
||||
productId: string,
|
||||
records: SimulationUsageRecord[],
|
||||
heatMaps?: { type: string; image: string | Blob | null }[]
|
||||
heatMaps?: { type: string; image: image }[]
|
||||
) => void;
|
||||
resetProductRecords: (
|
||||
projectId: string | undefined,
|
||||
@@ -131,7 +130,7 @@ export const useSimulationManager = create<SimulationManagerStore>((set, get) =>
|
||||
versionId: string,
|
||||
productId: string,
|
||||
records: SimulationUsageRecord[],
|
||||
heatMaps?: { type: string; image: string | Blob | null }[]
|
||||
heatMaps?: { type: string; image: image }[]
|
||||
) =>
|
||||
set((state) => {
|
||||
const projects = [...state.simulationRecords];
|
||||
@@ -199,60 +198,6 @@ export const useSimulationManager = create<SimulationManagerStore>((set, get) =>
|
||||
return { simulationRecords: projects };
|
||||
}),
|
||||
|
||||
// addSimulationRecords: (projectId, versionId, productId, records) =>
|
||||
// set((state) => {
|
||||
// const projects = state.simulationRecords.map((project) => {
|
||||
// if (project.projectId !== projectId) return project;
|
||||
|
||||
// return {
|
||||
// ...project,
|
||||
// versions: project.versions.map((version) => {
|
||||
// if (version.versionId !== versionId) return version;
|
||||
|
||||
// return {
|
||||
// ...version,
|
||||
// products: version.products.map((product) =>
|
||||
// product.productId === productId
|
||||
// ? {
|
||||
// ...product,
|
||||
// simulateData: [...product.simulateData, ...records],
|
||||
// }
|
||||
// : product
|
||||
// ),
|
||||
// };
|
||||
// }),
|
||||
// };
|
||||
// });
|
||||
|
||||
// // same creation logic for new project/version/product
|
||||
// if (!state.simulationRecords.find((p) => p.projectId === projectId)) {
|
||||
// projects.push({
|
||||
// projectId,
|
||||
// versions: [
|
||||
// {
|
||||
// versionId,
|
||||
// products: [{ productId, simulateData: [...records] }],
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// } else {
|
||||
// const project = projects.find((p) => p.projectId === projectId)!;
|
||||
// if (!project.versions.find((v) => v.versionId === versionId)) {
|
||||
// project.versions.push({
|
||||
// versionId,
|
||||
// products: [{ productId, simulateData: [...records] }],
|
||||
// });
|
||||
// } else {
|
||||
// const version = project.versions.find((v) => v.versionId === versionId)!;
|
||||
// if (!version.products.find((p) => p.productId === productId)) {
|
||||
// version.products.push({ productId, simulateData: [...records] });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return { simulationRecords: projects };
|
||||
// }),
|
||||
|
||||
resetProductRecords: (projectId, versionId, productId) =>
|
||||
set((state) => {
|
||||
const projects = state.simulationRecords.map((project) => {
|
||||
@@ -277,9 +222,7 @@ export const useSimulationManager = create<SimulationManagerStore>((set, get) =>
|
||||
|
||||
return { simulationRecords: projects };
|
||||
}),
|
||||
setSimulationRecords: (simulateData) => {
|
||||
set({ simulationRecords: simulateData });
|
||||
},
|
||||
|
||||
getProjectById: (projectId: string | undefined) => {
|
||||
return get().simulationRecords.find((p: ProjectSimulation) => p.projectId === projectId);
|
||||
},
|
||||
|
||||
@@ -8,11 +8,11 @@ interface SocketStore {
|
||||
threadSocket: Socket | null;
|
||||
simulationSocket: Socket | null;
|
||||
|
||||
initializeBuilderSocket: (token: string, refreshToken: string, projectId: string) => void;
|
||||
initializeVisualizationSocket: (token: string, refreshToken: string, projectId: string) => void;
|
||||
initializeThreadSocket: (token: string, refreshToken: string, projectId: string) => void;
|
||||
initializeBuilderSocket: (projectId: string) => void;
|
||||
initializeVisualizationSocket: (projectId: string) => void;
|
||||
initializeThreadSocket: (projectId: string) => void;
|
||||
initializeProjectSocket: (token: string, refreshToken: string) => void;
|
||||
initializeSimulationSocket: (token: string, refreshToken: string, projectId: string) => void;
|
||||
initializeSimulationSocket: (projectId: string) => void;
|
||||
|
||||
disconnectBuilderSocket: (projectId: string) => void;
|
||||
disconnectVisualizationSocket: (projectId: string) => void;
|
||||
@@ -56,36 +56,51 @@ export const useSocketStore = create<SocketStore>((set, get) => ({
|
||||
threadSocket: null,
|
||||
simulationSocket: null,
|
||||
|
||||
initializeBuilderSocket: (token, refreshToken, projectId) => {
|
||||
if (get().builderSocket) return;
|
||||
initializeBuilderSocket: (projectId) => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (get().builderSocket || !token || !refreshToken) return;
|
||||
const builderSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, socketOptions({ token, refreshToken, projectId }));
|
||||
attachLogs("Builder", builderSocket);
|
||||
set({ builderSocket });
|
||||
},
|
||||
|
||||
initializeVisualizationSocket: (token, refreshToken, projectId) => {
|
||||
if (get().visualizationSocket) return;
|
||||
initializeVisualizationSocket: (projectId) => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (get().visualizationSocket || !token || !refreshToken) return;
|
||||
const visualizationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, socketOptions({ token, refreshToken, projectId }));
|
||||
attachLogs("Visualization", visualizationSocket);
|
||||
set({ visualizationSocket });
|
||||
},
|
||||
|
||||
initializeThreadSocket: (token, refreshToken, projectId) => {
|
||||
if (get().threadSocket) return;
|
||||
initializeThreadSocket: (projectId) => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (get().threadSocket || !token || !refreshToken) return;
|
||||
const threadSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, socketOptions({ token, refreshToken, projectId }));
|
||||
attachLogs("Thread", threadSocket);
|
||||
set({ threadSocket });
|
||||
},
|
||||
|
||||
initializeProjectSocket: (token, refreshToken) => {
|
||||
if (get().projectSocket) return;
|
||||
initializeProjectSocket: () => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (get().projectSocket || !token || !refreshToken) return;
|
||||
const projectSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, socketOptions({ token, refreshToken }));
|
||||
attachLogs("Project", projectSocket);
|
||||
set({ projectSocket });
|
||||
},
|
||||
|
||||
initializeSimulationSocket: (token, refreshToken, projectId) => {
|
||||
if (get().simulationSocket) return;
|
||||
initializeSimulationSocket: (projectId) => {
|
||||
const token = localStorage.getItem("token");
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (get().simulationSocket || !token || !refreshToken) return;
|
||||
const simulationSocket = io(`http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Simulation_v1`, socketOptions({ token, refreshToken, projectId }));
|
||||
attachLogs("Simulation", simulationSocket);
|
||||
set({ simulationSocket });
|
||||
|
||||
Reference in New Issue
Block a user