added asset unique name to added , duplicated and pasted asset

This commit is contained in:
2025-09-25 12:50:44 +05:30
parent cf21d3face
commit e62389cbb1
9 changed files with 371 additions and 309 deletions

View File

@@ -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} />;
}

View File

@@ -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;

View File

@@ -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;

View 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;

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -32,7 +32,6 @@ function MoveControls3D() {
updateAsset,
getAssetById,
selectedAssets,
setSelectedAssets,
movedObjects,
setMovedObjects,
pastedObjects,
@@ -521,7 +520,6 @@ function MoveControls3D() {
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
setKeyEvent("");
};

View File

@@ -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);
};

View File

@@ -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;
}
}
}