diff --git a/app/src/components/ui/menu/contextMenu.tsx b/app/src/components/ui/menu/contextMenu.tsx index a020621..8377181 100644 --- a/app/src/components/ui/menu/contextMenu.tsx +++ b/app/src/components/ui/menu/contextMenu.tsx @@ -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, 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 = ({ visibility, onRename, onFocus, onFlipX, onFlipZ, onMove, onRotate, onDuplicate, onCopy, onPaste, onGroup, onArray, onDelete }) => { +const ContextMenu: React.FC = ({ 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 = ({ visibility, onRename, onFocus onClick: onFocus, }, { - key: "flipX", - label: "Flip to X axis", + key: "flip", + label: "Flip", icon: , - onClick: onFlipX, + onClick: onFlip, }, { - key: "flipZ", - label: "Flip to Z axis", - icon: , - onClick: onFlipZ, + key: "flipIndividual", + label: "Flip Individual", + icon: , + onClick: onFlipIndividual, }, { key: "transform", diff --git a/app/src/modules/scene/controls/assetControls/scaleControls3D.tsx b/app/src/modules/scene/controls/assetControls/scaleControls3D.tsx index 31e8317..1c92e5d 100644 --- a/app/src/modules/scene/controls/assetControls/scaleControls3D.tsx +++ b/app/src/modules/scene/controls/assetControls/scaleControls3D.tsx @@ -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; diff --git a/app/src/modules/scene/controls/contextControls/contextControls.tsx b/app/src/modules/scene/controls/contextControls/contextControls.tsx index 1af2627..3b05ab9 100644 --- a/app/src/modules/scene/controls/contextControls/contextControls.tsx +++ b/app/src/modules/scene/controls/contextControls/contextControls.tsx @@ -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() { > 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} /> diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index 4b948a1..c5dc0e9 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -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";