Files
Dwinzo_Demo/app/src/modules/builder/asset/models/model/model.tsx

218 lines
8.2 KiB
TypeScript
Raw Normal View History

2025-06-10 15:28:23 +05:30
import * as THREE from 'three';
2025-08-06 18:19:54 +05:30
import { useEffect, useRef, useState } from 'react';
2025-06-10 15:28:23 +05:30
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
2025-08-06 18:19:54 +05:30
import { useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useToggleView, useToolMode } from '../../../../../store/builder/store';
2025-06-10 15:28:23 +05:30
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
2025-08-06 18:19:54 +05:30
import useModuleStore from '../../../../../store/useModuleStore';
2025-06-23 09:37:53 +05:30
import { useSceneContext } from '../../../../scene/sceneContext';
import { SkeletonUtils } from 'three-stdlib';
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
import { ModelAnimator } from './animator/modelAnimator';
2025-08-06 18:19:54 +05:30
import { useModelEventHandlers } from './eventHandlers/useEventHandlers';
2025-06-10 15:28:23 +05:30
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();
2025-06-10 15:28:23 +05:30
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
2025-08-06 18:19:54 +05:30
const { assetStore } = useSceneContext();
const { resetAnimation } = assetStore();
const { setDeletableFloorItem } = useDeletableFloorItem();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
const [isSelected, setIsSelected] = useState(false);
const groupRef = useRef<THREE.Group>(null);
const [ikData, setIkData] = useState<any>();
const { selectedAssets } = useSelectedAssets();
2025-06-10 15:28:23 +05:30
useEffect(() => {
if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') {
getAssetIksApi(asset.assetId).then((data) => {
if (data.iks) {
const iks: IK[] = data.iks;
setIkData(iks);
}
})
}
}, [asset.modelUuid, ikData])
2025-06-12 09:31:51 +05:30
useEffect(() => {
setDeletableFloorItem(null);
if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) {
resetAnimation(asset.modelUuid);
}
}, [activeModule, toolMode, selectedFloorItem])
2025-06-12 09:31:51 +05:30
useEffect(() => {
if (selectedFloorItem && selectedFloorItem.userData.modelUuid === asset.modelUuid) {
setSelectedFloorItem(groupRef.current);
}
}, [isRendered, selectedFloorItem])
2025-06-10 15:28:23 +05:30
useEffect(() => {
2025-08-06 18:19:54 +05:30
if (selectedAssets.length > 0) {
if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) {
setIsSelected(true);
} else {
setIsSelected(false);
}
} else {
setIsSelected(false);
}
}, [selectedAssets])
2025-08-06 18:19:54 +05:30
useEffect(() => {
if (gltfScene) {
gltfScene.traverse((child: any) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
2025-06-10 15:28:23 +05:30
}
2025-08-06 18:19:54 +05:30
})
}
}, [gltfScene]);
2025-06-10 15:28:23 +05:30
2025-08-06 18:19:54 +05:30
useEffect(() => {
// Calculate Bounding Box
const calculateBoundingBox = (scene: THREE.Object3D) => {
const box = new THREE.Box3().setFromObject(scene);
setBoundingBox(box);
};
// Check Cache
const assetId = asset.assetId;
const cachedModel = THREE.Cache.get(assetId);
if (cachedModel) {
const clone: any = SkeletonUtils.clone(cachedModel.scene);
clone.animations = cachedModel.animations || [];
setGltfScene(clone);
calculateBoundingBox(clone);
return;
}
// Check IndexedDB
retrieveGLTF(assetId).then((indexedDBModel) => {
if (indexedDBModel) {
const blobUrl = URL.createObjectURL(indexedDBModel);
loader.load(
blobUrl,
(gltf) => {
2025-06-10 15:28:23 +05:30
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
},
undefined,
(error) => {
2025-08-06 18:19:54 +05:30
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
URL.revokeObjectURL(blobUrl);
2025-06-10 15:28:23 +05:30
}
);
return;
}
2025-08-06 18:19:54 +05:30
// Fetch from Backend
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
loader.load(
modelUrl,
(gltf: GLTF) => {
fetch(modelUrl)
.then((response) => response.blob())
.then((modelBlob) => storeGLTF(assetId, modelBlob))
.then(() => {
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
})
.catch((error) => {
console.error(
`[Backend] Error storing/loading ${asset.modelName}:`,
error
);
});
},
undefined,
(error) => {
echo.error(`[Backend] Error loading ${asset.modelName}:`);
2025-06-10 15:28:23 +05:30
}
2025-08-06 18:19:54 +05:30
);
}).catch((err) => {
console.error("Failed to load model:", asset.assetId, err);
});
}, []);
2025-08-06 18:19:54 +05:30
const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef });
2025-06-10 15:28:23 +05:30
return (
<group
2025-06-12 09:31:51 +05:30
key={asset.modelUuid}
2025-06-10 15:28:23 +05:30
name='Asset Model'
ref={groupRef}
uuid={asset.modelUuid}
position={asset.position}
rotation={asset.rotation}
visible={asset.isVisible}
userData={{ ...asset, iks: ikData }}
castShadow
receiveShadow
2025-06-10 15:28:23 +05:30
onDoubleClick={(e) => {
if (!toggleView) {
e.stopPropagation();
2025-06-10 15:28:23 +05:30
handleDblClick(asset);
}
}}
onClick={(e) => {
if (!toggleView) {
e.stopPropagation();
handleClick(e, asset);
2025-06-10 15:28:23 +05:30
}
}}
onPointerOver={(e) => {
2025-06-10 15:28:23 +05:30
if (!toggleView) {
e.stopPropagation();
2025-06-10 15:28:23 +05:30
handlePointerOver(asset);
}
}}
onPointerLeave={(e) => {
2025-06-10 15:28:23 +05:30
if (!toggleView) {
e.stopPropagation();
handlePointerOut(e, asset);
2025-06-10 15:28:23 +05:30
}
}}
onContextMenu={(e) => {
e.stopPropagation();
handleContextMenu(asset, e);
}}
>
{gltfScene && (
<>
{isRendered ? (
<>
<primitive object={gltfScene} />
<ModelAnimator asset={asset} gltfScene={gltfScene} />
</>
) : (
2025-08-04 18:06:22 +05:30
<>
{!isSelected &&
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
}
</>
)}
{isSelected &&
<AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />
}
</>
2025-06-10 15:28:23 +05:30
)}
</group>
2025-06-10 15:28:23 +05:30
);
}
export default Model;