added flip controls to asset and added in context menu too

This commit is contained in:
2025-09-24 13:55:26 +05:30
parent 187e067f04
commit 45fef163d3
4 changed files with 358 additions and 82 deletions

View File

@@ -5,7 +5,6 @@ import {
DeleteIcon,
DublicateIcon,
FlipXAxisIcon,
FlipZAxisIcon,
FocusIcon,
GroupIcon,
ModifiersIcon,
@@ -21,8 +20,8 @@ import {
type VisibilityKey =
| "rename"
| "focus"
| "flipX"
| "flipZ"
| "flip"
| "flipIndividual"
| "move"
| "rotate"
| "duplicate"
@@ -39,8 +38,8 @@ type ContextMenuProps = {
visibility: Record<Exclude<VisibilityKey, "transform" | "groupArray">, boolean>;
onRename: () => void;
onFocus: () => void;
onFlipX: () => void;
onFlipZ: () => void;
onFlip: () => void;
onFlipIndividual: () => void;
onMove: () => void;
onRotate: () => void;
onDuplicate: () => void;
@@ -61,7 +60,7 @@ type MenuItem = {
children?: MenuItem[];
};
const ContextMenu: React.FC<ContextMenuProps> = ({ visibility, onRename, onFocus, onFlipX, onFlipZ, onMove, onRotate, onDuplicate, onCopy, onPaste, onGroup, onArray, onDelete }) => {
const ContextMenu: React.FC<ContextMenuProps> = ({ visibility, onRename, onFocus, onFlip, onFlipIndividual, onMove, onRotate, onDuplicate, onCopy, onPaste, onGroup, onArray, onDelete }) => {
const items: MenuItem[] = [
{
key: "rename",
@@ -78,16 +77,16 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ visibility, onRename, onFocus
onClick: onFocus,
},
{
key: "flipX",
label: "Flip to X axis",
key: "flip",
label: "Flip",
icon: <FlipXAxisIcon />,
onClick: onFlipX,
onClick: onFlip,
},
{
key: "flipZ",
label: "Flip to Z axis",
icon: <FlipZAxisIcon />,
onClick: onFlipZ,
key: "flipIndividual",
label: "Flip Individual",
icon: <FlipXAxisIcon />,
onClick: onFlipIndividual,
},
{
key: "transform",

View File

@@ -1,16 +1,309 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import * as THREE from "three";
import { useContextActionStore } from "../../../../store/builder/store";
import { useSceneContext } from "../../sceneContext";
import { useSocketStore } from "../../../../store/socket/useSocketStore";
import { getUserData } from "../../../../functions/getUserData";
import useAssetResponseHandler from "../../../collaboration/responseHandler/useAssetResponseHandler";
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
function ScaleControls3D() {
const { assetStore, eventStore, productStore, versionStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
const { selectedAssets, getAssetById } = assetStore();
const { contextAction, setContextAction } = useContextActionStore();
const { updateAssetInScene } = useAssetResponseHandler();
const { projectId } = useParams();
const { builderSocket } = useSocketStore();
const { userId, organization } = getUserData();
const { selectedVersion } = versionStore();
useEffect(() => {
if (contextAction === "") {
if (contextAction === "flipAsset") {
flip();
setContextAction(null);
} else if (contextAction === "flipAssetIndividual") {
flipIndividual();
setContextAction(null);
}
}, [contextAction]);
return <></>;
const normalizeAngle = (angle: number) => {
const twoPI = Math.PI * 2;
return ((angle % twoPI) + twoPI) % twoPI;
};
const updateBackend = (productName: string, productUuid: string, projectId: string, eventData: any) => {
upsertProductOrEventApi({
productName: productName,
productUuid: productUuid,
projectId: projectId,
eventDatas: eventData,
versionId: selectedVersion?.versionId || "",
});
};
const handleAssetUpdate = async (modelUuid: string, newPosition: [number, number, number], newRotation: [number, number, number]) => {
const asset = getAssetById(modelUuid);
if (!asset) return;
const updatedAsset: Asset = {
modelUuid: asset.modelUuid,
modelName: asset.modelName,
assetId: asset.assetId,
position: newPosition,
rotation: newRotation,
scale: asset.scale,
isCollidable: true,
opacity: 1,
isLocked: false,
isVisible: true,
};
if (asset.eventData) {
const eventData = eventStore.getState().getEventByModelUuid(modelUuid);
const productData = productStore.getState().getEventByModelUuid(productStore.getState().selectedProduct.productUuid, modelUuid);
if (eventData) {
eventStore.getState().updateEvent(modelUuid, {
position: newPosition,
rotation: newRotation,
});
}
if (productData) {
const event = productStore.getState().updateEvent(productStore.getState().selectedProduct.productUuid, modelUuid, {
position: newPosition,
rotation: newRotation,
});
if (event) {
updateBackend(productStore.getState().selectedProduct.productName, productStore.getState().selectedProduct.productUuid, projectId || "", event);
}
updatedAsset.eventData = eventData;
}
}
if (!builderSocket?.connected) {
setAssetsApi({
modelUuid: updatedAsset.modelUuid,
modelName: updatedAsset.modelName,
assetId: updatedAsset.assetId,
position: updatedAsset.position,
rotation: updatedAsset.rotation,
scale: updatedAsset.scale,
isLocked: false,
isVisible: true,
versionId: selectedVersion?.versionId || "",
projectId: projectId || "",
})
.then((data) => {
if (!data.message || !data.data) {
echo.error(`Error flipping asset: ${updatedAsset.modelName}`);
return;
}
if (data.message === "Model updated successfully" && data.data) {
const model = {
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,
eventData: data.data.eventData,
};
updateAssetInScene(model, () => {
echo.info(`Flipped asset: ${model.modelName}`);
});
} else {
echo.error(`Error flipping asset: ${updatedAsset.modelName}`);
}
})
.catch(() => {
echo.error(`Error flipping asset: ${updatedAsset.modelName}`);
});
} else {
const data = {
organization,
modelUuid: updatedAsset.modelUuid,
modelName: updatedAsset.modelName,
assetId: updatedAsset.assetId,
position: updatedAsset.position,
rotation: updatedAsset.rotation,
scale: updatedAsset.scale,
isLocked: false,
isVisible: true,
socketId: builderSocket?.id,
versionId: selectedVersion?.versionId || "",
projectId,
userId,
};
builderSocket.emit("v1:model-asset:add", data);
}
};
const flip = () => {
if (!selectedAssets.length) return;
const undoActions: UndoRedo3DAction[] = [];
const assetUpdates: AssetData[] = [];
if (selectedAssets.length === 1) {
const obj = selectedAssets[0];
const { modelUuid, rotation, position } = obj.userData;
const originalPosition: [number, number, number] = position || [0, 0, 0];
const originalRotation: [number, number, number] = rotation || [0, 0, 0];
const newRotY = normalizeAngle((originalRotation?.[1] || 0) + Math.PI);
const newRotation: [number, number, number] = [0, newRotY, 0];
const asset = getAssetById(modelUuid);
if (asset) {
assetUpdates.push({
type: "Asset",
assetData: {
...asset,
position: originalPosition,
rotation: originalRotation,
},
newData: {
...asset,
position: originalPosition,
rotation: newRotation,
},
timeStap: new Date().toISOString(),
});
}
handleAssetUpdate(modelUuid, originalPosition, newRotation);
} else {
const bbox = new THREE.Box3();
selectedAssets.forEach((obj) => bbox.expandByObject(obj));
const pivot = new THREE.Vector3();
bbox.getCenter(pivot);
selectedAssets.forEach((obj) => {
const { modelUuid, position, rotation } = obj.userData;
const originalPosition: [number, number, number] = position || [0, 0, 0];
const originalRotation: [number, number, number] = rotation || [0, 0, 0];
const pos = new THREE.Vector3(...originalPosition);
pos.x = 2 * pivot.x - pos.x;
const newRotY = normalizeAngle(originalRotation[1] + Math.PI);
const newPosition: [number, number, number] = [pos.x, pos.y, pos.z];
const newRotation: [number, number, number] = [0, newRotY, 0];
const asset = getAssetById(modelUuid);
if (asset) {
assetUpdates.push({
type: "Asset",
assetData: {
...asset,
position: originalPosition,
rotation: originalRotation,
},
newData: {
...asset,
position: newPosition,
rotation: newRotation,
},
timeStap: new Date().toISOString(),
});
}
handleAssetUpdate(modelUuid, newPosition, newRotation);
});
}
if (assetUpdates.length > 0) {
if (assetUpdates.length === 1) {
undoActions.push({
module: "builder",
actionType: "Asset-Update",
asset: assetUpdates[0],
});
} else {
undoActions.push({
module: "builder",
actionType: "Assets-Update",
assets: assetUpdates,
});
}
push3D({
type: "Scene",
actions: undoActions,
});
}
};
const flipIndividual = () => {
if (!selectedAssets.length) return;
const undoActions: UndoRedo3DAction[] = [];
const assetUpdates: AssetData[] = [];
selectedAssets.forEach((obj) => {
const { modelUuid, position, rotation } = obj.userData;
const originalPosition: [number, number, number] = position || [0, 0, 0];
const originalRotation: [number, number, number] = rotation || [0, 0, 0];
const newRotY = normalizeAngle(originalRotation[1] + Math.PI);
const newRotation: [number, number, number] = [0, newRotY, 0];
const asset = getAssetById(modelUuid);
if (asset) {
assetUpdates.push({
type: "Asset",
assetData: {
...asset,
position: originalPosition,
rotation: originalRotation,
},
newData: {
...asset,
position: originalPosition,
rotation: newRotation,
},
timeStap: new Date().toISOString(),
});
}
handleAssetUpdate(modelUuid, position, newRotation);
});
if (assetUpdates.length > 0) {
if (assetUpdates.length === 1) {
undoActions.push({
module: "builder",
actionType: "Asset-Update",
asset: assetUpdates[0],
});
} else {
undoActions.push({
module: "builder",
actionType: "Assets-Update",
assets: assetUpdates,
});
}
push3D({
type: "Scene",
actions: undoActions,
});
}
};
return null;
}
export default ScaleControls3D;

View File

@@ -12,8 +12,8 @@ function ContextControls() {
const [visibility, setVisibility] = useState({
rename: true,
focus: true,
flipX: true,
flipZ: true,
flip: true,
flipIndividual: false,
move: true,
rotate: true,
duplicate: true,
@@ -25,6 +25,7 @@ function ContextControls() {
delete: true,
});
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { toolMode } = useToolMode();
@@ -40,8 +41,8 @@ function ContextControls() {
setVisibility({
rename: true,
focus: true,
flipX: true,
flipZ: true,
flip: true,
flipIndividual: false,
move: true,
rotate: true,
duplicate: true,
@@ -56,8 +57,8 @@ function ContextControls() {
setVisibility({
rename: false,
focus: true,
flipX: true,
flipZ: true,
flip: true,
flipIndividual: true,
move: true,
rotate: true,
duplicate: true,
@@ -72,8 +73,8 @@ function ContextControls() {
setVisibility({
rename: false,
focus: false,
flipX: false,
flipZ: false,
flip: false,
flipIndividual: false,
move: false,
rotate: false,
duplicate: false,
@@ -88,8 +89,8 @@ function ContextControls() {
setVisibility({
rename: false,
focus: false,
flipX: false,
flipZ: false,
flip: false,
flipIndividual: false,
move: false,
rotate: false,
duplicate: false,
@@ -130,23 +131,18 @@ function ContextControls() {
const handleContextClick = (event: MouseEvent) => {
event.preventDefault();
if (rightDrag.current) return;
if (selectedAssets.length > 0) {
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 });
setTimeout(() => {
setCanRender(true);
}, 0);
if (controls) {
(controls as CameraControls).enabled = false;
}
} else {
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 });
setTimeout(() => {
setCanRender(true);
}, 0);
if (controls) {
(controls as CameraControls).enabled = true;
}
setMenuPosition({
x: event.clientX - gl.domElement.width / 2,
y: event.clientY - gl.domElement.height / 2,
});
setTimeout(() => {
setCanRender(true);
}, 0);
if (controls) {
(controls as CameraControls).enabled = selectedAssets.length === 0;
}
};
@@ -181,59 +177,53 @@ function ContextControls() {
const handleAssetRename = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("renameAsset");
setIsRenameMode(true);
};
const handleAssetFocus = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("focusAsset");
};
const handleAssetFlip = () => {
setCanRender(false);
if (controls) (controls as CameraControls).enabled = true;
setContextAction("flipAsset");
};
const handleAssetFlipIndividual = () => {
setCanRender(false);
if (controls) (controls as CameraControls).enabled = true;
setContextAction("flipAssetIndividual");
};
const handleAssetMove = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("moveAsset");
};
const handleAssetRotate = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("rotateAsset");
};
const handleAssetCopy = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("copyAsset");
};
const handleAssetPaste = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("pasteAsset");
};
const handleAssetDelete = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("deleteAsset");
};
const handleAssetDuplicate = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
if (controls) (controls as CameraControls).enabled = true;
setContextAction("duplicateAsset");
};
@@ -251,18 +241,18 @@ function ContextControls() {
>
<ContextMenu
visibility={visibility}
onRename={() => handleAssetRename()}
onFocus={() => handleAssetFocus()}
onFlipX={() => console.log("Flip to X")}
onFlipZ={() => console.log("Flip to Z")}
onMove={() => handleAssetMove()}
onRotate={() => handleAssetRotate()}
onDuplicate={() => handleAssetDuplicate()}
onCopy={() => handleAssetCopy()}
onPaste={() => handleAssetPaste()}
onRename={handleAssetRename}
onFocus={handleAssetFocus}
onFlip={handleAssetFlip}
onFlipIndividual={handleAssetFlipIndividual}
onMove={handleAssetMove}
onRotate={handleAssetRotate}
onDuplicate={handleAssetDuplicate}
onCopy={handleAssetCopy}
onPaste={handleAssetPaste}
onGroup={() => console.log("Group")}
onArray={() => console.log("Array")}
onDelete={() => handleAssetDelete()}
onDelete={handleAssetDelete}
/>
</Html>
</ScreenSpace>

View File

@@ -10,12 +10,6 @@ import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useContextActionStore, useToggleView, useToolMode } from "../../../../../store/builder/store";
import { useSocketStore } from "../../../../../store/socket/useSocketStore";
import DuplicationControls3D from "../../assetControls/duplicationControls3D";
import CutCopyPasteControls3D from "../../assetControls/cutCopyPasteControls3D";
import MoveControls3D from "../../assetControls/moveControls3D";
import RotateControls3D from "../../assetControls/rotateControls3D";
import TransformControls3D from "../../assetControls/transformControls3D";
import ScaleControls3D from "../../assetControls/scaleControls3D";
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
import useAssetResponseHandler from "../../../../collaboration/responseHandler/useAssetResponseHandler";