added asset unique name to added , duplicated and pasted asset
This commit is contained in:
@@ -5,36 +5,29 @@ import { useThree } from "@react-three/fiber";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
||||
import useModuleStore from "../../../store/ui/useModuleStore";
|
||||
import { useSceneContext } from "../../scene/sceneContext";
|
||||
import { useLoadingProgress, useRenameModeStore, useSelectedItem } from "../../../store/builder/store";
|
||||
import { useLeftData, useTopData } from "../../../store/visualization/useZone3DWidgetStore";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import useAssetResponseHandler from "../../collaboration/responseHandler/useAssetResponseHandler";
|
||||
import Models from "./models/models";
|
||||
import addAssetModel from "./functions/addAssetModel";
|
||||
|
||||
import { getFloorAssets } from "../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi";
|
||||
import useAddAssetModel from "./hooks/useAddAssetModel";
|
||||
|
||||
const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
|
||||
|
||||
function AssetsGroup({ plane }: { readonly plane: React.MutableRefObject<THREE.Mesh | null> }) {
|
||||
const { activeModule } = useModuleStore();
|
||||
const { builderSocket } = useSocketStore();
|
||||
const { controls, gl, pointer, camera, raycaster, scene } = useThree();
|
||||
const { controls, gl, pointer } = useThree();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const { assetStore, versionStore, undoRedo3DStore } = useSceneContext();
|
||||
const { assetStore, versionStore } = useSceneContext();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { setAssets, clearAssets } = assetStore();
|
||||
const { addAssetToScene } = useAssetResponseHandler();
|
||||
const { selectedItem, setSelectedItem } = useSelectedItem();
|
||||
const { selectedItem } = useSelectedItem();
|
||||
const { projectId } = useParams();
|
||||
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
||||
const { userId } = getUserData();
|
||||
const { setTop } = useTopData();
|
||||
const { setLeft } = useLeftData();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
@@ -42,6 +35,8 @@ function AssetsGroup({ plane }: { readonly plane: React.MutableRefObject<THREE.M
|
||||
dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/");
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
const addAssetModel = useAddAssetModel({ plane, loader });
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
|
||||
@@ -145,7 +140,7 @@ function AssetsGroup({ plane }: { readonly plane: React.MutableRefObject<THREE.M
|
||||
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
|
||||
addAssetModel(scene, raycaster, camera, pointer, builderSocket, selectedItem, setSelectedItem, addAssetToScene, push3D, plane, loader, selectedVersion, projectId, userId);
|
||||
addAssetModel();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -184,7 +179,7 @@ function AssetsGroup({ plane }: { readonly plane: React.MutableRefObject<THREE.M
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
}, [selectedItem, camera, activeModule, controls, isRenameMode]);
|
||||
}, [selectedItem, activeModule, controls, isRenameMode]);
|
||||
|
||||
return <Models loader={loader} />;
|
||||
}
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
import * as THREE from "three";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils";
|
||||
import { Socket } from "socket.io-client";
|
||||
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||
import PointsCalculator from "../../../simulation/functions/pointsCalculator";
|
||||
import createEventData from "../../../simulation/functions/createEventData";
|
||||
|
||||
import { getUserData } from "../../../../functions/getUserData";
|
||||
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
|
||||
async function addAssetModel(
|
||||
scene: THREE.Scene,
|
||||
raycaster: THREE.Raycaster,
|
||||
camera: THREE.Camera,
|
||||
pointer: THREE.Vector2,
|
||||
builderSocket: Socket<any> | null,
|
||||
selectedItem: any,
|
||||
setSelectedItem: any,
|
||||
addAssetToScene: (asset: Asset, callback?: () => void) => void,
|
||||
push3D: (entry: UndoRedo3DTypes) => void,
|
||||
plane: React.MutableRefObject<THREE.Mesh | null>,
|
||||
loader: GLTFLoader,
|
||||
selectedVersion?: Version | null,
|
||||
projectId?: string,
|
||||
userId?: string
|
||||
): Promise<void> {
|
||||
////////// Load Floor GLtf's and set the positions, rotation, type etc. in state and store in localstorage //////////
|
||||
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
try {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const wallFloorsGroup = scene.getObjectByName("Walls-Floors-Group") as THREE.Group | null;
|
||||
const floorsGroup = scene.getObjectByName("Floors-Group") as THREE.Group | null;
|
||||
const floorChildren = floorsGroup?.children ?? [];
|
||||
const wallFloorChildren = wallFloorsGroup?.children ?? [];
|
||||
const floorIntersections = raycaster.intersectObjects([...floorChildren, ...wallFloorChildren], true);
|
||||
const intersectedFloor = floorIntersections.find((intersect) => intersect.object.name.includes("Floor"));
|
||||
|
||||
const planeIntersections = raycaster.intersectObject(plane.current!, true);
|
||||
const intersectedPlane = planeIntersections[0];
|
||||
|
||||
let intersectPoint: THREE.Vector3 | null = null;
|
||||
|
||||
if (intersectedFloor && intersectedPlane) {
|
||||
// intersectPoint = intersectedFloor.distance < intersectedPlane.distance ?
|
||||
// new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z)
|
||||
// : new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
if (intersectedFloor.distance < intersectedPlane.distance) {
|
||||
if (intersectedFloor.object.userData.floorUuid) {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, intersectedFloor.object.userData.floorDepth, intersectedFloor.point.z);
|
||||
} else {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, 0, intersectedFloor.point.z);
|
||||
}
|
||||
} else {
|
||||
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
}
|
||||
} else if (intersectedFloor) {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z);
|
||||
} else if (intersectedPlane) {
|
||||
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
}
|
||||
|
||||
if (intersectPoint) {
|
||||
if (intersectPoint.y < 0) {
|
||||
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
|
||||
}
|
||||
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||
if (cachedModel) {
|
||||
handleModelLoad(cachedModel, intersectPoint!, selectedItem, addAssetToScene, push3D, builderSocket, selectedVersion?.versionId || "", projectId, userId);
|
||||
return;
|
||||
} else {
|
||||
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||
if (cachedModelBlob) {
|
||||
const blobUrl = URL.createObjectURL(cachedModelBlob);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
handleModelLoad(gltf, intersectPoint!, selectedItem, addAssetToScene, push3D, builderSocket, selectedVersion?.versionId || "", projectId, userId);
|
||||
});
|
||||
} else {
|
||||
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, async (gltf) => {
|
||||
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||
await storeGLTF(selectedItem.id, modelBlob);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, addAssetToScene, push3D, builderSocket, selectedVersion?.versionId || "", projectId, userId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
echo.error("Failed to add asset");
|
||||
} finally {
|
||||
setSelectedItem({});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleModelLoad(
|
||||
gltf: any,
|
||||
intersectPoint: THREE.Vector3,
|
||||
selectedItem: any,
|
||||
addAssetToScene: (asset: Asset, callback?: () => void) => void,
|
||||
push3D: (entry: UndoRedo3DTypes) => void,
|
||||
builderSocket: Socket<any> | null,
|
||||
versionId: string,
|
||||
projectId?: string,
|
||||
userId?: string
|
||||
) {
|
||||
const { organization } = getUserData();
|
||||
const model = gltf.scene.clone();
|
||||
model.userData = {
|
||||
name: selectedItem.name,
|
||||
modelId: selectedItem.id,
|
||||
modelUuid: model.uuid,
|
||||
};
|
||||
model.position.set(intersectPoint.x, intersectPoint.y, intersectPoint.z);
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleAfterGsap);
|
||||
|
||||
const newFloorItem: Asset = {
|
||||
modelUuid: model.uuid,
|
||||
modelName: selectedItem.name,
|
||||
assetId: selectedItem.id,
|
||||
position: [intersectPoint.x, intersectPoint.y, intersectPoint.z],
|
||||
rotation: [0, 0, 0],
|
||||
scale: [1, 1, 1],
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
isCollidable: false,
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
const socketData: any = {
|
||||
organization,
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
position: newFloorItem.position,
|
||||
rotation: newFloorItem.rotation,
|
||||
scale: newFloorItem.scale,
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: builderSocket?.id || "",
|
||||
versionId: versionId,
|
||||
projectId: projectId,
|
||||
userId: userId,
|
||||
};
|
||||
|
||||
const apiData: any = {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
position: newFloorItem.position,
|
||||
assetId: newFloorItem.assetId,
|
||||
rotation: newFloorItem.rotation,
|
||||
scale: newFloorItem.scale,
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
versionId: versionId,
|
||||
projectId: projectId || "",
|
||||
};
|
||||
|
||||
if (selectedItem.type) {
|
||||
const data = PointsCalculator(selectedItem.type, gltf.scene.clone(), new THREE.Vector3(...model.rotation));
|
||||
|
||||
if (!data?.points) return;
|
||||
|
||||
const eventData: any = { type: selectedItem.type, subType: selectedItem.subType || "" };
|
||||
|
||||
const assetEvent = createEventData(newFloorItem, selectedItem.type, selectedItem.subType, data.points);
|
||||
if (!assetEvent) return;
|
||||
|
||||
if ("point" in assetEvent) {
|
||||
eventData.point = {
|
||||
uuid: assetEvent.point.uuid,
|
||||
position: assetEvent.point.position,
|
||||
rotation: assetEvent.point.rotation,
|
||||
};
|
||||
} else if ("points" in assetEvent) {
|
||||
eventData.points = assetEvent.points.map((point) => ({
|
||||
uuid: point.uuid,
|
||||
position: point.position,
|
||||
rotation: point.rotation,
|
||||
}));
|
||||
}
|
||||
|
||||
socketData.eventData = JSON.parse(JSON.stringify(eventData));
|
||||
apiData.eventData = JSON.parse(JSON.stringify(eventData));
|
||||
}
|
||||
|
||||
if (!builderSocket?.connected) {
|
||||
// API
|
||||
|
||||
setAssetsApi(apiData)
|
||||
.then((data) => {
|
||||
if (!data.message) {
|
||||
echo.error(`Error adding asset: ${newFloorItem.modelUuid}`);
|
||||
return;
|
||||
}
|
||||
if (data.message === "Model created 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,
|
||||
isCollidable: true,
|
||||
isVisible: data.data.isVisible,
|
||||
opacity: 1,
|
||||
...(data.data.eventData ? { eventData: data.data.eventData } : {}),
|
||||
};
|
||||
|
||||
addAssetToScene(model, () => {
|
||||
push3D({
|
||||
type: "Scene",
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Add",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
timeStap: new Date().toISOString(),
|
||||
assetData: model,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
echo.info(`Added asset: ${model.modelUuid}`);
|
||||
});
|
||||
} else {
|
||||
echo.error(`Error adding asset: ${data?.data?.modelName}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
echo.error(`Error adding asset: ${newFloorItem.modelUuid}`);
|
||||
});
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
push3D({
|
||||
type: "Scene",
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Add",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
timeStap: new Date().toISOString(),
|
||||
assetData: model,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log("socketData: ", socketData);
|
||||
builderSocket.emit("v1:model-asset:add", socketData);
|
||||
}
|
||||
}
|
||||
|
||||
export default addAssetModel;
|
||||
@@ -0,0 +1,60 @@
|
||||
const generateUniqueAssetName = (baseName: string, existingAssets: Asset[], usedNames: Set<string> = new Set()): string => {
|
||||
// Extract the base name without any existing numbers
|
||||
const baseMatch = baseName.match(/^(.*?)(?:\.(\d+))?$/);
|
||||
const trueBaseName = baseMatch![1];
|
||||
const existingNumber = baseMatch![2] ? parseInt(baseMatch![2], 10) : 0;
|
||||
|
||||
// Find all assets that match the true base name pattern (both existing and newly used)
|
||||
const allUsedNumbers: number[] = [];
|
||||
const pattern = new RegExp(`^${trueBaseName}(?:\\.(\\d+))?$`);
|
||||
|
||||
// Check existing assets
|
||||
existingAssets.forEach((asset) => {
|
||||
const match = asset.modelName.match(pattern);
|
||||
if (match) {
|
||||
if (!match[1]) {
|
||||
allUsedNumbers.push(0);
|
||||
} else {
|
||||
allUsedNumbers.push(parseInt(match[1], 10));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check names we've already assigned in this copy operation
|
||||
usedNames.forEach((usedName) => {
|
||||
const match = usedName.match(pattern);
|
||||
if (match) {
|
||||
if (!match[1]) {
|
||||
allUsedNumbers.push(0);
|
||||
} else {
|
||||
allUsedNumbers.push(parseInt(match[1], 10));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also include the existing number from the copied asset
|
||||
if (!allUsedNumbers.includes(existingNumber)) {
|
||||
allUsedNumbers.push(existingNumber);
|
||||
}
|
||||
|
||||
if (allUsedNumbers.length === 0) {
|
||||
return trueBaseName;
|
||||
}
|
||||
|
||||
const maxNumber = Math.max(...allUsedNumbers);
|
||||
let nextNumber = maxNumber + 1;
|
||||
|
||||
// Find the next available number that hasn't been used
|
||||
while (allUsedNumbers.includes(nextNumber)) {
|
||||
nextNumber++;
|
||||
}
|
||||
|
||||
if (nextNumber === 1 && existingNumber === 0) {
|
||||
// If we're going from no number to first number, use "001"
|
||||
return `${trueBaseName}.${"001"}`;
|
||||
}
|
||||
|
||||
return `${trueBaseName}.${nextNumber.toString().padStart(3, "0")}`;
|
||||
};
|
||||
|
||||
export default generateUniqueAssetName;
|
||||
280
app/src/modules/builder/asset/hooks/useAddAssetModel.ts
Normal file
280
app/src/modules/builder/asset/hooks/useAddAssetModel.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import * as THREE from "three";
|
||||
import { useCallback } from "react";
|
||||
import { Socket } from "socket.io-client";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils";
|
||||
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||
import { useSocketStore } from "../../../../store/socket/useSocketStore";
|
||||
import { useSelectedItem } from "../../../../store/builder/store";
|
||||
import { useSceneContext } from "../../../scene/sceneContext";
|
||||
import useAssetResponseHandler from "../../../collaboration/responseHandler/useAssetResponseHandler";
|
||||
import PointsCalculator from "../../../simulation/functions/pointsCalculator";
|
||||
import createEventData from "../../../simulation/functions/createEventData";
|
||||
import generateUniqueAssetName from "../functions/generateUniqueAssetName";
|
||||
import { getUserData } from "../../../../functions/getUserData";
|
||||
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
|
||||
interface UseAddAssetModelProps {
|
||||
plane: React.MutableRefObject<THREE.Mesh | null>;
|
||||
loader: GLTFLoader;
|
||||
}
|
||||
|
||||
function useAddAssetModel({ plane, loader }: UseAddAssetModelProps) {
|
||||
const { builderSocket } = useSocketStore();
|
||||
const { pointer, camera, raycaster, scene } = useThree();
|
||||
const { versionStore, undoRedo3DStore, assetStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { addAssetToScene } = useAssetResponseHandler();
|
||||
const { selectedItem, setSelectedItem } = useSelectedItem();
|
||||
const { projectId } = useParams();
|
||||
const { userId } = getUserData();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
|
||||
const handleModelLoad = useCallback(
|
||||
async (
|
||||
gltf: any,
|
||||
intersectPoint: THREE.Vector3,
|
||||
selectedItem: any,
|
||||
addAssetToScene: (asset: Asset, callback?: () => void) => void,
|
||||
push3D: (entry: UndoRedo3DTypes) => void,
|
||||
builderSocket: Socket<any> | null,
|
||||
versionId: string,
|
||||
projectId?: string,
|
||||
userId?: string
|
||||
) => {
|
||||
const { organization } = getUserData();
|
||||
const model = gltf.scene.clone();
|
||||
const uniqueModelName = generateUniqueAssetName(selectedItem.name, assets);
|
||||
|
||||
model.userData = {
|
||||
name: uniqueModelName,
|
||||
modelId: selectedItem.id,
|
||||
modelUuid: model.uuid,
|
||||
};
|
||||
model.position.set(intersectPoint.x, intersectPoint.y, intersectPoint.z);
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleAfterGsap);
|
||||
|
||||
const newFloorItem: Asset = {
|
||||
modelUuid: model.uuid,
|
||||
modelName: uniqueModelName,
|
||||
assetId: selectedItem.id,
|
||||
position: [intersectPoint.x, intersectPoint.y, intersectPoint.z],
|
||||
rotation: [0, 0, 0],
|
||||
scale: [1, 1, 1],
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
isCollidable: false,
|
||||
opacity: 1,
|
||||
};
|
||||
|
||||
const socketData: any = {
|
||||
organization,
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
position: newFloorItem.position,
|
||||
rotation: newFloorItem.rotation,
|
||||
scale: newFloorItem.scale,
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: builderSocket?.id || "",
|
||||
versionId: versionId,
|
||||
projectId: projectId,
|
||||
userId: userId,
|
||||
};
|
||||
|
||||
const apiData: any = {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
position: newFloorItem.position,
|
||||
assetId: newFloorItem.assetId,
|
||||
rotation: newFloorItem.rotation,
|
||||
scale: newFloorItem.scale,
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
versionId: versionId,
|
||||
projectId: projectId || "",
|
||||
};
|
||||
|
||||
if (selectedItem.type) {
|
||||
const data = PointsCalculator(selectedItem.type, gltf.scene.clone(), new THREE.Vector3(...model.rotation));
|
||||
|
||||
if (!data?.points) return;
|
||||
|
||||
const eventData: any = { type: selectedItem.type, subType: selectedItem.subType || "" };
|
||||
|
||||
const assetEvent = createEventData(newFloorItem, selectedItem.type, selectedItem.subType, data.points);
|
||||
if (!assetEvent) return;
|
||||
|
||||
if ("point" in assetEvent) {
|
||||
eventData.point = {
|
||||
uuid: assetEvent.point.uuid,
|
||||
position: assetEvent.point.position,
|
||||
rotation: assetEvent.point.rotation,
|
||||
};
|
||||
} else if ("points" in assetEvent) {
|
||||
eventData.points = assetEvent.points.map((point) => ({
|
||||
uuid: point.uuid,
|
||||
position: point.position,
|
||||
rotation: point.rotation,
|
||||
}));
|
||||
}
|
||||
|
||||
socketData.eventData = JSON.parse(JSON.stringify(eventData));
|
||||
apiData.eventData = JSON.parse(JSON.stringify(eventData));
|
||||
newFloorItem.eventData = JSON.parse(JSON.stringify(eventData));
|
||||
}
|
||||
|
||||
if (!builderSocket?.connected) {
|
||||
// API
|
||||
|
||||
setAssetsApi(apiData)
|
||||
.then((data) => {
|
||||
if (!data.message) {
|
||||
echo.error(`Error adding asset: ${newFloorItem.modelUuid}`);
|
||||
return;
|
||||
}
|
||||
if (data.message === "Model created 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,
|
||||
isCollidable: true,
|
||||
isVisible: data.data.isVisible,
|
||||
opacity: 1,
|
||||
...(data.data.eventData ? { eventData: data.data.eventData } : {}),
|
||||
};
|
||||
|
||||
addAssetToScene(model, () => {
|
||||
push3D({
|
||||
type: "Scene",
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Add",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
timeStap: new Date().toISOString(),
|
||||
assetData: model,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
echo.info(`Added asset: ${model.modelUuid}`);
|
||||
});
|
||||
} else {
|
||||
echo.error(`Error adding asset: ${data?.data?.modelName}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
echo.error(`Error adding asset: ${newFloorItem.modelUuid}`);
|
||||
});
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
push3D({
|
||||
type: "Scene",
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Add",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
timeStap: new Date().toISOString(),
|
||||
assetData: newFloorItem,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
builderSocket.emit("v1:model-asset:add", socketData);
|
||||
}
|
||||
},
|
||||
[assets]
|
||||
);
|
||||
|
||||
const addAssetModel = useCallback(async (): Promise<void> => {
|
||||
if (!selectedItem.id) return;
|
||||
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
try {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const wallFloorsGroup = scene.getObjectByName("Walls-Floors-Group") as THREE.Group | null;
|
||||
const floorsGroup = scene.getObjectByName("Floors-Group") as THREE.Group | null;
|
||||
const floorChildren = floorsGroup?.children ?? [];
|
||||
const wallFloorChildren = wallFloorsGroup?.children ?? [];
|
||||
const floorIntersections = raycaster.intersectObjects([...floorChildren, ...wallFloorChildren], true);
|
||||
const intersectedFloor = floorIntersections.find((intersect) => intersect.object.name.includes("Floor"));
|
||||
|
||||
const planeIntersections = raycaster.intersectObject(plane.current!, true);
|
||||
const intersectedPlane = planeIntersections[0];
|
||||
|
||||
let intersectPoint: THREE.Vector3 | null = null;
|
||||
|
||||
if (intersectedFloor && intersectedPlane) {
|
||||
// intersectPoint = intersectedFloor.distance < intersectedPlane.distance ?
|
||||
// new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z)
|
||||
// : new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
if (intersectedFloor.distance < intersectedPlane.distance) {
|
||||
if (intersectedFloor.object.userData.floorUuid) {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, intersectedFloor.object.userData.floorDepth, intersectedFloor.point.z);
|
||||
} else {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, 0, intersectedFloor.point.z);
|
||||
}
|
||||
} else {
|
||||
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
}
|
||||
} else if (intersectedFloor) {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z);
|
||||
} else if (intersectedPlane) {
|
||||
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
}
|
||||
|
||||
if (intersectPoint) {
|
||||
if (intersectPoint.y < 0) {
|
||||
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
|
||||
}
|
||||
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||
if (cachedModel) {
|
||||
handleModelLoad(cachedModel, intersectPoint, selectedItem, addAssetToScene, push3D, builderSocket, selectedVersion?.versionId || "", projectId, userId);
|
||||
return;
|
||||
} else {
|
||||
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||
if (cachedModelBlob) {
|
||||
const blobUrl = URL.createObjectURL(cachedModelBlob);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
handleModelLoad(gltf, intersectPoint!, selectedItem, addAssetToScene, push3D, builderSocket, selectedVersion?.versionId || "", projectId, userId);
|
||||
});
|
||||
} else {
|
||||
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, async (gltf) => {
|
||||
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||
await storeGLTF(selectedItem.id, modelBlob);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, addAssetToScene, push3D, builderSocket, selectedVersion?.versionId || "", projectId, userId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
echo.error("Failed to add asset");
|
||||
} finally {
|
||||
setSelectedItem({});
|
||||
}
|
||||
}, [scene, raycaster, camera, pointer, builderSocket, selectedItem, setSelectedItem, addAssetToScene, push3D, plane, loader, selectedVersion, projectId, userId, handleModelLoad]);
|
||||
|
||||
return addAssetModel;
|
||||
}
|
||||
|
||||
export default useAddAssetModel;
|
||||
@@ -11,6 +11,7 @@ import { getUserData } from "../../../../functions/getUserData";
|
||||
import useAssetResponseHandler from "../../../collaboration/responseHandler/useAssetResponseHandler";
|
||||
|
||||
import createEventData from "../../../simulation/functions/createEventData";
|
||||
import generateUniqueAssetName from "../../../builder/asset/functions/generateUniqueAssetName";
|
||||
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
import { deleteFloorAssetApi } from "../../../../services/factoryBuilder/asset/floorAsset/deleteFloorAssetApi";
|
||||
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
@@ -29,7 +30,6 @@ const CutCopyPasteControls3D = () => {
|
||||
removeAsset,
|
||||
getAssetById,
|
||||
selectedAssets,
|
||||
setSelectedAssets,
|
||||
copiedObjects,
|
||||
setCopiedObjects,
|
||||
pastedObjects,
|
||||
@@ -281,7 +281,7 @@ const CutCopyPasteControls3D = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const copySelection = () => {
|
||||
const copySelection = useCallback(() => {
|
||||
if (selectedAssets.length > 0) {
|
||||
const newClones = selectedAssets.map((asset: any) => {
|
||||
const clone = SkeletonUtils.clone(asset);
|
||||
@@ -291,16 +291,20 @@ const CutCopyPasteControls3D = () => {
|
||||
setCopiedObjects(newClones);
|
||||
echo.info("Objects copied!");
|
||||
}
|
||||
};
|
||||
}, [selectedAssets, setCopiedObjects]);
|
||||
|
||||
const pasteCopiedObjects = () => {
|
||||
const pasteCopiedObjects = useCallback(() => {
|
||||
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
|
||||
const { center, relatives } = calculateRelativePositions(copiedObjects);
|
||||
setRelativePositions(relatives);
|
||||
const usedNames = new Set<string>();
|
||||
|
||||
const newPastedObjects = copiedObjects.map((obj: any) => {
|
||||
const clone = SkeletonUtils.clone(obj);
|
||||
clone.userData.modelUuid = THREE.MathUtils.generateUUID();
|
||||
const uniqueModelName = generateUniqueAssetName(obj.userData.modelName, assets, usedNames);
|
||||
clone.userData.modelName = uniqueModelName;
|
||||
usedNames.add(uniqueModelName);
|
||||
return clone;
|
||||
});
|
||||
|
||||
@@ -333,7 +337,7 @@ const CutCopyPasteControls3D = () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [copiedObjects, pastedObjects.length, assets]);
|
||||
|
||||
const addPastedObjects = () => {
|
||||
if (pastedObjects.length === 0) return;
|
||||
@@ -587,7 +591,6 @@ const CutCopyPasteControls3D = () => {
|
||||
setPastedObjects([]);
|
||||
setDuplicatedObjects([]);
|
||||
setRotatedObjects([]);
|
||||
setSelectedAssets([]);
|
||||
setIsPasting(false);
|
||||
setCenterOffset(null);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { handleAssetPositionSnap } from "../selectionControls/selection3D/functi
|
||||
import useAssetResponseHandler from "../../../collaboration/responseHandler/useAssetResponseHandler";
|
||||
|
||||
import createEventData from "../../../simulation/functions/createEventData";
|
||||
import generateUniqueAssetName from "../../../builder/asset/functions/generateUniqueAssetName";
|
||||
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
|
||||
const DuplicationControls3D = () => {
|
||||
@@ -22,21 +23,8 @@ const DuplicationControls3D = () => {
|
||||
const { assetStore, undoRedo3DStore, versionStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { projectId } = useParams();
|
||||
const {
|
||||
assets,
|
||||
addAsset,
|
||||
removeAsset,
|
||||
getAssetById,
|
||||
selectedAssets,
|
||||
setSelectedAssets,
|
||||
duplicatedObjects,
|
||||
setDuplicatedObjects,
|
||||
setPastedObjects,
|
||||
movedObjects,
|
||||
setMovedObjects,
|
||||
rotatedObjects,
|
||||
setRotatedObjects,
|
||||
} = assetStore();
|
||||
const { assets, addAsset, removeAsset, getAssetById, selectedAssets, duplicatedObjects, setDuplicatedObjects, setPastedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects } =
|
||||
assetStore();
|
||||
const { updateAssetInScene, removeAssetFromScene } = useAssetResponseHandler();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { userId, organization } = getUserData();
|
||||
@@ -237,10 +225,14 @@ const DuplicationControls3D = () => {
|
||||
const duplicateSelection = useCallback(() => {
|
||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||
const positions: Record<string, THREE.Vector3> = {};
|
||||
const usedNames = new Set<string>();
|
||||
|
||||
const newDuplicatedObjects = selectedAssets.map((obj: any) => {
|
||||
const clone = SkeletonUtils.clone(obj);
|
||||
clone.userData.modelUuid = THREE.MathUtils.generateUUID();
|
||||
const uniqueModelName = generateUniqueAssetName(obj.userData.modelName, assets, usedNames);
|
||||
clone.userData.modelName = uniqueModelName;
|
||||
usedNames.add(uniqueModelName);
|
||||
positions[clone.userData.modelUuid] = new THREE.Vector3().copy(obj.position);
|
||||
return clone;
|
||||
});
|
||||
@@ -275,7 +267,7 @@ const DuplicationControls3D = () => {
|
||||
addAsset(asset);
|
||||
});
|
||||
}
|
||||
}, [selectedAssets, duplicatedObjects]);
|
||||
}, [selectedAssets, duplicatedObjects.length, assets]);
|
||||
|
||||
const addDuplicatedAssets = () => {
|
||||
if (duplicatedObjects.length === 0) return;
|
||||
@@ -529,7 +521,6 @@ const DuplicationControls3D = () => {
|
||||
setPastedObjects([]);
|
||||
setDuplicatedObjects([]);
|
||||
setRotatedObjects([]);
|
||||
setSelectedAssets([]);
|
||||
setIsDuplicating(false);
|
||||
setDragOffset(null);
|
||||
setAxisConstraint(null);
|
||||
|
||||
@@ -32,7 +32,6 @@ function MoveControls3D() {
|
||||
updateAsset,
|
||||
getAssetById,
|
||||
selectedAssets,
|
||||
setSelectedAssets,
|
||||
movedObjects,
|
||||
setMovedObjects,
|
||||
pastedObjects,
|
||||
@@ -521,7 +520,6 @@ function MoveControls3D() {
|
||||
setDuplicatedObjects([]);
|
||||
setMovedObjects([]);
|
||||
setRotatedObjects([]);
|
||||
setSelectedAssets([]);
|
||||
setKeyEvent("");
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ function RotateControls3D() {
|
||||
rotatedObjects,
|
||||
setRotatedObjects,
|
||||
selectedAssets,
|
||||
setSelectedAssets,
|
||||
movedObjects,
|
||||
setMovedObjects,
|
||||
pastedObjects,
|
||||
@@ -472,7 +471,6 @@ function RotateControls3D() {
|
||||
setDuplicatedObjects([]);
|
||||
setMovedObjects([]);
|
||||
setRotatedObjects([]);
|
||||
setSelectedAssets([]);
|
||||
setIsIndividualRotating(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export function handleAssetRotationSnap({
|
||||
keyEvent,
|
||||
snapBaseRef,
|
||||
prevRotationRef,
|
||||
wasShiftHeldRef
|
||||
wasShiftHeldRef,
|
||||
}: {
|
||||
object: THREE.Object3D;
|
||||
pointerDeltaX: number;
|
||||
@@ -15,9 +15,9 @@ export function handleAssetRotationSnap({
|
||||
prevRotationRef: React.MutableRefObject<number | null>;
|
||||
wasShiftHeldRef: React.MutableRefObject<boolean>;
|
||||
}): number {
|
||||
const SHIFT_SPEED = 0.5; // Fine rotation speed
|
||||
const NORMAL_SPEED = 5; // Normal rotation speed
|
||||
const CTRL_SNAP_DEG = 15; // 15 degrees
|
||||
const SHIFT_SPEED = 0.5; // Fine rotation speed
|
||||
const NORMAL_SPEED = 5; // Normal rotation speed
|
||||
const CTRL_SNAP_DEG = 15; // 15 degrees
|
||||
const CTRL_SHIFT_SNAP_DEG = 5; // 5 degrees
|
||||
|
||||
const isShiftHeld = keyEvent.includes("Shift");
|
||||
@@ -38,7 +38,7 @@ export function handleAssetRotationSnap({
|
||||
const snapRad = THREE.MathUtils.degToRad(snapDeg);
|
||||
|
||||
// Get current Y rotation from object's quaternion
|
||||
const euler = new THREE.Euler().setFromQuaternion(object.quaternion, 'YXZ');
|
||||
const euler = new THREE.Euler().setFromQuaternion(object.quaternion, "YXZ");
|
||||
let currentAngle = euler.y;
|
||||
|
||||
// Initialize snap base on first frame
|
||||
@@ -71,4 +71,4 @@ export function handleAssetRotationSnap({
|
||||
prevRotationRef.current = null;
|
||||
return deltaAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user