2025-06-10 15:28:23 +05:30
|
|
|
import * as THREE from 'three';
|
2025-06-12 09:31:51 +05:30
|
|
|
import { useCallback, 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';
|
|
|
|
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
|
|
|
|
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
|
2025-07-18 14:14:34 +05:30
|
|
|
import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
|
2025-06-10 15:28:23 +05:30
|
|
|
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
|
2025-07-02 17:31:17 +05:30
|
|
|
import { CameraControls } from '@react-three/drei';
|
2025-06-10 15:28:23 +05:30
|
|
|
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
|
|
|
|
|
import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore';
|
|
|
|
|
import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore';
|
|
|
|
|
import { useProductContext } from '../../../../simulation/products/productContext';
|
|
|
|
|
import { useParams } from 'react-router-dom';
|
2025-06-23 09:37:53 +05:30
|
|
|
import { getUserData } from '../../../../../functions/getUserData';
|
|
|
|
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
|
|
|
|
import { useVersionContext } from '../../../version/versionContext';
|
2025-07-02 17:31:17 +05:30
|
|
|
import { SkeletonUtils } from 'three-stdlib';
|
2025-07-28 15:26:49 +05:30
|
|
|
|
2025-07-07 10:11:37 +05:30
|
|
|
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
|
2025-07-08 16:31:20 +05:30
|
|
|
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
2025-07-28 15:05:28 +05:30
|
|
|
import { ModelAnimator } from './animator/modelAnimator';
|
2025-06-10 15:28:23 +05:30
|
|
|
|
2025-07-28 17:21:07 +05:30
|
|
|
const distanceWorker = new Worker(new URL("../../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url));
|
|
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
function Model({ asset }: { readonly asset: Asset }) {
|
2025-07-08 16:31:20 +05:30
|
|
|
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
2025-07-18 14:14:34 +05:30
|
|
|
const savedTheme: string = localStorage.getItem("theme") || "light";
|
2025-07-28 15:00:24 +05:30
|
|
|
const { camera, controls, gl, scene } = useThree();
|
2025-06-10 15:28:23 +05:30
|
|
|
const { activeTool } = useActiveTool();
|
2025-07-08 16:31:20 +05:30
|
|
|
const { toolMode } = useToolMode();
|
2025-06-10 15:28:23 +05:30
|
|
|
const { toggleView } = useToggleView();
|
|
|
|
|
const { subModule } = useSubModuleStore();
|
|
|
|
|
const { activeModule } = useModuleStore();
|
2025-06-23 09:37:53 +05:30
|
|
|
const { assetStore, eventStore, productStore } = useSceneContext();
|
2025-07-28 15:05:28 +05:30
|
|
|
const { removeAsset, resetAnimation } = assetStore();
|
2025-06-10 15:28:23 +05:30
|
|
|
const { setTop } = useTopData();
|
|
|
|
|
const { setLeft } = useLeftData();
|
2025-07-08 14:19:09 +05:30
|
|
|
const { getIsEventInProduct, addPoint } = productStore();
|
2025-06-23 09:37:53 +05:30
|
|
|
const { getEventByModelUuid } = eventStore();
|
2025-06-10 15:28:23 +05:30
|
|
|
const { selectedProductStore } = useProductContext();
|
|
|
|
|
const { selectedProduct } = selectedProductStore();
|
|
|
|
|
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
|
|
|
|
|
const { socket } = useSocketStore();
|
|
|
|
|
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
2025-07-02 17:31:17 +05:30
|
|
|
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
2025-06-30 12:22:42 +05:30
|
|
|
const { limitDistance } = useLimitDistance();
|
2025-06-10 15:28:23 +05:30
|
|
|
const { renderDistance } = useRenderDistance();
|
2025-07-08 14:19:09 +05:30
|
|
|
const leftDrag = useRef(false);
|
|
|
|
|
const isLeftMouseDown = useRef(false);
|
|
|
|
|
const rightDrag = useRef(false);
|
|
|
|
|
const isRightMouseDown = useRef(false);
|
2025-07-08 16:31:20 +05:30
|
|
|
const [isRendered, setIsRendered] = useState(false);
|
|
|
|
|
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
|
|
|
|
|
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
2025-07-18 14:14:34 +05:30
|
|
|
const [isSelected, setIsSelected] = useState(false);
|
2025-07-08 16:31:20 +05:30
|
|
|
const groupRef = useRef<THREE.Group>(null);
|
|
|
|
|
const [ikData, setIkData] = useState<any>();
|
|
|
|
|
const { selectedVersionStore } = useVersionContext();
|
|
|
|
|
const { selectedVersion } = selectedVersionStore();
|
|
|
|
|
const { userId, organization } = getUserData();
|
|
|
|
|
const { projectId } = useParams();
|
2025-07-18 14:14:34 +05:30
|
|
|
const { selectedAssets } = useSelectedAssets();
|
2025-07-28 17:21:07 +05:30
|
|
|
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
2025-06-10 15:28:23 +05:30
|
|
|
|
2025-07-07 10:11:37 +05:30
|
|
|
const updateBackend = (
|
|
|
|
|
productName: string,
|
|
|
|
|
productUuid: string,
|
|
|
|
|
projectId: string,
|
|
|
|
|
eventData: EventsSchema
|
|
|
|
|
) => {
|
|
|
|
|
upsertProductOrEventApi({
|
|
|
|
|
productName: productName,
|
|
|
|
|
productUuid: productUuid,
|
|
|
|
|
projectId: projectId,
|
|
|
|
|
eventDatas: eventData,
|
|
|
|
|
versionId: selectedVersion?.versionId || '',
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-08 16:31:20 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') {
|
|
|
|
|
getAssetIksApi(asset.assetId).then((data) => {
|
|
|
|
|
if (data.iks) {
|
2025-07-09 13:38:06 +05:30
|
|
|
const iks: IK[] = data.iks;
|
|
|
|
|
setIkData(iks);
|
2025-07-08 16:31:20 +05:30
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}, [asset.modelUuid, ikData])
|
|
|
|
|
|
2025-07-28 13:05:32 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (gltfScene) {
|
|
|
|
|
gltfScene.traverse((child: any) => {
|
|
|
|
|
if (child.isMesh) {
|
|
|
|
|
child.castShadow = true;
|
|
|
|
|
child.receiveShadow = true;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}, [gltfScene]);
|
|
|
|
|
|
2025-06-12 09:31:51 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
setDeletableFloorItem(null);
|
2025-07-08 10:38:16 +05:30
|
|
|
if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) {
|
2025-07-02 17:31:17 +05:30
|
|
|
resetAnimation(asset.modelUuid);
|
|
|
|
|
}
|
|
|
|
|
}, [activeModule, toolMode, selectedFloorItem])
|
2025-06-12 09:31:51 +05:30
|
|
|
|
2025-07-08 10:38:16 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (selectedFloorItem && selectedFloorItem.userData.modelUuid === asset.modelUuid) {
|
|
|
|
|
setSelectedFloorItem(groupRef.current);
|
|
|
|
|
}
|
|
|
|
|
}, [isRendered, selectedFloorItem])
|
|
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
const loader = new GLTFLoader();
|
|
|
|
|
const dracoLoader = new DRACOLoader();
|
|
|
|
|
|
|
|
|
|
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
|
|
|
|
loader.setDRACOLoader(dracoLoader);
|
|
|
|
|
const loadModel = async () => {
|
|
|
|
|
try {
|
2025-06-26 15:11:52 +05:30
|
|
|
|
|
|
|
|
// Check Cache
|
2025-06-10 15:28:23 +05:30
|
|
|
const assetId = asset.assetId;
|
|
|
|
|
const cachedModel = THREE.Cache.get(assetId);
|
|
|
|
|
if (cachedModel) {
|
2025-07-02 17:31:17 +05:30
|
|
|
const clone: any = SkeletonUtils.clone(cachedModel.scene);
|
|
|
|
|
clone.animations = cachedModel.animations || [];
|
|
|
|
|
setGltfScene(clone);
|
|
|
|
|
calculateBoundingBox(clone);
|
2025-06-10 15:28:23 +05:30
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check IndexedDB
|
|
|
|
|
const indexedDBModel = await retrieveGLTF(assetId);
|
|
|
|
|
if (indexedDBModel) {
|
|
|
|
|
const blobUrl = URL.createObjectURL(indexedDBModel);
|
|
|
|
|
loader.load(blobUrl, (gltf) => {
|
|
|
|
|
URL.revokeObjectURL(blobUrl);
|
|
|
|
|
THREE.Cache.remove(blobUrl);
|
|
|
|
|
THREE.Cache.add(assetId, gltf);
|
|
|
|
|
setGltfScene(gltf.scene.clone());
|
|
|
|
|
calculateBoundingBox(gltf.scene);
|
|
|
|
|
},
|
|
|
|
|
undefined,
|
|
|
|
|
(error) => {
|
|
|
|
|
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
|
|
|
|
|
URL.revokeObjectURL(blobUrl);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch from Backend
|
|
|
|
|
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
|
|
|
|
|
const handleBackendLoad = async (gltf: GLTF) => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(modelUrl);
|
|
|
|
|
const modelBlob = await response.blob();
|
|
|
|
|
await storeGLTF(assetId, modelBlob);
|
|
|
|
|
THREE.Cache.add(assetId, gltf);
|
|
|
|
|
setGltfScene(gltf.scene.clone());
|
|
|
|
|
calculateBoundingBox(gltf.scene);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
loader.load(
|
|
|
|
|
modelUrl,
|
|
|
|
|
handleBackendLoad,
|
|
|
|
|
undefined,
|
|
|
|
|
(error) => {
|
|
|
|
|
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to load model:", asset.assetId, err);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const calculateBoundingBox = (scene: THREE.Object3D) => {
|
|
|
|
|
const box = new THREE.Box3().setFromObject(scene);
|
|
|
|
|
setBoundingBox(box);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadModel();
|
|
|
|
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-07-28 15:26:49 +05:30
|
|
|
useFrame(() => {
|
|
|
|
|
const assetPosition = scene.getObjectByProperty("uuid", asset.modelUuid)?.position;
|
|
|
|
|
if (limitDistance && assetPosition) {
|
|
|
|
|
if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) {
|
|
|
|
|
setIsRendered(true);
|
|
|
|
|
} else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) {
|
|
|
|
|
setIsRendered(false);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!isRendered) {
|
|
|
|
|
setIsRendered(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
const handleDblClick = (asset: Asset) => {
|
|
|
|
|
if (asset) {
|
|
|
|
|
if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
|
|
|
|
const size = boundingBox.getSize(new THREE.Vector3());
|
|
|
|
|
const center = boundingBox.getCenter(new THREE.Vector3());
|
|
|
|
|
|
|
|
|
|
const front = new THREE.Vector3(0, 0, 1);
|
|
|
|
|
groupRef.current.localToWorld(front);
|
|
|
|
|
front.sub(groupRef.current.position).normalize();
|
|
|
|
|
|
|
|
|
|
const distance = Math.max(size.x, size.y, size.z) * 2;
|
|
|
|
|
const newPosition = center.clone().addScaledVector(front, distance);
|
|
|
|
|
|
|
|
|
|
(controls as CameraControls).setPosition(
|
|
|
|
|
newPosition.x,
|
|
|
|
|
newPosition.y,
|
|
|
|
|
newPosition.z,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
|
|
|
|
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
|
|
|
|
cover: true,
|
|
|
|
|
paddingTop: 5,
|
|
|
|
|
paddingLeft: 5,
|
|
|
|
|
paddingBottom: 5,
|
|
|
|
|
paddingRight: 5,
|
|
|
|
|
});
|
|
|
|
|
setSelectedFloorItem(groupRef.current);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-08 14:19:09 +05:30
|
|
|
const handleClick = (evt: ThreeEvent<MouseEvent>, asset: Asset) => {
|
|
|
|
|
if (leftDrag.current || toggleView) return;
|
2025-06-10 15:28:23 +05:30
|
|
|
if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
|
2025-06-23 09:37:53 +05:30
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
//REST
|
|
|
|
|
|
|
|
|
|
// const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName);
|
|
|
|
|
|
|
|
|
|
//SOCKET
|
|
|
|
|
|
|
|
|
|
const data = {
|
2025-06-23 09:37:53 +05:30
|
|
|
organization,
|
2025-06-10 15:28:23 +05:30
|
|
|
modelUuid: asset.modelUuid,
|
|
|
|
|
modelName: asset.modelName,
|
|
|
|
|
socketId: socket.id,
|
|
|
|
|
userId,
|
2025-06-23 09:37:53 +05:30
|
|
|
versionId: selectedVersion?.versionId || '',
|
2025-06-10 15:28:23 +05:30
|
|
|
projectId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = socket.emit('v1:model-asset:delete', data)
|
|
|
|
|
|
2025-06-23 09:37:53 +05:30
|
|
|
eventStore.getState().removeEvent(asset.modelUuid);
|
2025-07-07 10:11:37 +05:30
|
|
|
const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid);
|
|
|
|
|
|
|
|
|
|
updatedEvents.forEach((event) => {
|
|
|
|
|
updateBackend(
|
|
|
|
|
selectedProduct.productName,
|
|
|
|
|
selectedProduct.productUuid,
|
|
|
|
|
projectId || '',
|
|
|
|
|
event
|
|
|
|
|
);
|
|
|
|
|
})
|
2025-06-10 15:28:23 +05:30
|
|
|
|
|
|
|
|
if (response) {
|
|
|
|
|
|
|
|
|
|
removeAsset(asset.modelUuid);
|
|
|
|
|
|
|
|
|
|
echo.success("Model Removed!");
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 14:19:09 +05:30
|
|
|
} else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') {
|
|
|
|
|
if (asset.eventData && asset.eventData.type === 'Conveyor') {
|
|
|
|
|
const intersectedPoint = evt.point;
|
|
|
|
|
const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone());
|
|
|
|
|
if (localPosition) {
|
|
|
|
|
const conveyorPoint: ConveyorPointSchema = {
|
|
|
|
|
uuid: THREE.MathUtils.generateUUID(),
|
|
|
|
|
position: [localPosition?.x, localPosition?.y, localPosition?.z],
|
|
|
|
|
rotation: [0, 0, 0],
|
|
|
|
|
action: {
|
|
|
|
|
actionUuid: THREE.MathUtils.generateUUID(),
|
|
|
|
|
actionName: `Action 1`,
|
|
|
|
|
actionType: 'default',
|
|
|
|
|
material: 'Default Material',
|
|
|
|
|
delay: 0,
|
|
|
|
|
spawnInterval: 5,
|
|
|
|
|
spawnCount: 1,
|
|
|
|
|
triggers: []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint);
|
|
|
|
|
|
|
|
|
|
if (event) {
|
|
|
|
|
updateBackend(
|
|
|
|
|
selectedProduct.productName,
|
|
|
|
|
selectedProduct.productUuid,
|
|
|
|
|
projectId || '',
|
|
|
|
|
event
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-10 15:28:23 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-12 09:31:51 +05:30
|
|
|
const handlePointerOver = useCallback((asset: Asset) => {
|
|
|
|
|
if (activeTool === "delete" && activeModule === 'builder') {
|
2025-06-10 15:28:23 +05:30
|
|
|
if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
setDeletableFloorItem(groupRef.current);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-12 09:31:51 +05:30
|
|
|
}, [activeTool, activeModule, deletableFloorItem]);
|
2025-06-10 15:28:23 +05:30
|
|
|
|
2025-07-08 11:44:25 +05:30
|
|
|
const handlePointerOut = useCallback((evt: ThreeEvent<MouseEvent>, asset: Asset) => {
|
|
|
|
|
if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
|
2025-06-10 15:28:23 +05:30
|
|
|
setDeletableFloorItem(null);
|
|
|
|
|
}
|
2025-06-12 09:31:51 +05:30
|
|
|
}, [activeTool, deletableFloorItem]);
|
2025-06-10 15:28:23 +05:30
|
|
|
|
|
|
|
|
const handleContextMenu = (asset: Asset, evt: ThreeEvent<MouseEvent>) => {
|
2025-07-08 14:19:09 +05:30
|
|
|
if (rightDrag.current || toggleView) return;
|
2025-06-10 15:28:23 +05:30
|
|
|
if (activeTool === "cursor" && subModule === 'simulations') {
|
|
|
|
|
if (asset.modelUuid) {
|
|
|
|
|
const canvasElement = gl.domElement;
|
|
|
|
|
const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid);
|
|
|
|
|
if (isInProduct) {
|
|
|
|
|
const event = getEventByModelUuid(asset.modelUuid);
|
|
|
|
|
if (event) {
|
|
|
|
|
setSelectedAsset(event);
|
|
|
|
|
const canvasRect = canvasElement.getBoundingClientRect();
|
|
|
|
|
const relativeX = evt.clientX - canvasRect.left;
|
|
|
|
|
const relativeY = evt.clientY - canvasRect.top;
|
|
|
|
|
setTop(relativeY);
|
|
|
|
|
setLeft(relativeX);
|
|
|
|
|
} else {
|
|
|
|
|
clearSelectedAsset();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const event = getEventByModelUuid(asset.modelUuid);
|
|
|
|
|
if (event) {
|
|
|
|
|
setSelectedAsset(event)
|
|
|
|
|
const canvasRect = canvasElement.getBoundingClientRect();
|
|
|
|
|
const relativeX = evt.clientX - canvasRect.left;
|
|
|
|
|
const relativeY = evt.clientY - canvasRect.top;
|
|
|
|
|
setTop(relativeY);
|
|
|
|
|
setLeft(relativeX);
|
|
|
|
|
} else {
|
|
|
|
|
clearSelectedAsset()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
clearSelectedAsset()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
clearSelectedAsset()
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 17:31:17 +05:30
|
|
|
|
2025-07-08 14:19:09 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
const canvasElement = gl.domElement;
|
|
|
|
|
|
|
|
|
|
const onPointerDown = (evt: any) => {
|
|
|
|
|
if (evt.button === 0) {
|
|
|
|
|
isLeftMouseDown.current = true;
|
|
|
|
|
leftDrag.current = false;
|
|
|
|
|
}
|
|
|
|
|
if (evt.button === 2) {
|
|
|
|
|
isRightMouseDown.current = true;
|
|
|
|
|
rightDrag.current = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onPointerMove = () => {
|
|
|
|
|
if (isLeftMouseDown.current) {
|
|
|
|
|
leftDrag.current = true;
|
|
|
|
|
}
|
|
|
|
|
if (isRightMouseDown.current) {
|
|
|
|
|
rightDrag.current = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onPointerUp = (evt: any) => {
|
|
|
|
|
if (evt.button === 0) {
|
|
|
|
|
isLeftMouseDown.current = false;
|
|
|
|
|
}
|
|
|
|
|
if (evt.button === 2) {
|
|
|
|
|
isRightMouseDown.current = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
canvasElement.addEventListener('pointerdown', onPointerDown);
|
|
|
|
|
canvasElement.addEventListener('pointermove', onPointerMove);
|
|
|
|
|
canvasElement.addEventListener('pointerup', onPointerUp);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
canvasElement.removeEventListener('pointerdown', onPointerDown);
|
|
|
|
|
canvasElement.removeEventListener('pointermove', onPointerMove);
|
|
|
|
|
canvasElement.removeEventListener('pointerup', onPointerUp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}, [gl])
|
|
|
|
|
|
2025-07-18 14:14:34 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
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-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}
|
2025-07-09 13:38:06 +05:30
|
|
|
userData={{ ...asset, iks: ikData }}
|
2025-07-28 13:05:32 +05:30
|
|
|
castShadow
|
|
|
|
|
receiveShadow
|
2025-06-10 15:28:23 +05:30
|
|
|
onDoubleClick={(e) => {
|
2025-07-08 11:44:25 +05:30
|
|
|
e.stopPropagation();
|
2025-06-10 15:28:23 +05:30
|
|
|
if (!toggleView) {
|
|
|
|
|
handleDblClick(asset);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onClick={(e) => {
|
2025-07-08 11:44:25 +05:30
|
|
|
e.stopPropagation();
|
2025-06-10 15:28:23 +05:30
|
|
|
if (!toggleView) {
|
2025-07-08 14:19:09 +05:30
|
|
|
handleClick(e, asset);
|
2025-06-10 15:28:23 +05:30
|
|
|
}
|
|
|
|
|
}}
|
2025-07-08 11:44:25 +05:30
|
|
|
onPointerOver={(e) => {
|
|
|
|
|
e.stopPropagation();
|
2025-06-10 15:28:23 +05:30
|
|
|
if (!toggleView) {
|
|
|
|
|
handlePointerOver(asset);
|
|
|
|
|
}
|
|
|
|
|
}}
|
2025-07-08 11:44:25 +05:30
|
|
|
onPointerLeave={(e) => {
|
|
|
|
|
e.stopPropagation();
|
2025-06-10 15:28:23 +05:30
|
|
|
if (!toggleView) {
|
2025-07-08 11:44:25 +05:30
|
|
|
handlePointerOut(e, asset);
|
2025-06-10 15:28:23 +05:30
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onContextMenu={(e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
handleContextMenu(asset, e);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{gltfScene && (
|
2025-07-18 14:14:34 +05:30
|
|
|
<>
|
|
|
|
|
{isRendered ? (
|
2025-07-28 15:05:28 +05:30
|
|
|
<>
|
|
|
|
|
|
|
|
|
|
<primitive object={gltfScene} />
|
|
|
|
|
|
|
|
|
|
<ModelAnimator asset={asset} gltfScene={gltfScene} />
|
|
|
|
|
|
|
|
|
|
</>
|
2025-07-18 14:14:34 +05:30
|
|
|
) : (
|
2025-07-28 17:21:07 +05:30
|
|
|
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
|
2025-07-18 14:14:34 +05:30
|
|
|
)}
|
|
|
|
|
{isSelected &&
|
|
|
|
|
<AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />
|
|
|
|
|
}
|
|
|
|
|
</>
|
2025-06-10 15:28:23 +05:30
|
|
|
)}
|
2025-07-08 14:19:09 +05:30
|
|
|
</group>
|
2025-06-10 15:28:23 +05:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Model;
|