diff --git a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx
index c075f52..15d32ce 100644
--- a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx
+++ b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx
@@ -216,6 +216,7 @@ const GlobalProperties: React.FC = () => {
// setRenderDistance(parseInt(e.target.value));
// }
// }
+
return (
@@ -239,12 +240,12 @@ const GlobalProperties: React.FC = () => {
label="Wall Visibility"
onClick={changeWallVisibility}
/>
- {/* */}
+ />
void;
};
+ assemblyCount: {
+ value: number;
+ min: number;
+ max: number;
+ step: number;
+ defaultValue: string,
+ disabled: false,
+ onChange: (value: number) => void;
+ }
swapOptions: string[];
swapDefaultOption: string;
onSwapSelect: (value: string) => void;
@@ -18,6 +28,7 @@ interface AssemblyActionProps {
const AssemblyAction: React.FC = ({
processTime,
+ assemblyCount,
swapOptions,
swapDefaultOption,
onSwapSelect,
@@ -34,6 +45,21 @@ const AssemblyAction: React.FC = ({
onClick={() => { }}
onChange={processTime.onChange}
/>
+
+ {assemblyCount && (
+ { }}
+ onChange={(value) => assemblyCount.onChange(parseInt(value))}
+ />
+ )}
void;
};
- loadCount?: {
+ loadCount: {
value: number;
min: number;
max: number;
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx
index 6d91d1c..fc73e58 100644
--- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx
+++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx
@@ -20,6 +20,7 @@ function HumanMechanics() {
const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker");
const [speed, setSpeed] = useState("0.5");
const [loadCount, setLoadCount] = useState(0);
+ const [assemblyCount, setAssemblyCount] = useState(0);
const [loadCapacity, setLoadCapacity] = useState("1");
const [processTime, setProcessTime] = useState(10);
const [swappedMaterial, setSwappedMaterial] = useState("Default material");
@@ -56,6 +57,7 @@ function HumanMechanics() {
setLoadCapacity(firstAction.loadCapacity.toString());
setActiveOption(firstAction.actionType);
setLoadCount(firstAction.loadCount || 0);
+ setAssemblyCount(firstAction.assemblyCount || 0);
setProcessTime(firstAction.processTime || 10);
setSwappedMaterial(firstAction.swapMaterial || "Default material");
}
@@ -84,6 +86,7 @@ function HumanMechanics() {
setActiveOption(newCurrentAction.actionType);
setLoadCapacity(newCurrentAction.loadCapacity.toString());
setLoadCount(newCurrentAction.loadCount || 0);
+ setAssemblyCount(newCurrentAction.assemblyCount || 0);
if (newCurrentAction.actionType === 'assembly') {
setProcessTime(newCurrentAction.processTime || 10);
@@ -200,6 +203,28 @@ function HumanMechanics() {
setLoadCount(value);
};
+ const handleAssemblyCountChange = (value: number) => {
+ if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
+
+ const updatedAction = { ...currentAction, assemblyCount: value };
+ const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action);
+ const updatedPoint = { ...selectedPointData, actions: updatedActions };
+
+ const event = updateAction(
+ selectedProduct.productUuid,
+ selectedAction.actionId,
+ updatedAction
+ );
+
+ if (event) {
+ updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event);
+ }
+
+ setCurrentAction(updatedAction);
+ setSelectedPointData(updatedPoint);
+ setAssemblyCount(value);
+ };
+
const handleProcessTimeChange = (value: number) => {
if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
@@ -281,6 +306,7 @@ function HumanMechanics() {
actionName: `Action ${selectedPointData.actions.length + 1}`,
actionType: "worker",
loadCount: 1,
+ assemblyCount: 1,
loadCapacity: 1,
processTime: 10,
triggers: [],
@@ -304,15 +330,15 @@ function HumanMechanics() {
setSelectedAction(newAction.actionUuid, newAction.actionName);
};
- const handleDeleteAction = () => {
- if (!selectedPointData || !selectedAction.actionId) return;
+ const handleDeleteAction = (actionUuid: string) => {
+ if (!selectedPointData || !actionUuid) return;
- const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== selectedAction.actionId);
+ const updatedActions = selectedPointData.actions.filter(action => action.actionUuid !== actionUuid);
const updatedPoint = { ...selectedPointData, actions: updatedActions };
const event = removeAction(
selectedProduct.productUuid,
- selectedAction.actionId
+ actionUuid
);
if (event) {
@@ -389,7 +415,7 @@ function HumanMechanics() {
}}
loadCount={{
value: loadCount,
- min: 0,
+ min: 1,
max: 20,
step: 1,
defaultValue: "1",
@@ -407,6 +433,15 @@ function HumanMechanics() {
max: 60,
onChange: handleProcessTimeChange,
}}
+ assemblyCount={{
+ value: assemblyCount,
+ min: 1,
+ max: 20,
+ step: 1,
+ defaultValue: "1",
+ disabled: false,
+ onChange: handleAssemblyCountChange,
+ }}
swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]}
swapDefaultOption={swappedMaterial}
onSwapSelect={handleMaterialChange}
diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx
index a05e423..89f7471 100644
--- a/app/src/modules/builder/asset/assetsGroup.tsx
+++ b/app/src/modules/builder/asset/assetsGroup.tsx
@@ -16,12 +16,7 @@ import { getUserData } from "../../../functions/getUserData";
import { useSceneContext } from "../../scene/sceneContext";
import { useVersionContext } from "../version/versionContext";
-const gltfLoaderWorker = new Worker(
- new URL(
- "../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
- import.meta.url
- )
-);
+const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
const { activeModule } = useModuleStore();
@@ -44,9 +39,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
- dracoLoader.setDecoderPath(
- "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
- );
+ dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
useEffect(() => {
@@ -267,6 +260,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
actionName: "Action 1",
actionType: "worker",
loadCount: 1,
+ assemblyCount: 1,
loadCapacity: 1,
processTime: 10,
triggers: []
diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts
index 4b72cd3..80d8b0a 100644
--- a/app/src/modules/builder/asset/functions/addAssetModel.ts
+++ b/app/src/modules/builder/asset/functions/addAssetModel.ts
@@ -384,6 +384,7 @@ async function handleModelLoad(
actionName: "Action 1",
actionType: "worker",
loadCount: 1,
+ assemblyCount: 1,
loadCapacity: 1,
processTime: 10,
triggers: []
diff --git a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx
index 9dea36a..da08edf 100644
--- a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx
+++ b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx
@@ -1,26 +1,71 @@
-import { Box3, BoxGeometry, EdgesGeometry, Vector3 } from "three";
-import { RigidBody } from "@react-three/rapier";
+import { Box3, Vector3, Quaternion } from "three";
import { useMemo } from "react";
+import { Cylinder } from "@react-three/drei";
-export const AssetBoundingBox = ({ boundingBox }: { boundingBox: Box3 }) => {
+export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => {
+ const { edgeCylinders, center, size } = useMemo(() => {
+ if (!boundingBox) return { edgeCylinders: [], center: new Vector3(), size: new Vector3() };
- const size = boundingBox.getSize(new Vector3());
- const center = boundingBox.getCenter(new Vector3());
+ const min = boundingBox.min;
+ const max = boundingBox.max;
+ const center = boundingBox.getCenter(new Vector3());
+ const size = boundingBox.getSize(new Vector3());
- const boxGeometry = useMemo(() => new BoxGeometry(size.x, size.y, size.z), [size]);
- const edges = useMemo(() => new EdgesGeometry(boxGeometry), [boxGeometry]);
+ const corners = [
+ new Vector3(min.x, min.y, min.z),
+ new Vector3(max.x, min.y, min.z),
+ new Vector3(max.x, max.y, min.z),
+ new Vector3(min.x, max.y, min.z),
+ new Vector3(min.x, min.y, max.z),
+ new Vector3(max.x, min.y, max.z),
+ new Vector3(max.x, max.y, max.z),
+ new Vector3(min.x, max.y, max.z),
+ ];
+
+ const edgeIndices: [number, number][] = [
+ [0, 1], [1, 2], [2, 3], [3, 0],
+ [4, 5], [5, 6], [6, 7], [7, 4],
+ [0, 4], [1, 5], [2, 6], [3, 7],
+ ];
+
+ const radius = 0.005 * lineWidth;
+
+ const edgeCylinders = edgeIndices.map(([startIdx, endIdx], i) => {
+ const start = corners[startIdx];
+ const end = corners[endIdx];
+ const direction = new Vector3().subVectors(end, start);
+ const length = direction.length();
+ const midPoint = new Vector3().addVectors(start, end).multiplyScalar(0.5);
+ const quaternion = new Quaternion().setFromUnitVectors(
+ new Vector3(0, 1, 0),
+ direction.clone().normalize()
+ );
+
+ return {
+ key: `edge-cylinder-${i}`,
+ position: midPoint,
+ rotation: quaternion,
+ length,
+ radius,
+ };
+ });
+
+ return { edgeCylinders, center, size };
+ }, [boundingBox, lineWidth]);
+
+ if (!boundingBox) return null;
return (
-
-
-
-
-
+
+ {edgeCylinders.map(({ key, position, rotation, length, radius }) => (
+
+
+
+ ))}
-
+
-
-
+
);
};
diff --git a/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx
new file mode 100644
index 0000000..d2c76eb
--- /dev/null
+++ b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx
@@ -0,0 +1,94 @@
+import { useEffect, useRef, useCallback, useState } from 'react';
+import * as THREE from 'three';
+import { useFrame } from '@react-three/fiber';
+import { useSceneContext } from '../../../../../scene/sceneContext';
+import useModuleStore from '../../../../../../store/useModuleStore';
+import { usePauseButtonStore, useAnimationPlaySpeed } from '../../../../../../store/usePlayButtonStore';
+
+interface ModelAnimatorProps {
+ asset: Asset;
+ gltfScene: THREE.Object3D;
+}
+
+export function ModelAnimator({
+ asset,
+ gltfScene,
+}: ModelAnimatorProps) {
+ const mixerRef = useRef();
+ const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
+ const [previousAnimation, setPreviousAnimation] = useState(null);
+
+ const blendFactor = useRef(0);
+ const blendDuration = 0.5;
+
+ const { speed } = useAnimationPlaySpeed();
+ const { isPaused } = usePauseButtonStore();
+ const { assetStore } = useSceneContext();
+ const { activeModule } = useModuleStore();
+ const { setAnimations, setAnimationComplete } = assetStore();
+
+ const handleAnimationComplete = useCallback(() => {
+ if (asset.animationState) {
+ setAnimationComplete(asset.modelUuid, true);
+ }
+ }, [asset.animationState]);
+
+ useEffect(() => {
+ if (!gltfScene || !gltfScene.animations || gltfScene.animations.length === 0) return;
+
+ mixerRef.current = new THREE.AnimationMixer(gltfScene);
+
+ gltfScene.animations.forEach((clip: THREE.AnimationClip) => {
+ const action = mixerRef.current!.clipAction(clip);
+ actions.current[clip.name] = action;
+ });
+
+ const animationNames = gltfScene.animations.map(clip => clip.name);
+ setAnimations(asset.modelUuid, animationNames);
+
+ return () => {
+ mixerRef.current?.stopAllAction();
+ mixerRef.current = undefined;
+ };
+ }, [gltfScene]);
+
+ useEffect(() => {
+ if (!asset.animationState || !mixerRef.current) return;
+
+ const { current, loopAnimation, isPlaying } = asset.animationState;
+ const currentAction = actions.current[current];
+ const previousAction = previousAnimation ? actions.current[previousAnimation] : null;
+
+ if (isPlaying && currentAction && !isPaused) {
+ blendFactor.current = 0;
+
+ currentAction.reset();
+ currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
+ currentAction.clampWhenFinished = true;
+
+ if (previousAction && previousAction !== currentAction) {
+ previousAction.crossFadeTo(currentAction, blendDuration, false);
+ }
+
+ currentAction.play();
+ mixerRef.current.addEventListener('finished', handleAnimationComplete);
+ setPreviousAnimation(current);
+ } else {
+ Object.values(actions.current).forEach((action) => action.stop());
+ }
+
+ return () => {
+ if (mixerRef.current) {
+ mixerRef.current.removeEventListener('finished', handleAnimationComplete);
+ }
+ };
+ }, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]);
+
+ useFrame((_, delta) => {
+ if (mixerRef.current) {
+ mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1));
+ }
+ });
+
+ return null;
+}
diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts
new file mode 100644
index 0000000..d678382
--- /dev/null
+++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts
@@ -0,0 +1,238 @@
+import * as THREE from 'three';
+import { CameraControls } from '@react-three/drei';
+import { ThreeEvent } from '@react-three/fiber';
+import { useCallback } from 'react';
+import { ProductStoreType } from '../../../../../../store/simulation/useProductStore';
+import { EventStoreType } from '../../../../../../store/simulation/useEventsStore';
+import { Socket } from 'socket.io-client';
+
+import { useActiveTool, useToolMode } from '../../../../../../store/builder/store';
+import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore';
+import { useSocketStore } from '../../../../../../store/builder/store';
+import { useSceneContext } from '../../../../../scene/sceneContext';
+import { useProductContext } from '../../../../../simulation/products/productContext';
+import { useVersionContext } from '../../../../version/versionContext';
+import { useParams } from 'react-router-dom';
+import { getUserData } from '../../../../../../functions/getUserData';
+
+// import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi';
+
+export function useModelEventHandlers({
+ controls,
+ boundingBox,
+ groupRef,
+ toggleView,
+ deletableFloorItem,
+ setDeletableFloorItem,
+ setSelectedFloorItem,
+ gl,
+ setTop,
+ setLeft,
+ getIsEventInProduct,
+ getEventByModelUuid,
+ setSelectedAsset,
+ clearSelectedAsset,
+ removeAsset,
+ updateBackend,
+ leftDrag,
+ rightDrag
+}: {
+ controls: CameraControls | any,
+ boundingBox: THREE.Box3 | null,
+ groupRef: React.RefObject,
+ toggleView: boolean,
+ deletableFloorItem: THREE.Object3D | null,
+ setDeletableFloorItem: (item: THREE.Object3D | null) => void,
+ setSelectedFloorItem: (item: THREE.Object3D | null) => void,
+ gl: THREE.WebGLRenderer,
+ setTop: (top: number) => void,
+ setLeft: (left: number) => void,
+ getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean,
+ getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined,
+ setSelectedAsset: (EventData: EventsSchema) => void,
+ clearSelectedAsset: () => void,
+ removeAsset: (modelUuid: string) => void,
+ updateBackend: (productName: string, productUuid: string, projectId: string, event: EventsSchema) => void,
+ leftDrag: React.MutableRefObject,
+ rightDrag: React.MutableRefObject
+}) {
+
+ const { activeTool } = useActiveTool();
+ const { activeModule } = useModuleStore();
+ const { subModule } = useSubModuleStore();
+ const { socket } = useSocketStore();
+ const { eventStore, productStore } = useSceneContext();
+ const { selectedProductStore } = useProductContext();
+ const { selectedProduct } = selectedProductStore();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { projectId } = useParams();
+ const { userId, organization } = getUserData();
+
+ const handleDblClick = (asset: Asset) => {
+ if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
+ const size = boundingBox.getSize(new THREE.Vector3());
+ const center = boundingBox.getCenter(new THREE.Vector3());
+
+ const front = new THREE.Vector3(0, 0, 1);
+ groupRef.current.localToWorld(front);
+ front.sub(groupRef.current.position).normalize();
+
+ const distance = Math.max(size.x, size.y, size.z) * 2;
+ const newPosition = center.clone().addScaledVector(front, distance);
+
+ (controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true);
+ (controls as CameraControls).setTarget(center.x, center.y, center.z, true);
+ (controls as CameraControls).fitToBox(groupRef.current, true, {
+ cover: true,
+ paddingTop: 5,
+ paddingLeft: 5,
+ paddingBottom: 5,
+ paddingRight: 5,
+ });
+
+ setSelectedFloorItem(groupRef.current);
+ }
+ };
+
+ const handleClick = async (evt: ThreeEvent, asset: Asset) => {
+ if (leftDrag.current || toggleView) return;
+ if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
+
+ //REST
+
+ // const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName);
+
+ //SOCKET
+
+ const data = {
+ organization,
+ modelUuid: asset.modelUuid,
+ modelName: asset.modelName,
+ socketId: socket.id,
+ userId,
+ versionId: selectedVersion?.versionId || '',
+ projectId
+ }
+
+ const response = socket.emit('v1:model-asset:delete', data)
+
+ eventStore.getState().removeEvent(asset.modelUuid);
+ const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid);
+
+ updatedEvents.forEach((event) => {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productUuid,
+ projectId || '',
+ event
+ );
+ })
+
+ if (response) {
+
+ removeAsset(asset.modelUuid);
+
+ echo.success("Model Removed!");
+ }
+
+ } else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') {
+ if (asset.eventData && asset.eventData.type === 'Conveyor') {
+ const intersectedPoint = evt.point;
+ const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone());
+ if (localPosition) {
+ const conveyorPoint: ConveyorPointSchema = {
+ uuid: THREE.MathUtils.generateUUID(),
+ position: [localPosition?.x, localPosition?.y, localPosition?.z],
+ rotation: [0, 0, 0],
+ action: {
+ actionUuid: THREE.MathUtils.generateUUID(),
+ actionName: `Action 1`,
+ actionType: 'default',
+ material: 'Default Material',
+ delay: 0,
+ spawnInterval: 5,
+ spawnCount: 1,
+ triggers: []
+ }
+ }
+
+ const event = productStore.getState().addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint);
+
+ if (event) {
+ updateBackend(
+ selectedProduct.productName,
+ selectedProduct.productUuid,
+ projectId || '',
+ event
+ );
+ }
+ }
+ }
+
+ }
+ };
+
+ const handlePointerOver = useCallback((asset: Asset) => {
+ if (activeTool === "delete" && activeModule === 'builder') {
+ if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
+ return;
+ } else {
+ setDeletableFloorItem(groupRef.current);
+ }
+ }
+ }, [activeTool, activeModule, deletableFloorItem]);
+
+ const handlePointerOut = useCallback((evt: ThreeEvent, asset: Asset) => {
+ if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
+ setDeletableFloorItem(null);
+ }
+ }, [activeTool, deletableFloorItem]);
+
+ const handleContextMenu = (asset: Asset, evt: ThreeEvent) => {
+ if (rightDrag.current || toggleView) return;
+ if (activeTool === "cursor" && subModule === 'simulations') {
+ if (asset.modelUuid) {
+ const canvasElement = gl.domElement;
+ const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid);
+ if (isInProduct) {
+ const event = getEventByModelUuid(asset.modelUuid);
+ if (event) {
+ setSelectedAsset(event);
+ const canvasRect = canvasElement.getBoundingClientRect();
+ const relativeX = evt.clientX - canvasRect.left;
+ const relativeY = evt.clientY - canvasRect.top;
+ setTop(relativeY);
+ setLeft(relativeX);
+ } else {
+ clearSelectedAsset();
+ }
+ } else {
+ const event = getEventByModelUuid(asset.modelUuid);
+ if (event) {
+ setSelectedAsset(event)
+ const canvasRect = canvasElement.getBoundingClientRect();
+ const relativeX = evt.clientX - canvasRect.left;
+ const relativeY = evt.clientY - canvasRect.top;
+ setTop(relativeY);
+ setLeft(relativeX);
+ } else {
+ clearSelectedAsset()
+ }
+ }
+ } else {
+ clearSelectedAsset()
+ }
+ } else {
+ clearSelectedAsset()
+ }
+ }
+
+ return {
+ handleDblClick,
+ handleClick,
+ handlePointerOver,
+ handlePointerOut,
+ handleContextMenu
+ };
+}
diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx
index ffd9c6e..01b47fd 100644
--- a/app/src/modules/builder/asset/models/model/model.tsx
+++ b/app/src/modules/builder/asset/models/model/model.tsx
@@ -5,8 +5,8 @@ import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
import { RapierRigidBody, RigidBody } from '@react-three/rapier';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
-import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
-import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
+import { ThreeEvent, useThree } from '@react-three/fiber';
+import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
import { CameraControls } from '@react-three/drei';
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
@@ -17,23 +17,23 @@ import { useParams } from 'react-router-dom';
import { getUserData } from '../../../../../functions/getUserData';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useVersionContext } from '../../../version/versionContext';
-import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore';
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
import ConveyorCollider from './conveyorCollider';
-function Model({ asset }: { readonly asset: Asset }) {
+import { ModelAnimator } from './animator/modelAnimator';
+
+function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
- const { camera, controls, gl, scene } = useThree();
+ const { controls, gl, scene } = useThree();
+ const savedTheme: string = localStorage.getItem("theme") || "light";
const { activeTool } = useActiveTool();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { subModule } = useSubModuleStore();
const { activeModule } = useModuleStore();
- const { speed } = useAnimationPlaySpeed();
- const { isPaused } = usePauseButtonStore();
const { assetStore, eventStore, productStore } = useSceneContext();
- const { removeAsset, setAnimations, resetAnimation, setAnimationComplete } = assetStore();
+ const { removeAsset, resetAnimation } = assetStore();
const { setTop } = useTopData();
const { setLeft } = useLeftData();
const { getIsEventInProduct, addPoint } = productStore();
@@ -44,28 +44,22 @@ function Model({ asset }: { readonly asset: Asset }) {
const { socket } = useSocketStore();
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
- const { limitDistance } = useLimitDistance();
- const { renderDistance } = useRenderDistance();
const leftDrag = useRef(false);
const isLeftMouseDown = useRef(false);
const rightDrag = useRef(false);
const isRightMouseDown = useRef(false);
- const [isRendered, setIsRendered] = useState(false);
const [gltfScene, setGltfScene] = useState(null);
const [boundingBox, setBoundingBox] = useState(null);
const [conveyorPlaneSize, setConveyorPlaneSize] = useState<[number, number] | null>(null);
const groupRef = useRef(null);
const rigidBodyRef = useRef(null);
- const mixerRef = useRef();
- const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
- const [previousAnimation, setPreviousAnimation] = useState(null);
+ const [isSelected, setIsSelected] = useState(false);
const [ikData, setIkData] = useState();
- const blendFactor = useRef(0);
- const blendDuration = 0.5;
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
+ const { selectedAssets } = useSelectedAssets();
const updateBackend = (
productName: string,
@@ -93,6 +87,17 @@ function Model({ asset }: { readonly asset: Asset }) {
}
}, [asset.modelUuid, ikData])
+ useEffect(() => {
+ if (gltfScene) {
+ gltfScene.traverse((child: any) => {
+ if (child.isMesh) {
+ child.castShadow = true;
+ child.receiveShadow = true;
+ }
+ })
+ }
+ }, [gltfScene]);
+
useEffect(() => {
setDeletableFloorItem(null);
if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) {
@@ -123,16 +128,6 @@ function Model({ asset }: { readonly asset: Asset }) {
clone.animations = cachedModel.animations || [];
setGltfScene(clone);
calculateBoundingBox(clone);
- if (cachedModel.animations && clone.animations.length > 0) {
- const animationName = clone.animations.map((clip: any) => clip.name);
- setAnimations(asset.modelUuid, animationName)
- mixerRef.current = new THREE.AnimationMixer(clone);
-
- clone.animations.forEach((animation: any) => {
- const action = mixerRef.current!.clipAction(animation);
- actions.current[animation.name] = action;
- });
- }
return;
}
@@ -197,21 +192,6 @@ function Model({ asset }: { readonly asset: Asset }) {
}, []);
- useFrame(() => {
- const assetPosition = new THREE.Vector3(...asset.position);
- if (limitDistance) {
- if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) {
- setIsRendered(true);
- } else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) {
- setIsRendered(false);
- }
- } else {
- if (!isRendered) {
- setIsRendered(true);
- }
- }
- })
-
const handleDblClick = (asset: Asset) => {
if (asset) {
if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
@@ -377,50 +357,6 @@ function Model({ asset }: { readonly asset: Asset }) {
}
}
- const handleAnimationComplete = useCallback(() => {
- if (asset.animationState) {
- setAnimationComplete(asset.modelUuid, true);
- }
- }, [asset.animationState]);
-
- useFrame((_, delta) => {
- if (mixerRef.current) {
- mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1));
- }
- });
-
- useEffect(() => {
- if (!asset.animationState || !mixerRef.current) return;
-
- const { current, loopAnimation, isPlaying } = asset.animationState;
- const currentAction = actions.current[current];
- const previousAction = previousAnimation ? actions.current[previousAnimation] : null;
-
- if (isPlaying && currentAction && activeModule === 'simulation' && !isPaused) {
- blendFactor.current = 0;
-
- currentAction.reset();
- currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
- currentAction.clampWhenFinished = true;
-
- if (previousAction && previousAction !== currentAction) {
- previousAction.crossFadeTo(currentAction, blendDuration, false);
- }
-
- currentAction.play();
- mixerRef.current.addEventListener('finished', handleAnimationComplete);
- setPreviousAnimation(current);
- } else {
- Object.values(actions.current).forEach((action) => action.stop());
- }
-
- return () => {
- if (mixerRef.current) {
- mixerRef.current.removeEventListener('finished', handleAnimationComplete);
- }
- };
- }, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]);
-
useEffect(() => {
const canvasElement = gl.domElement;
@@ -465,6 +401,18 @@ function Model({ asset }: { readonly asset: Asset }) {
}, [gl])
+ useEffect(() => {
+ if (selectedAssets.length > 0) {
+ if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) {
+ setIsSelected(true);
+ } else {
+ setIsSelected(false);
+ }
+ } else {
+ setIsSelected(false);
+ }
+ }, [selectedAssets])
+
return (
{
e.stopPropagation();
if (!toggleView) {
@@ -508,6 +458,7 @@ function Model({ asset }: { readonly asset: Asset }) {
<>
{isRendered ? (
<>
+
RigidBody>
+
+
>
) : (
- <>
- {boundingBox &&
-
- }
- >
+
)}
+
+
+ {isSelected &&
+
+ }
>
)}
diff --git a/app/src/modules/builder/asset/models/models.tsx b/app/src/modules/builder/asset/models/models.tsx
index a7bd752..cdfa21b 100644
--- a/app/src/modules/builder/asset/models/models.tsx
+++ b/app/src/modules/builder/asset/models/models.tsx
@@ -1,17 +1,45 @@
-import Model from './model/model';
-import { useThree } from '@react-three/fiber';
+import { useEffect, useRef, useState } from "react";
+import { useThree, useFrame } from "@react-three/fiber";
+import { Vector3 } from "three";
import { CameraControls } from '@react-three/drei';
-import { Vector3 } from 'three';
-import { useSelectedFloorItem } from '../../../../store/builder/store';
+import { useLimitDistance, useRenderDistance, useSelectedFloorItem } from '../../../../store/builder/store';
import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore';
import { useSceneContext } from '../../../scene/sceneContext';
+import Model from './model/model';
+
+const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url));
+
function Models() {
- const { controls } = useThree();
+ const { controls, camera } = useThree();
const { assetStore } = useSceneContext();
const { assets } = assetStore();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
+ const { limitDistance } = useLimitDistance();
+ const { renderDistance } = useRenderDistance();
+
+ const [renderMap, setRenderMap] = useState>({});
+
+ const cameraPos = useRef(new Vector3());
+
+ useEffect(() => {
+ distanceWorker.onmessage = (e) => {
+ const { shouldRender, modelUuid } = e.data;
+ setRenderMap((prev) => {
+ if (prev[modelUuid] === shouldRender) return prev;
+ return { ...prev, [modelUuid]: shouldRender };
+ });
+ };
+ }, []);
+
+ useFrame(() => {
+ camera.getWorldPosition(cameraPos.current);
+ for (const asset of assets) {
+ const isRendered = renderMap[asset.modelUuid] ?? false;
+ distanceWorker.postMessage({ modelUuid: asset.modelUuid, assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2], }, cameraPosition: cameraPos.current, limitDistance, renderDistance, isRendered, });
+ }
+ });
return (
- {assets.map((asset) =>
-
- )}
+ {assets.map((asset) => (
+
+ ))}
- )
+ );
}
-export default Models
\ No newline at end of file
+export default Models;
\ No newline at end of file
diff --git a/app/src/modules/builder/floor/floorCreator/floorCreator.tsx b/app/src/modules/builder/floor/floorCreator/floorCreator.tsx
index acab425..28450aa 100644
--- a/app/src/modules/builder/floor/floorCreator/floorCreator.tsx
+++ b/app/src/modules/builder/floor/floorCreator/floorCreator.tsx
@@ -19,8 +19,9 @@ function FloorCreator() {
const { toolMode } = useToolMode();
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
- const { floorStore } = useSceneContext();
- const { addFloor, getFloorPointById, getFloorByPoints } = floorStore();
+ const { floorStore, undoRedo2DStore } = useSceneContext();
+ const { addFloor, getFloorPointById } = floorStore();
+ const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
@@ -103,6 +104,21 @@ function FloorCreator() {
};
addFloor(floor);
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Line-Create',
+ point: {
+ type: 'Floor',
+ lineData: floor,
+ timeStamp: new Date().toISOString(),
+ }
+ }
+ ],
+ })
+
if (projectId) {
// API
@@ -142,6 +158,21 @@ function FloorCreator() {
};
addFloor(floor);
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Line-Create',
+ point: {
+ type: 'Floor',
+ lineData: floor,
+ timeStamp: new Date().toISOString(),
+ }
+ }
+ ],
+ })
+
if (projectId) {
// API
@@ -191,6 +222,21 @@ function FloorCreator() {
};
addFloor(floor);
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Line-Create',
+ point: {
+ type: 'Floor',
+ lineData: floor,
+ timeStamp: new Date().toISOString(),
+ }
+ }
+ ],
+ })
+
if (projectId) {
// API
@@ -243,7 +289,7 @@ function FloorCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
- }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, getFloorByPoints, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
+ }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
return (
<>
diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx
index a0470aa..c9f5e9b 100644
--- a/app/src/modules/builder/line/line.tsx
+++ b/app/src/modules/builder/line/line.tsx
@@ -30,10 +30,11 @@ function Line({ points }: Readonly) {
const [isDeletable, setIsDeletable] = useState(false);
const { socket } = useSocketStore();
const { toolMode } = useToolMode();
- const { wallStore, floorStore, zoneStore } = useSceneContext();
+ const { wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext();
+ const { push2D } = undoRedo2DStore();
const { removeWallByPoints, setPosition: setWallPosition, getWallsByPointId } = wallStore();
- const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId } = floorStore();
- const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId } = zoneStore();
+ const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore();
+ const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
@@ -117,10 +118,26 @@ function Line({ points }: Readonly) {
}
socket.emit('v1:model-Wall:delete', data);
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Line-Delete',
+ point: {
+ type: 'Wall',
+ lineData: removedWall,
+ timeStamp: new Date().toISOString(),
+ }
+ }
+ ]
+ });
}
+
setHoveredLine(null);
}
if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
+ const Floors = getFloorsByPoints(points);
const { removedFloors, updatedFloors } = removeFloorByPoints(points);
if (removedFloors.length > 0) {
removedFloors.forEach(floor => {
@@ -143,6 +160,22 @@ function Line({ points }: Readonly) {
socket.emit('v1:model-Floor:delete', data);
}
});
+
+ const removedFloorsData = removedFloors.map((floor) => ({
+ type: "Floor" as const,
+ lineData: floor,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Delete',
+ points: removedFloorsData
+ }
+ ]
+ });
}
if (updatedFloors.length > 0) {
updatedFloors.forEach(floor => {
@@ -165,11 +198,29 @@ function Line({ points }: Readonly) {
socket.emit('v1:model-Floor:add', data);
}
});
+
+ const updatedFloorsData = updatedFloors.map((floor) => ({
+ type: "Floor" as const,
+ lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
+ newData: floor,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Update',
+ points: updatedFloorsData
+ }
+ ]
+ });
}
setHoveredLine(null);
}
if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
+ const Zones = getZonesByPoints(points);
const { removedZones, updatedZones } = removeZoneByPoints(points);
if (removedZones.length > 0) {
removedZones.forEach(zone => {
@@ -192,6 +243,22 @@ function Line({ points }: Readonly) {
socket.emit('v1:zone:delete', data);
}
});
+
+ const removedZonesData = removedZones.map((zone) => ({
+ type: "Zone" as const,
+ lineData: zone,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Delete',
+ points: removedZonesData
+ }
+ ]
+ });
}
if (updatedZones.length > 0) {
updatedZones.forEach(zone => {
@@ -214,7 +281,26 @@ function Line({ points }: Readonly) {
socket.emit('v1:zone:add', data);
}
});
+
+ const updatedZonesData = updatedZones.map((zone) => ({
+ type: "Zone" as const,
+ lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
+ newData: zone,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Update',
+ points: updatedZonesData
+ }
+ ]
+ });
}
+
+ setHoveredLine(null);
}
handleCanvasCursors('default');
}
diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx
index 5c62dd5..b203f05 100644
--- a/app/src/modules/builder/point/point.tsx
+++ b/app/src/modules/builder/point/point.tsx
@@ -32,13 +32,14 @@ function Point({ point }: { readonly point: Point }) {
const [dragOffset, setDragOffset] = useState(null);
const { socket } = useSocketStore();
const { toolMode } = useToolMode();
- const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
+ const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext();
+ const { push2D } = undoRedo2DStore();
const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore();
const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore();
const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore();
const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore();
const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position });
- const { hoveredPoint,hoveredLine, setHoveredPoint } = useBuilderStore();
+ const { hoveredPoint, hoveredLine, setHoveredPoint } = useBuilderStore();
const { selectedPoints } = useSelectedPoints();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
@@ -47,6 +48,13 @@ function Point({ point }: { readonly point: Point }) {
const boxScale: [number, number, number] = Constants.pointConfig.boxScale;
const colors = getColor(point);
+ const [initialPositions, setInitialPositions] = useState<{
+ aisles?: Aisle[],
+ walls?: Wall[],
+ floors?: Floor[],
+ zones?: Zone[]
+ }>({});
+
useEffect(() => {
handleCanvasCursors('default');
}, [toolMode])
@@ -152,6 +160,20 @@ function Point({ point }: { readonly point: Point }) {
const currentPosition = new THREE.Vector3(...point.position);
const offset = new THREE.Vector3().subVectors(currentPosition, hit);
setDragOffset(offset);
+
+ if (point.pointType === 'Aisle') {
+ const aisles = getAislesByPointId(point.pointUuid);
+ setInitialPositions({ aisles });
+ } else if (point.pointType === 'Wall') {
+ const walls = getWallsByPointId(point.pointUuid);
+ setInitialPositions({ walls });
+ } else if (point.pointType === 'Floor') {
+ const floors = getFloorsByPointId(point.pointUuid);
+ setInitialPositions({ floors });
+ } else if (point.pointType === 'Zone') {
+ const zones = getZonesByPointId(point.pointUuid);
+ setInitialPositions({ zones });
+ }
}
};
@@ -159,6 +181,7 @@ function Point({ point }: { readonly point: Point }) {
handleCanvasCursors('default');
setDragOffset(null);
if (toolMode !== 'move') return;
+
if (point.pointType === 'Aisle') {
const updatedAisles = getAislesByPointId(point.pointUuid);
if (updatedAisles.length > 0 && projectId) {
@@ -180,6 +203,23 @@ function Point({ point }: { readonly point: Point }) {
type: updatedAisle.type
})
})
+
+ if (initialPositions.aisles && initialPositions.aisles.length > 0) {
+ const updatedPoints = initialPositions.aisles.map((aisle) => ({
+ type: "Aisle" as const,
+ lineData: aisle,
+ newData: updatedAisles.find(a => a.aisleUuid === aisle.aisleUuid),
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [{
+ actionType: 'Lines-Update',
+ points: updatedPoints,
+ }]
+ });
+ }
}
} else if (point.pointType === 'Wall') {
const updatedWalls = getWallsByPointId(point.pointUuid);
@@ -203,6 +243,23 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Wall:add', data);
});
}
+
+ if (initialPositions.walls && initialPositions.walls.length > 0) {
+ const updatedPoints = initialPositions.walls.map((wall) => ({
+ type: "Wall" as const,
+ lineData: wall,
+ newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid),
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [{
+ actionType: 'Lines-Update',
+ points: updatedPoints,
+ }]
+ });
+ }
} else if (point.pointType === 'Floor') {
const updatedFloors = getFloorsByPointId(point.pointUuid);
if (updatedFloors && updatedFloors.length > 0 && projectId) {
@@ -225,6 +282,23 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Floor:add', data);
});
}
+
+ if (initialPositions.floors && initialPositions.floors.length > 0) {
+ const updatedPoints = initialPositions.floors.map((floor) => ({
+ type: "Floor" as const,
+ lineData: floor,
+ newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid),
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [{
+ actionType: 'Lines-Update',
+ points: updatedPoints,
+ }]
+ });
+ }
} else if (point.pointType === 'Zone') {
const updatedZones = getZonesByPointId(point.pointUuid);
if (updatedZones && updatedZones.length > 0 && projectId) {
@@ -247,13 +321,33 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:zone:add', data);
});
}
+
+ if (initialPositions.zones && initialPositions.zones.length > 0) {
+ const updatedPoints = initialPositions.zones.map((zone) => ({
+ type: "Zone" as const,
+ lineData: zone,
+ newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid),
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [{
+ actionType: 'Lines-Update',
+ points: updatedPoints,
+ }]
+ });
+ }
}
+
+ setInitialPositions({});
}
const handlePointClick = (point: Point) => {
if (toolMode === '2D-Delete') {
if (point.pointType === 'Aisle') {
const removedAisles = removeAislePoint(point.pointUuid);
+ setHoveredPoint(null);
if (removedAisles.length > 0) {
removedAisles.forEach(aisle => {
if (projectId) {
@@ -273,9 +367,25 @@ function Point({ point }: { readonly point: Point }) {
}
socket.emit('v1:model-aisle:delete', data);
+
}
});
- setHoveredPoint(null);
+
+ const removedAislesData = removedAisles.map((aisle) => ({
+ type: "Aisle" as const,
+ lineData: aisle,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Delete',
+ points: removedAislesData
+ }
+ ]
+ });
}
}
if (point.pointType === 'Wall') {
@@ -302,9 +412,26 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Wall:delete', data);
}
});
+
+ const removedWallsData = removedWalls.map((wall) => ({
+ type: "Wall" as const,
+ lineData: wall,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Delete',
+ points: removedWallsData
+ }
+ ]
+ });
}
}
if (point.pointType === 'Floor') {
+ const Floors = getFloorsByPointId(point.pointUuid);
const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid);
setHoveredPoint(null);
if (removedFloors.length > 0) {
@@ -328,6 +455,22 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Floor:delete', data);
}
});
+
+ const removedFloorsData = removedFloors.map((floor) => ({
+ type: "Floor" as const,
+ lineData: floor,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Delete',
+ points: removedFloorsData
+ }
+ ]
+ });
}
if (updatedFloors.length > 0) {
updatedFloors.forEach(floor => {
@@ -350,9 +493,27 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:model-Floor:add', data);
}
});
+
+ const updatedFloorsData = updatedFloors.map((floor) => ({
+ type: "Floor" as const,
+ lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
+ newData: floor,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Update',
+ points: updatedFloorsData
+ }
+ ]
+ });
}
}
if (point.pointType === 'Zone') {
+ const Zones = getZonesByPointId(point.pointUuid);
const { removedZones, updatedZones } = removeZonePoint(point.pointUuid);
setHoveredPoint(null);
if (removedZones.length > 0) {
@@ -376,6 +537,22 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:zone:delete', data);
}
});
+
+ const removedZonesData = removedZones.map((zone) => ({
+ type: "Zone" as const,
+ lineData: zone,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Delete',
+ points: removedZonesData
+ }
+ ]
+ });
}
if (updatedZones.length > 0) {
updatedZones.forEach(zone => {
@@ -398,6 +575,23 @@ function Point({ point }: { readonly point: Point }) {
socket.emit('v1:zone:add', data);
}
});
+
+ const updatedZonesData = updatedZones.map((zone) => ({
+ type: "Zone" as const,
+ lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
+ newData: zone,
+ timeStamp: new Date().toISOString(),
+ }));
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Lines-Update',
+ points: updatedZonesData
+ }
+ ]
+ });
}
}
handleCanvasCursors('default');
@@ -422,7 +616,6 @@ function Point({ point }: { readonly point: Point }) {
return null;
}
-
return (
<>
{!isSelected ?
@@ -453,7 +646,7 @@ function Point({ point }: { readonly point: Point }) {
onPointerOut={() => {
if (hoveredPoint) {
setHoveredPoint(null);
- if(!hoveredLine){
+ if (!hoveredLine) {
handleCanvasCursors('default');
}
}
diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx
index a441ea8..1a73bfe 100644
--- a/app/src/modules/builder/wall/Instances/instance/wall.tsx
+++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx
@@ -10,7 +10,7 @@ import { useToggleView, useWallVisibility } from '../../../../../store/builder/s
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import * as Constants from '../../../../../types/world/worldConstants';
-import DecalInstance from '../../../Decal/decalInstance';
+// import DecalInstance from '../../../Decal/decalInstance';
import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
@@ -63,10 +63,10 @@ function Wall({ wall }: { readonly wall: Wall }) {
const materials = useMemo(() => {
return [
- new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Left
- new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Right
- new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Top
- new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible }), // Bottom
+ new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Left
+ new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Right
+ new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Top
+ new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }), // Bottom
new THREE.MeshStandardMaterial({
color: Constants.wallConfig.defaultColor,
side: THREE.DoubleSide,
@@ -99,11 +99,15 @@ function Wall({ wall }: { readonly wall: Wall }) {
return (
-
+
@@ -265,7 +265,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
e.stopPropagation();
let currentObject = e.object as THREE.Object3D;
while (currentObject) {
- if (currentObject.name === "Scene") {
+ if (currentObject.userData.wallUuid) {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
@@ -286,7 +286,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
e.stopPropagation();
let currentObject = e.object as THREE.Object3D;
while (currentObject) {
- if (currentObject.name === "Scene") {
+ if (currentObject.userData.wallUuid) {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
diff --git a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx
index f1f1b6a..bed501f 100644
--- a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx
+++ b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx
@@ -19,8 +19,9 @@ function ZoneCreator() {
const { toolMode } = useToolMode();
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
- const { zoneStore } = useSceneContext();
- const { zones, addZone, getZonePointById, getZoneByPoints } = zoneStore();
+ const { zoneStore, undoRedo2DStore } = useSceneContext();
+ const { addZone, getZonePointById } = zoneStore();
+ const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
@@ -102,6 +103,21 @@ function ZoneCreator() {
};
addZone(zone);
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Line-Create',
+ point: {
+ type: 'Zone',
+ lineData: zone,
+ timeStamp: new Date().toISOString(),
+ }
+ }
+ ],
+ })
+
if (projectId) {
// API
@@ -139,6 +155,21 @@ function ZoneCreator() {
};
addZone(zone);
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Line-Create',
+ point: {
+ type: 'Zone',
+ lineData: zone,
+ timeStamp: new Date().toISOString(),
+ }
+ }
+ ],
+ })
+
if (projectId) {
// API
@@ -186,6 +217,21 @@ function ZoneCreator() {
};
addZone(zone);
+
+ push2D({
+ type: 'Draw',
+ actions: [
+ {
+ actionType: 'Line-Create',
+ point: {
+ type: 'Zone',
+ lineData: zone,
+ timeStamp: new Date().toISOString(),
+ }
+ }
+ ],
+ })
+
if (projectId) {
// API
@@ -238,7 +284,7 @@ function ZoneCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
- }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, getZoneByPoints, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
+ }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
return (
<>
diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx
index 975ab54..77f234c 100644
--- a/app/src/modules/scene/controls/controls.tsx
+++ b/app/src/modules/scene/controls/controls.tsx
@@ -14,6 +14,7 @@ import TransformControl from "./transformControls/transformControls";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
+import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
export default function Controls() {
const controlsRef = useRef(null);
@@ -142,6 +143,8 @@ export default function Controls() {
+
+
>
diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx
index 790a330..fa12f61 100644
--- a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx
@@ -83,7 +83,7 @@ function MoveControls2D({
if (keyCombination === "G") {
if (selectedPoints.length > 0) {
- moveAssets();
+ movePoints();
}
}
@@ -164,7 +164,7 @@ function MoveControls2D({
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
- const moveAssets = useCallback(() => {
+ const movePoints = (() => {
if (selectedPoints.length === 0) return;
const states: Record = {};
@@ -192,9 +192,9 @@ function MoveControls2D({
setMovedObjects(selectedPoints);
setIsMoving(true);
- }, [selectedPoints, camera, pointer, plane, raycaster, calculateDragOffset]);
+ });
- const resetToInitialPositions = useCallback(() => {
+ const resetToInitialPositions = () => {
setTimeout(() => {
movedObjects.forEach((movedPoint: THREE.Object3D) => {
if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) {
@@ -218,7 +218,7 @@ function MoveControls2D({
}
});
}, 0)
- }, [movedObjects, initialStates, setAislePosition, setWallPosition, setFloorPosition, setZonePosition]);
+ };
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx
index 9c81a82..33e9555 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx
@@ -1,5 +1,5 @@
import * as THREE from "three";
-import { useEffect, useMemo } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
@@ -10,20 +10,18 @@ import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
-// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
+// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
const CopyPasteControls3D = ({
copiedObjects,
setCopiedObjects,
pastedObjects,
setpastedObjects,
- selectionGroup,
setDuplicatedObjects,
movedObjects,
setMovedObjects,
rotatedObjects,
setRotatedObjects,
- boundingBoxRef
}: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
@@ -33,31 +31,67 @@ const CopyPasteControls3D = ({
const { assetStore, eventStore } = useSceneContext();
const { addEvent } = eventStore();
const { projectId } = useParams();
- const { assets, addAsset } = assetStore();
+ const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
+ const [isPasting, setIsPasting] = useState(false);
+ const [relativePositions, setRelativePositions] = useState([]);
+ const [centerOffset, setCenterOffset] = useState(null);
+ const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
+ left: false,
+ right: false,
+ });
+
+ const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => {
+ if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] };
+
+ const box = new THREE.Box3();
+ objects.forEach(obj => box.expandByObject(obj));
+ const center = new THREE.Vector3();
+ box.getCenter(center);
+
+ const relatives = objects.map(obj => {
+ const relativePos = new THREE.Vector3().subVectors(obj.position, center);
+ return relativePos;
+ });
+
+ return { center, relatives };
+ }, []);
+
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
- let isMoving = false;
+ let isPointerMoving = false;
- const onPointerDown = () => {
- isMoving = false;
+ const onPointerDown = (event: PointerEvent) => {
+ isPointerMoving = false;
+ if (event.button === 0) mouseButtonsDown.current.left = true;
+ if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerMove = () => {
- isMoving = true;
+ isPointerMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
- if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
+ if (event.button === 0) mouseButtonsDown.current.left = false;
+ if (event.button === 2) mouseButtonsDown.current.right = false;
+
+ if (!isPointerMoving && pastedObjects.length > 0 && event.button === 0) {
event.preventDefault();
addPastedObjects();
}
+ if (!isPointerMoving && pastedObjects.length > 0 && event.button === 2) {
+ event.preventDefault();
+ clearSelection();
+ pastedObjects.forEach((obj: THREE.Object3D) => {
+ removeAsset(obj.userData.modelUuid);
+ });
+ }
};
const onKeyDown = (event: KeyboardEvent) => {
@@ -69,6 +103,13 @@ const CopyPasteControls3D = ({
if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
+ if (keyCombination === "ESCAPE" && pastedObjects.length > 0) {
+ event.preventDefault();
+ clearSelection();
+ pastedObjects.forEach((obj: THREE.Object3D) => {
+ removeAsset(obj.userData.modelUuid);
+ });
+ }
};
if (!toggleView) {
@@ -84,27 +125,23 @@ const CopyPasteControls3D = ({
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
-
}, [assets, camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, rotatedObjects]);
useFrame(() => {
- if (pastedObjects.length > 0) {
- const intersectionPoint = new THREE.Vector3();
- raycaster.setFromCamera(pointer, camera);
- const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
- if (point) {
- const position = new THREE.Vector3();
- if (boundingBoxRef.current) {
- boundingBoxRef.current?.getWorldPosition(position)
- selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
- } else {
- const box = new THREE.Box3();
- pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
- const center = new THREE.Vector3();
- box.getCenter(center);
- selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
- }
- }
+ if (!isPasting || pastedObjects.length === 0) return;
+ if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) return;
+
+ const intersectionPoint = new THREE.Vector3();
+ raycaster.setFromCamera(pointer, camera);
+ const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
+
+ if (hit && centerOffset) {
+ pastedObjects.forEach((pastedObject: THREE.Object3D, index: number) => {
+ const model = scene.getObjectByProperty("uuid", pastedObject.userData.modelUuid);
+ if (!model) return;
+ const newPos = new THREE.Vector3().addVectors(hit, relativePositions[index]);
+ model.position.set(newPos.x, 0, newPos.z);
+ });
}
});
@@ -127,31 +164,41 @@ const CopyPasteControls3D = ({
const pasteCopiedObjects = () => {
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
- const newClones = copiedObjects.map((obj: THREE.Object3D) => {
- const clone = obj.clone();
- clone.position.copy(obj.position);
+ const { center, relatives } = calculateRelativePositions(copiedObjects);
+ setRelativePositions(relatives);
+
+ const newPastedObjects = copiedObjects.map((obj: any) => {
+ const clone = SkeletonUtils.clone(obj);
+ clone.userData.modelUuid = THREE.MathUtils.generateUUID();
return clone;
});
- selectionGroup.current.add(...newClones);
- setpastedObjects([...newClones]);
- setSelectedAssets([...newClones]);
- const intersectionPoint = new THREE.Vector3();
+ setpastedObjects(newPastedObjects);
+
raycaster.setFromCamera(pointer, camera);
- const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
+ const intersectionPoint = new THREE.Vector3();
+ const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
- if (point) {
- const position = new THREE.Vector3();
- if (boundingBoxRef.current) {
- boundingBoxRef.current?.getWorldPosition(position)
- selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
- } else {
- const box = new THREE.Box3();
- newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
- const center = new THREE.Vector3();
- box.getCenter(center);
- selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
- }
+ if (hit) {
+ const offset = new THREE.Vector3().subVectors(center, hit);
+ setCenterOffset(offset);
+ setIsPasting(true);
+
+ newPastedObjects.forEach((obj: THREE.Object3D, index: number) => {
+ const initialPos = new THREE.Vector3().addVectors(hit, relatives[index]);
+ const asset: Asset = {
+ modelUuid: obj.userData.modelUuid,
+ modelName: obj.userData.modelName,
+ assetId: obj.userData.assetId,
+ position: initialPos.toArray(),
+ rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
+ isLocked: false,
+ isVisible: true,
+ isCollidable: false,
+ opacity: 0.5,
+ };
+ addAsset(asset);
+ });
}
}
};
@@ -159,33 +206,35 @@ const CopyPasteControls3D = ({
const addPastedObjects = () => {
if (pastedObjects.length === 0) return;
- pastedObjects.forEach(async (obj: THREE.Object3D) => {
- if (obj) {
- const worldPosition = new THREE.Vector3();
- obj.getWorldPosition(worldPosition);
- obj.position.copy(worldPosition);
+ pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
+ if (pastedAsset) {
+ const assetUuid = pastedAsset.userData.modelUuid;
+ const asset = getAssetById(assetUuid);
+ const model = scene.getObjectByProperty("uuid", pastedAsset.userData.modelUuid);
+ if (!asset || !model) return;
+ const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
- modelUuid: THREE.MathUtils.generateUUID(),
- modelName: obj.userData.modelName,
- assetId: obj.userData.assetId,
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
+ modelUuid: pastedAsset.userData.modelUuid,
+ modelName: pastedAsset.userData.modelName,
+ assetId: pastedAsset.userData.assetId,
+ position: [position.x, position.y, position.z],
+ rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] },
isLocked: false,
isVisible: true,
};
let updatedEventData = null;
- if (obj.userData.eventData) {
- updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData));
+ if (pastedAsset.userData.eventData) {
+ updatedEventData = JSON.parse(JSON.stringify(pastedAsset.userData.eventData));
updatedEventData.modelUuid = newFloorItem.modelUuid;
const eventData: any = {
- type: obj.userData.eventData.type,
+ type: pastedAsset.userData.eventData.type,
};
- if (obj.userData.eventData.type === "Conveyor") {
+ if (pastedAsset.userData.eventData.type === "Conveyor") {
const ConveyorEvent: ConveyorEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -217,7 +266,7 @@ const CopyPasteControls3D = ({
rotation: point.rotation
}));
- } else if (obj.userData.eventData.type === "Vehicle") {
+ } else if (pastedAsset.userData.eventData.type === "Vehicle") {
const vehicleEvent: VehicleEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -255,7 +304,7 @@ const CopyPasteControls3D = ({
rotation: vehicleEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "ArmBot") {
+ } else if (pastedAsset.userData.eventData.type === "ArmBot") {
const roboticArmEvent: RoboticArmEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -289,7 +338,7 @@ const CopyPasteControls3D = ({
rotation: roboticArmEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "StaticMachine") {
+ } else if (pastedAsset.userData.eventData.type === "StaticMachine") {
const machineEvent: MachineEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -317,7 +366,7 @@ const CopyPasteControls3D = ({
position: machineEvent.point.position,
rotation: machineEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "Storage") {
+ } else if (pastedAsset.userData.eventData.type === "Storage") {
const storageEvent: StorageEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -344,7 +393,7 @@ const CopyPasteControls3D = ({
position: storageEvent.point.position,
rotation: storageEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "Human") {
+ } else if (pastedAsset.userData.eventData.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -363,6 +412,7 @@ const CopyPasteControls3D = ({
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
+ assemblyCount: 1,
loadCount: 1,
processTime: 10,
triggers: []
@@ -379,28 +429,14 @@ const CopyPasteControls3D = ({
}
newFloorItem.eventData = eventData;
- //REST
-
- // await setAssetsApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
-
- //SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
- position: newFloorItem.position,
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
+ position: [position.x, 0, position.z],
+ rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
@@ -410,54 +446,36 @@ const CopyPasteControls3D = ({
projectId
};
- // console.log('data: ', data);
- socket.emit("v1:model-asset:add", data);
+ //REST
- obj.userData = {
- name: newFloorItem.modelName,
- modelId: newFloorItem.assetId,
- modelUuid: newFloorItem.modelUuid,
- eventData: JSON.parse(JSON.stringify(eventData))
- };
+ // await setAssetsApi(data);
+
+ //SOCKET
+
+ socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
- position: data.position,
+ position: [position.x, 0, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
eventData: data.eventData
- }
-
- addAsset(asset);
+ };
+ updateAsset(asset.modelUuid, asset);
} else {
-
- //REST
-
- // await setAssetsApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
-
- //SOCKET
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
- position: newFloorItem.position,
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
+ position: [position.x, 0, position.z],
+ rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
@@ -466,22 +484,21 @@ const CopyPasteControls3D = ({
userId
};
- // console.log('data: ', data);
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
- position: data.position,
+ position: [position.x, 0, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
- }
+ };
- addAsset(asset);
+ updateAsset(asset.modelUuid, asset);
}
}
});
@@ -491,15 +508,14 @@ const CopyPasteControls3D = ({
};
const clearSelection = () => {
- selectionGroup.current.children = [];
- selectionGroup.current.position.set(0, 0, 0);
- selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
- }
+ setIsPasting(false);
+ setCenterOffset(null);
+ };
return null;
};
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx
index 38816f4..bd78aa7 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx
@@ -1,5 +1,5 @@
import * as THREE from "three";
-import { useEffect, useMemo } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
@@ -10,18 +10,16 @@ import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
-// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
+// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
const DuplicationControls3D = ({
duplicatedObjects,
setDuplicatedObjects,
setpastedObjects,
- selectionGroup,
movedObjects,
setMovedObjects,
rotatedObjects,
setRotatedObjects,
- boundingBoxRef
}: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
@@ -31,39 +29,71 @@ const DuplicationControls3D = ({
const { assetStore, eventStore } = useSceneContext();
const { addEvent } = eventStore();
const { projectId } = useParams();
- const { assets, addAsset } = assetStore();
+ const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
+ const [dragOffset, setDragOffset] = useState(null);
+ const [initialPositions, setInitialPositions] = useState>({});
+ const [isDuplicating, setIsDuplicating] = useState(false);
+ const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
+ left: false,
+ right: false,
+ });
+
+ const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
+ const pointPosition = new THREE.Vector3().copy(point.position);
+ return new THREE.Vector3().subVectors(pointPosition, hitPoint);
+ }, []);
+
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
- let isMoving = false;
+ let isPointerMoving = false;
- const onPointerDown = () => {
- isMoving = false;
+ const onPointerDown = (event: PointerEvent) => {
+ isPointerMoving = false;
+ if (event.button === 0) mouseButtonsDown.current.left = true;
+ if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerMove = () => {
- isMoving = true;
+ isPointerMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
- if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
+ if (event.button === 0) mouseButtonsDown.current.left = false;
+ if (event.button === 2) mouseButtonsDown.current.right = false;
+
+ if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
addDuplicatedAssets();
}
+ if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 2) {
+ event.preventDefault();
+ clearSelection();
+ duplicatedObjects.forEach((obj: THREE.Object3D) => {
+ removeAsset(obj.userData.modelUuid);
+ });
+ }
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
- if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
+ if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
+ if (keyCombination === "ESCAPE" && duplicatedObjects.length > 0) {
+ event.preventDefault();
+ clearSelection();
+ duplicatedObjects.forEach((obj: THREE.Object3D) => {
+ removeAsset(obj.userData.modelUuid);
+ });
+ }
};
if (!toggleView) {
@@ -79,87 +109,133 @@ const DuplicationControls3D = ({
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
-
}, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]);
useFrame(() => {
- if (duplicatedObjects.length > 0) {
- const intersectionPoint = new THREE.Vector3();
- raycaster.setFromCamera(pointer, camera);
- const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
- if (point) {
- const position = new THREE.Vector3();
- if (boundingBoxRef.current) {
- boundingBoxRef.current?.getWorldPosition(position)
- selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
- } else {
- const box = new THREE.Box3();
- duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
- const center = new THREE.Vector3();
- box.getCenter(center);
- selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
+ if (!isDuplicating || duplicatedObjects.length === 0) return;
+
+ const intersectionPoint = new THREE.Vector3();
+ raycaster.setFromCamera(pointer, camera);
+ const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
+
+ if (hit) {
+ if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
+ const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid);
+ if (model) {
+ const newOffset = calculateDragOffset(model, intersectionPoint);
+ setDragOffset(newOffset);
}
+ return;
+ }
+
+ if (dragOffset) {
+ const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
+
+ duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => {
+ if (duplicatedObject.userData.modelUuid) {
+ const initialPosition = initialPositions[duplicatedObject.userData.modelUuid];
+
+ if (initialPosition) {
+
+ const relativeOffset = new THREE.Vector3().subVectors(
+ initialPosition,
+ initialPositions[duplicatedObjects[0].userData.modelUuid]
+ );
+ const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid);
+
+ const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset);
+ const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
+
+ if (model) {
+ model.position.set(...positionArray);
+ }
+ }
+ }
+ });
}
}
});
- const duplicateSelection = () => {
+ const duplicateSelection = useCallback(() => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
- const newClones = selectedAssets.map((asset: any) => {
- if (asset.userData.rigidBodyRef) {
- let userData = { ...asset.userData };
+ const positions: Record = {};
+
+ const newDuplicatedObjects = selectedAssets.map((obj: any) => {
+ if (obj.userData.rigidBodyRef) {
+ let userData = { ...obj.userData };
delete userData.rigidBodyRef;
- asset.userData = userData;
+ obj.userData = userData;
}
- const clone = SkeletonUtils.clone(asset);
- clone.position.copy(asset.position);
+ const clone = SkeletonUtils.clone(obj);
+ clone.userData.modelUuid = THREE.MathUtils.generateUUID();
+ positions[clone.userData.modelUuid] = new THREE.Vector3().copy(obj.position);
return clone;
});
- selectionGroup.current.add(...newClones);
- setDuplicatedObjects(newClones);
+ setDuplicatedObjects(newDuplicatedObjects);
+ setInitialPositions(positions);
- const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
- const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
+ const intersectionPoint = new THREE.Vector3();
+ const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
- if (point) {
- const position = new THREE.Vector3();
- boundingBoxRef.current?.getWorldPosition(position)
- selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
+ if (hit) {
+ const offset = calculateDragOffset(selectedAssets[0], hit);
+ setDragOffset(offset);
}
+
+ setIsDuplicating(true);
+
+ newDuplicatedObjects.forEach((obj: THREE.Object3D) => {
+ const asset: Asset = {
+ modelUuid: obj.userData.modelUuid,
+ modelName: obj.userData.modelName,
+ assetId: obj.userData.assetId,
+ position: [obj.position.x, 0, obj.position.z],
+ rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
+ isLocked: false,
+ isVisible: true,
+ isCollidable: false,
+ opacity: 0.5,
+ };
+ addAsset(asset);
+ });
}
- };
+ }, [selectedAssets, duplicatedObjects]);
+
const addDuplicatedAssets = () => {
if (duplicatedObjects.length === 0) return;
- duplicatedObjects.forEach(async (obj: THREE.Object3D) => {
- if (obj) {
- const worldPosition = new THREE.Vector3();
- obj.getWorldPosition(worldPosition);
- obj.position.copy(worldPosition);
+ duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => {
+ if (duplicatedAsset) {
+ const assetUuid = duplicatedAsset.userData.modelUuid;
+ const asset = getAssetById(assetUuid);
+ const model = scene.getObjectByProperty("uuid", duplicatedAsset.userData.modelUuid);
+ if (!asset || !model) return;
+ const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
- modelUuid: THREE.MathUtils.generateUUID(),
- modelName: obj.userData.modelName,
- assetId: obj.userData.assetId,
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
+ modelUuid: duplicatedAsset.userData.modelUuid,
+ modelName: duplicatedAsset.userData.modelName,
+ assetId: duplicatedAsset.userData.assetId,
+ position: [position.x, position.y, position.z],
+ rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] },
isLocked: false,
isVisible: true,
};
let updatedEventData = null;
- if (obj.userData.eventData) {
- updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData));
+
+ if (duplicatedAsset.userData.eventData) {
+ updatedEventData = JSON.parse(JSON.stringify(duplicatedAsset.userData.eventData));
updatedEventData.modelUuid = newFloorItem.modelUuid;
const eventData: any = {
- type: obj.userData.eventData.type,
+ type: duplicatedAsset.userData.eventData.type,
};
- if (obj.userData.eventData.type === "Conveyor") {
+ if (duplicatedAsset.userData.eventData.type === "Conveyor") {
const ConveyorEvent: ConveyorEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -191,7 +267,7 @@ const DuplicationControls3D = ({
rotation: point.rotation
}));
- } else if (obj.userData.eventData.type === "Vehicle") {
+ } else if (duplicatedAsset.userData.eventData.type === "Vehicle") {
const vehicleEvent: VehicleEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -229,7 +305,7 @@ const DuplicationControls3D = ({
rotation: vehicleEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "ArmBot") {
+ } else if (duplicatedAsset.userData.eventData.type === "ArmBot") {
const roboticArmEvent: RoboticArmEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -263,7 +339,7 @@ const DuplicationControls3D = ({
rotation: roboticArmEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "StaticMachine") {
+ } else if (duplicatedAsset.userData.eventData.type === "StaticMachine") {
const machineEvent: MachineEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -291,7 +367,7 @@ const DuplicationControls3D = ({
position: machineEvent.point.position,
rotation: machineEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "Storage") {
+ } else if (duplicatedAsset.userData.eventData.type === "Storage") {
const storageEvent: StorageEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -318,7 +394,7 @@ const DuplicationControls3D = ({
position: storageEvent.point.position,
rotation: storageEvent.point.rotation
};
- } else if (obj.userData.eventData.type === "Human") {
+ } else if (duplicatedAsset.userData.eventData.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
@@ -337,6 +413,7 @@ const DuplicationControls3D = ({
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
+ assemblyCount: 1,
loadCount: 1,
processTime: 10,
triggers: []
@@ -354,121 +431,92 @@ const DuplicationControls3D = ({
newFloorItem.eventData = eventData;
- //REST
-
- // await setAssetsApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
-
- //SOCKET
-
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
- position: newFloorItem.position,
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
+ position: [position.x, position.y, position.z],
+ rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
eventData: eventData,
versionId: selectedVersion?.versionId || '',
- projectId,
- userId
+ userId,
+ projectId
};
- // console.log('data: ', data);
+ //REST
+
+ // await setAssetsApi(data);
+
+ //SOCKET
+
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
- position: data.position,
+ position: [position.x, position.y, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
eventData: data.eventData
- }
-
- addAsset(asset);
+ };
+ updateAsset(asset.modelUuid, asset);
} else {
-
- //REST
-
- // await setAssetsApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
-
- //SOCKET
-
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
- position: newFloorItem.position,
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
+ position: [position.x, position.y, position.z],
+ rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
versionId: selectedVersion?.versionId || '',
- userId,
- projectId
+ projectId,
+ userId
};
- // console.log('data: ', data);/
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
- position: data.position,
+ position: [position.x, position.y, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
- }
+ };
- addAsset(asset);
+ updateAsset(asset.modelUuid, asset);
}
}
});
echo.success("Object duplicated!");
clearSelection();
- }
+ };
const clearSelection = () => {
- selectionGroup.current.children = [];
- selectionGroup.current.position.set(0, 0, 0);
- selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
- }
+ setIsDuplicating(false);
+ setDragOffset(null);
+ };
return null;
};
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx
index 6658f62..18f4abf 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx
@@ -1,5 +1,5 @@
import * as THREE from "three";
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
@@ -22,7 +22,6 @@ function MoveControls3D({
setpastedObjects,
duplicatedObjects,
setDuplicatedObjects,
- selectionGroup,
rotatedObjects,
setRotatedObjects,
boundingBoxRef,
@@ -39,11 +38,16 @@ function MoveControls3D({
const { userId, organization } = getUserData();
const { projectId } = useParams();
const { assetStore, eventStore, productStore } = useSceneContext();
- const { updateAsset } = assetStore();
- const AssetGroup = useRef(undefined);
+ const { updateAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
+ const [dragOffset, setDragOffset] = useState(null);
+ const [initialPositions, setInitialPositions] = useState>({});
+ const [initialStates, setInitialStates] = useState>({});
+ const [isMoving, setIsMoving] = useState(false);
+ const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
+
const updateBackend = (
productName: string,
productUuid: string,
@@ -65,47 +69,39 @@ function MoveControls3D({
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
- const itemsGroup: any = scene.getObjectByName("Asset Group");
- AssetGroup.current = itemsGroup;
-
- if (!AssetGroup.current) {
- console.error("Asset Group not found in the scene.");
- return;
- }
-
- let isMoving = false;
-
- const onPointerDown = () => {
- isMoving = false;
- };
+ let isPointerMoving = false;
const onPointerMove = () => {
- isMoving = true;
+ isPointerMoving = true;
};
const onKeyUp = (event: KeyboardEvent) => {
- const isModifierKey = event.key === "Control" || event.key === "Shift";
+ const isModifierKey = (!event.shiftKey && !event.ctrlKey);
- if (isModifierKey) {
+ if (isModifierKey && keyEvent !== "") {
setKeyEvent("");
}
};
+ const onPointerDown = (event: PointerEvent) => {
+ isPointerMoving = false;
+
+ if (event.button === 0) mouseButtonsDown.current.left = true;
+ if (event.button === 2) mouseButtonsDown.current.right = true;
+ };
+
const onPointerUp = (event: PointerEvent) => {
- if (!isMoving && movedObjects.length > 0 && event.button === 0) {
+ if (event.button === 0) mouseButtonsDown.current.left = false;
+ if (event.button === 2) mouseButtonsDown.current.right = false;
+
+ if (!isPointerMoving && movedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeMovedAssets();
}
- if (!isMoving && movedObjects.length > 0 && event.button === 2) {
+ if (!isPointerMoving && movedObjects.length > 0 && event.button === 2) {
event.preventDefault();
-
+ resetToInitialPositions();
clearSelection();
- movedObjects.forEach((asset: any) => {
- if (AssetGroup.current) {
- AssetGroup.current.attach(asset);
- }
- });
-
setMovedObjects([]);
}
setKeyEvent("");
@@ -117,10 +113,12 @@ function MoveControls3D({
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0)
return;
- if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
- setKeyEvent(keyCombination);
- } else {
- setKeyEvent("");
+ if (keyCombination !== keyEvent) {
+ if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") {
+ setKeyEvent(keyCombination);
+ } else {
+ setKeyEvent("");
+ }
}
if (keyCombination === "G") {
@@ -131,14 +129,8 @@ function MoveControls3D({
if (keyCombination === "ESCAPE") {
event.preventDefault();
-
+ resetToInitialPositions();
clearSelection();
- movedObjects.forEach((asset: any) => {
- if (AssetGroup.current) {
- AssetGroup.current.attach(asset);
- }
- });
-
setMovedObjects([]);
}
};
@@ -158,118 +150,166 @@ function MoveControls3D({
canvasElement.removeEventListener("keydown", onKeyDown);
canvasElement?.removeEventListener("keyup", onKeyUp);
};
- }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent,]);
+ }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]);
- let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25;
+ const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
+ const pointPosition = new THREE.Vector3().copy(point.position);
+ return new THREE.Vector3().subVectors(pointPosition, hitPoint);
+ }, []);
+
+ const resetToInitialPositions = useCallback(() => {
+ setTimeout(() => {
+ movedObjects.forEach((movedObject: THREE.Object3D) => {
+ if (movedObject.userData.modelUuid && initialStates[movedObject.uuid]) {
+ const initialState = initialStates[movedObject.uuid];
+ const positionArray: [number, number, number] = [
+ initialState.position.x,
+ initialState.position.y,
+ initialState.position.z
+ ];
+
+ updateAsset(movedObject.userData.modelUuid, {
+ position: positionArray,
+ rotation: [
+ initialState.rotation?.x || 0,
+ initialState.rotation?.y || 0,
+ initialState.rotation?.z || 0
+ ],
+ });
+
+ movedObject.position.copy(initialState.position);
+ if (initialState.rotation) {
+ movedObject.rotation.copy(initialState.rotation);
+ }
+ }
+ });
+ }, 0)
+ }, [movedObjects, initialStates, updateAsset]);
useFrame(() => {
- if (movedObjects.length > 0) {
- const intersectionPoint = new THREE.Vector3();
- raycaster.setFromCamera(pointer, camera);
- let point = raycaster.ray.intersectPlane(plane, intersectionPoint);
- const floorsGroup = scene.getObjectByName("Floors-Group") as Types.Group | null;
- const floorChildren = floorsGroup?.children ?? [];
- const floorIntersections = raycaster.intersectObjects([...floorChildren], true);
- const intersectedFloor = floorIntersections.find((intersect) => intersect.object.name.includes("Floor"));
+ if (!isMoving || movedObjects.length === 0) return;
- if (intersectedFloor && selectedAssets.length === 1) {
- if (intersectedFloor.object.userData.floorUuid) {
- point = new THREE.Vector3(intersectedFloor.point.x, intersectedFloor.object.userData.floorDepth, intersectedFloor.point.z);
+ const intersectionPoint = new THREE.Vector3();
+ raycaster.setFromCamera(pointer, camera);
+ const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
+
+ if (hit) {
+ if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
+ if (movedObjects[0]) {
+ const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint);
+ setDragOffset(newOffset);
}
+ return;
}
- if (point) {
- let targetX = point.x;
- let targetY = point.y;
- let targetZ = point.z;
+ if (dragOffset) {
+ const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
- if (keyEvent === "Ctrl") {
- targetX = snapControls(targetX, "Ctrl");
- targetZ = snapControls(targetZ, "Ctrl");
+ let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1;
+
+ const initialBasePosition = initialPositions[movedObjects[0].uuid];
+ const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition);
+
+ let adjustedDifference = positionDifference.multiplyScalar(moveDistance);
+
+ const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference);
+
+ if (keyEvent.includes("Ctrl")) {
+ baseNewPosition.x = snapControls(baseNewPosition.x, keyEvent);
+ baseNewPosition.z = snapControls(baseNewPosition.z, keyEvent);
}
- // else if (keyEvent === "Ctrl+Shift") {
- // targetX = snapControls(targetX, "Ctrl+Shift");
- // targetZ = snapControls(targetZ, "Ctrl+Shift");
- // } else if (keyEvent === "Shift") {
- // targetX = snapControls(targetX, "Shift");
- // targetZ = snapControls(targetZ, "Shift");
- // } else {
- // }
+ movedObjects.forEach((movedAsset: THREE.Object3D) => {
+ if (movedAsset.userData.modelUuid) {
+ const initialPosition = initialPositions[movedAsset.userData.modelUuid];
+
+ if (initialPosition) {
+ const relativeOffset = new THREE.Vector3().subVectors(
+ initialPosition,
+ initialPositions[movedObjects[0].uuid]
+ );
+ const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
+
+ const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset);
+ const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
+
+ if (model) {
+ model.position.set(...positionArray);
+ }
+ }
+ }
+ });
const position = new THREE.Vector3();
-
if (boundingBoxRef.current) {
boundingBoxRef.current.getWorldPosition(position);
- selectionGroup.current.position.lerp(
- new THREE.Vector3(
- targetX - (position.x - selectionGroup.current.position.x),
- targetY - (position.y - selectionGroup.current.position.y),
- targetZ - (position.z - selectionGroup.current.position.z)
- ),
- moveSpeed
- );
} else {
const box = new THREE.Box3();
- movedObjects.forEach((obj: THREE.Object3D) =>
- box.expandByObject(obj)
- );
+ movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
-
- selectionGroup.current.position.lerp(
- new THREE.Vector3(
- targetX - (center.x - selectionGroup.current.position.x),
- selectionGroup.current.position.y,
- targetZ - (center.z - selectionGroup.current.position.z)
- ),
- moveSpeed
- );
}
}
}
});
const moveAssets = () => {
- setMovedObjects(selectedAssets);
- selectedAssets.forEach((asset: any) => {
- selectionGroup.current.attach(asset);
+ const states: Record = {};
+ const positions: Record = {};
+
+ selectedAssets.forEach((asset: THREE.Object3D) => {
+ states[asset.uuid] = {
+ position: new THREE.Vector3().copy(asset.position),
+ rotation: asset.rotation ? new THREE.Euler().copy(asset.rotation) : undefined
+ };
+ positions[asset.uuid] = new THREE.Vector3().copy(asset.position);
});
+
+ setInitialStates(states);
+ setInitialPositions(positions);
+
+ raycaster.setFromCamera(pointer, camera);
+ const intersectionPoint = new THREE.Vector3();
+ const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
+
+ if (hit && selectedAssets[0]) {
+ const offset = calculateDragOffset(selectedAssets[0], hit);
+ setDragOffset(offset);
+ }
+
+ setMovedObjects(selectedAssets);
+ setIsMoving(true);
};
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;
- movedObjects.forEach(async (obj: THREE.Object3D) => {
- if (obj && AssetGroup.current) {
- let worldPosition = new THREE.Vector3();
- obj.getWorldPosition(worldPosition);
-
- if (worldPosition.y < 0) {
- worldPosition.y = 0;
- }
-
- selectionGroup.current.remove(obj);
- obj.position.copy(worldPosition);
+ movedObjects.forEach(async (movedAsset: THREE.Object3D) => {
+ if (movedAsset) {
+ const assetUuid = movedAsset.userData.modelUuid;
+ const asset = getAssetById(assetUuid);
+ const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
+ if (!asset || !model) return;
+ const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
- modelUuid: obj.userData.modelUuid,
- modelName: obj.userData.modelName,
- assetId: obj.userData.assetId,
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
+ modelUuid: movedAsset.userData.modelUuid,
+ modelName: movedAsset.userData.modelName,
+ assetId: movedAsset.userData.assetId,
+ position: [position.x, position.y, position.z],
+ rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z },
isLocked: false,
isVisible: true,
};
- if (obj.userData.eventData) {
- const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid);
- const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, obj.userData.modelUuid);
+ if (movedAsset.userData.eventData) {
+ const eventData = eventStore.getState().getEventByModelUuid(movedAsset.userData.modelUuid);
+ const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, movedAsset.userData.modelUuid);
if (eventData) {
- eventStore.getState().updateEvent(obj.userData.modelUuid, {
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
+ eventStore.getState().updateEvent(movedAsset.userData.modelUuid, {
+ position: [position.x, position.y, position.z],
+ rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z],
});
}
@@ -278,10 +318,10 @@ function MoveControls3D({
.getState()
.updateEvent(
selectedProduct.productUuid,
- obj.userData.modelUuid,
+ movedAsset.userData.modelUuid,
{
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
+ position: [position.x, position.y, position.z],
+ rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z],
}
);
@@ -298,33 +338,18 @@ function MoveControls3D({
}
}
- updateAsset(obj.userData.modelUuid, {
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
+ updateAsset(movedAsset.userData.modelUuid, {
+ position: asset.position,
+ rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z],
});
- //REST
-
- // await setAssetsApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
-
- //SOCKET
-
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
- rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
+ rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
@@ -333,22 +358,22 @@ function MoveControls3D({
userId
};
- // console.log('data: ', data);
- socket.emit("v1:model-asset:add", data);
+ //REST
- AssetGroup.current.add(obj);
+ // await setAssetsApi(data);
+
+ //SOCKET
+
+ socket.emit("v1:model-asset:add", data);
}
});
echo.success("Object moved!");
-
+ setIsMoving(false);
clearSelection();
};
const clearSelection = () => {
- selectionGroup.current.children = [];
- selectionGroup.current.position.set(0, 0, 0);
- selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
@@ -365,4 +390,4 @@ function MoveControls3D({
);
}
-export default MoveControls3D;
+export default MoveControls3D;
\ No newline at end of file
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx
index fdde56a..0ca114d 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx
@@ -1,5 +1,5 @@
import * as THREE from "three";
-import { useEffect, useMemo, useRef } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
@@ -20,11 +20,9 @@ function RotateControls3D({
pastedObjects,
setpastedObjects,
duplicatedObjects,
- setDuplicatedObjects,
- selectionGroup
+ setDuplicatedObjects
}: any) {
-
- const { camera, controls, gl, scene, pointer, raycaster } = useThree();
+ const { camera, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
@@ -36,26 +34,33 @@ function RotateControls3D({
const { projectId } = useParams();
const { assetStore, eventStore, productStore } = useSceneContext();
const { updateAsset } = assetStore();
- const AssetGroup = useRef(undefined);
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
- const updateBackend = (
+ const [initialRotations, setInitialRotations] = useState>({});
+ const [initialPositions, setInitialPositions] = useState>({});
+ const [isRotating, setIsRotating] = useState(false);
+ const prevPointerPosition = useRef(null);
+ const rotationCenter = useRef(null);
+ const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
+ left: false,
+ right: false,
+ });
+
+ const updateBackend = useCallback((
productName: string,
productUuid: string,
projectId: string,
eventData: EventsSchema
) => {
upsertProductOrEventApi({
- productName: productName,
- productUuid: productUuid,
- projectId: projectId,
+ productName,
+ productUuid,
+ projectId,
eventDatas: eventData,
versionId: selectedVersion?.versionId || '',
- })
- }
-
- const prevPointerPosition = useRef(null);
+ });
+ }, [selectedVersion]);
useEffect(() => {
if (!camera || !scene || toggleView) return;
@@ -63,45 +68,37 @@ function RotateControls3D({
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
- const itemsGroup: any = scene.getObjectByName("Asset Group");
- AssetGroup.current = itemsGroup;
-
- if (!AssetGroup.current) {
- console.error("Asset Group not found in the scene.");
- return;
- }
-
- let isMoving = false;
-
- const onPointerDown = () => {
- isMoving = false;
- };
+ let isPointerMoving = false;
const onPointerMove = () => {
- isMoving = true;
+ isPointerMoving = true;
+ };
+
+ const onPointerDown = (event: PointerEvent) => {
+ isPointerMoving = false;
+ if (event.button === 0) mouseButtonsDown.current.left = true;
+ if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerUp = (event: PointerEvent) => {
- if (!isMoving && rotatedObjects.length > 0 && event.button === 0) {
+ if (event.button === 0) mouseButtonsDown.current.left = false;
+ if (event.button === 2) mouseButtonsDown.current.right = false;
+
+ if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeRotatedAssets();
}
- if (!isMoving && rotatedObjects.length > 0 && event.button === 2) {
+ if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 2) {
event.preventDefault();
-
+ resetToInitialRotations();
clearSelection();
- rotatedObjects.forEach((asset: any) => {
- if (AssetGroup.current) {
- AssetGroup.current.attach(asset);
- }
- });
-
setRotatedObjects([]);
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return;
+
if (event.key.toLowerCase() === "r") {
if (selectedAssets.length > 0) {
rotateAssets();
@@ -109,15 +106,8 @@ function RotateControls3D({
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
-
+ resetToInitialRotations();
clearSelection();
- rotatedObjects.forEach((asset: any) => {
- if (AssetGroup.current) {
- AssetGroup.current.attach(asset);
- }
- });
-
-
setRotatedObjects([]);
}
};
@@ -135,41 +125,87 @@ function RotateControls3D({
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
- }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, rotatedObjects, movedObjects]);
+ }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]);
+
+ const resetToInitialRotations = useCallback(() => {
+ rotatedObjects.forEach((obj: THREE.Object3D) => {
+ const uuid = obj.uuid;
+ if (obj.userData.modelUuid) {
+ const initialRotation = initialRotations[uuid];
+ const initialPosition = initialPositions[uuid];
+
+ if (initialRotation && initialPosition) {
+ const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,];
+ const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,];
+
+ updateAsset(obj.userData.modelUuid, {
+ rotation: rotationArray,
+ position: positionArray,
+ });
+
+ obj.rotation.copy(initialRotation);
+ obj.position.copy(initialPosition);
+ }
+ }
+ });
+ }, [rotatedObjects, initialRotations, initialPositions, updateAsset]);
useFrame(() => {
- if (rotatedObjects.length > 0) {
- const intersectionPoint = new THREE.Vector3();
- raycaster.setFromCamera(pointer, camera);
- const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
+ if (!isRotating || rotatedObjects.length === 0) return;
- if (point && prevPointerPosition.current) {
- const box = new THREE.Box3();
- rotatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
- const center = new THREE.Vector3();
- box.getCenter(center);
-
- const delta = new THREE.Vector3().subVectors(point, center);
- const prevPointerPosition3D = new THREE.Vector3(prevPointerPosition.current.x, 0, prevPointerPosition.current.y);
-
- const angle = Math.atan2(delta.z, delta.x) - Math.atan2(prevPointerPosition3D.z - center.z, prevPointerPosition3D.x - center.x);
-
- selectionGroup.current.rotation.y += -angle;
-
- selectionGroup.current.position.sub(center);
- selectionGroup.current.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
- selectionGroup.current.position.add(center);
+ const intersectionPoint = new THREE.Vector3();
+ raycaster.setFromCamera(pointer, camera);
+ const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
+ if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
+ if (point) {
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
+ return;
+ }
+
+ if (point && prevPointerPosition.current && rotationCenter.current) {
+ const center = rotationCenter.current;
+
+ const currentAngle = Math.atan2(point.z - center.z, point.x - center.x);
+ const prevAngle = Math.atan2(
+ prevPointerPosition.current.y - center.z,
+ prevPointerPosition.current.x - center.x
+ );
+ const angleDelta = prevAngle - currentAngle;
+
+ const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
+
+ rotatedObjects.forEach((obj: THREE.Object3D) => {
+ if (obj.userData.modelUuid) {
+ const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
+ relativePosition.applyMatrix4(rotationMatrix);
+ obj.position.copy(center).add(relativePosition);
+ obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta);
+ }
+ });
+
+ prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
});
- const rotateAssets = () => {
+ const rotateAssets = useCallback(() => {
+ const rotations: Record = {};
+ const positions: Record = {};
+
const box = new THREE.Box3();
- selectedAssets.forEach((asset: any) => box.expandByObject(asset));
+ selectedAssets.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
+ rotationCenter.current = center;
+
+ selectedAssets.forEach((obj: THREE.Object3D) => {
+ rotations[obj.uuid] = new THREE.Euler().copy(obj.rotation);
+ positions[obj.uuid] = new THREE.Vector3().copy(obj.position);
+ });
+
+ setInitialRotations(rotations);
+ setInitialPositions(positions);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
@@ -179,56 +215,54 @@ function RotateControls3D({
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
- selectedAssets.forEach((asset: any) => {
- selectionGroup.current.attach(asset);
- });
-
setRotatedObjects(selectedAssets);
- };
+ setIsRotating(true);
+ }, [selectedAssets, camera, pointer, raycaster, plane]);
- const placeRotatedAssets = () => {
+ const placeRotatedAssets = useCallback(() => {
if (rotatedObjects.length === 0) return;
- rotatedObjects.forEach(async (obj: THREE.Object3D) => {
- if (obj && AssetGroup.current) {
- const worldPosition = new THREE.Vector3();
- const worldQuaternion = new THREE.Quaternion();
+ rotatedObjects.forEach((obj: THREE.Object3D) => {
+ if (obj && obj.userData.modelUuid) {
+ const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
- obj.getWorldPosition(worldPosition);
- obj.getWorldQuaternion(worldQuaternion);
-
- selectionGroup.current.remove(obj);
-
- obj.position.copy(worldPosition);
- obj.quaternion.copy(worldQuaternion);
+ const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z];
const newFloorItem: Types.FloorItemType = {
modelUuid: obj.userData.modelUuid,
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
+ position: positionArray,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
- isVisible: true
+ isVisible: true,
};
if (obj.userData.eventData) {
const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid);
- const productData = productStore.getState().getEventByModelUuid(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid);
+ const productData = productStore.getState().getEventByModelUuid(
+ selectedProduct.productUuid,
+ obj.userData.modelUuid
+ );
if (eventData) {
eventStore.getState().updateEvent(obj.userData.modelUuid, {
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
- })
+ position: positionArray,
+ rotation: rotationArray,
+ });
}
- if (productData) {
- const event = productStore.getState().updateEvent(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid, {
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
- })
- if (event && organization) {
+ if (productData) {
+ const event = productStore.getState().updateEvent(
+ selectedProduct.productUuid,
+ obj.userData.modelUuid,
+ {
+ position: positionArray,
+ rotation: rotationArray,
+ }
+ );
+
+ if (event) {
updateBackend(
selectedProduct.productName,
selectedProduct.productUuid,
@@ -242,25 +276,10 @@ function RotateControls3D({
}
updateAsset(obj.userData.modelUuid, {
- position: [worldPosition.x, worldPosition.y, worldPosition.z],
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
+ position: positionArray,
+ rotation: rotationArray,
});
- //REST
-
- // await setAssetsApi(
- // organization,
- // obj.uuid,
- // obj.userData.name,
- // [worldPosition.x, worldPosition.y, worldPosition.z],
- // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
- // obj.userData.modelId,
- // false,
- // true,
- // );
-
- //SOCKET
-
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
@@ -276,29 +295,29 @@ function RotateControls3D({
userId
};
- // console.log('data: ', data);
- socket.emit("v1:model-asset:add", data);
+ //REST
- AssetGroup.current.add(obj);
+ // await setAssetsApi(data);
+
+ //SOCKET
+
+ socket.emit("v1:model-asset:add", data);
}
});
- echo.success("Object rotated!");
+ setIsRotating(false);
clearSelection();
- }
+ }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
const clearSelection = () => {
- selectionGroup.current.children = [];
- selectionGroup.current.position.set(0, 0, 0);
- selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
- }
+ };
return null;
}
-export default RotateControls3D
\ No newline at end of file
+export default RotateControls3D;
\ No newline at end of file
diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx
index 080a5d8..eb8bdae 100644
--- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx
+++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx
@@ -1,9 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import * as THREE from "three";
-import { useFrame, useThree } from "@react-three/fiber";
+import { useThree } from "@react-three/fiber";
import { SelectionHelper } from "../selectionHelper";
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
-import * as Types from "../../../../../types/world/worldTypes";
import useModuleStore from "../../../../../store/useModuleStore";
import { useParams } from "react-router-dom";
@@ -13,7 +12,6 @@ import { useVersionContext } from "../../../../builder/version/versionContext";
import { useProductContext } from "../../../../simulation/products/productContext";
import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
-import BoundingBox from "./boundingBoxHelper3D";
import DuplicationControls3D from "./duplicationControls3D";
import CopyPasteControls3D from "./copyPasteControls3D";
import MoveControls3D from "./moveControls3D";
@@ -23,7 +21,6 @@ import RotateControls3D from "./rotateControls3D";
const SelectionControls3D: React.FC = () => {
const { camera, controls, gl, scene, raycaster, pointer } = useThree();
- const selectionGroup = useRef() as Types.RefGroup;
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const [movedObjects, setMovedObjects] = useState([]);
@@ -229,12 +226,6 @@ const SelectionControls3D: React.FC = () => {
}
}, [activeModule, toolMode, toggleView]);
- useFrame(() => {
- if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
- selectionGroup.current.position.set(0, 0, 0);
- }
- });
-
const selectAssets = useCallback(() => {
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
if (controls) (controls as any).enabled = true;
@@ -267,9 +258,6 @@ const SelectionControls3D: React.FC = () => {
}, [selectionBox, pointer, controls, selectedAssets, setSelectedAssets]);
const clearSelection = () => {
- selectionGroup.current.children = [];
- selectionGroup.current.position.set(0, 0, 0);
- selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setSelectedAssets([]);
@@ -345,19 +333,13 @@ const SelectionControls3D: React.FC = () => {
return (
<>
-
-
-
-
-
+
-
+
-
+
-
-
-
+
>
);
};
diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts
new file mode 100644
index 0000000..e5f31a5
--- /dev/null
+++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts
@@ -0,0 +1,355 @@
+import { useParams } from "react-router-dom";
+import { getUserData } from "../../../../../functions/getUserData";
+import { useVersionContext } from "../../../../builder/version/versionContext";
+import { useSceneContext } from "../../../sceneContext";
+import { useSocketStore } from "../../../../../store/builder/store";
+
+// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
+// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
+
+// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
+// import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi";
+
+// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
+// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi";
+
+// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
+// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
+
+function useRedoHandler() {
+ const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
+ const { redo2D, peekRedo2D } = undoRedo2DStore();
+ const { addWall, removeWall, updateWall } = wallStore();
+ const { addFloor, removeFloor, updateFloor } = floorStore();
+ const { addZone, removeZone, updateZone } = zoneStore();
+ const { addAisle, removeAisle, updateAisle } = aisleStore();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { userId, organization } = getUserData();
+ const { projectId } = useParams();
+ const { socket } = useSocketStore();
+
+ const handleRedo = () => {
+ const redoData = peekRedo2D();
+ if (!redoData) return;
+
+ if (redoData.type === 'Draw') {
+ const { actions } = redoData;
+
+ actions.forEach(action => {
+ const { actionType } = action;
+
+ if ('point' in action) {
+ const point = action.point;
+
+ if (actionType === 'Line-Create') {
+ handleCreate(point);
+ } else if (actionType === 'Line-Update') {
+ handleUpdate(point);
+ } else if (actionType === 'Line-Delete') {
+ handleRemove(point);
+ }
+
+ } else if ('points' in action) {
+ const points = action.points;
+
+ if (actionType === 'Lines-Create') {
+ points.forEach(handleCreate);
+ } else if (actionType === 'Lines-Update') {
+ points.forEach(handleUpdate);
+ } else if (actionType === 'Lines-Delete') {
+ points.forEach(handleRemove);
+ }
+ }
+ });
+ } else if (redoData.type === 'UI') {
+ // Handle UI actions if needed
+ }
+
+ redo2D();
+ };
+
+ const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
+ switch (point.type) {
+ case 'Wall': createWallFromBackend(point.lineData); break;
+ case 'Floor': createFloorFromBackend(point.lineData); break;
+ case 'Zone': createZoneFromBackend(point.lineData); break;
+ case 'Aisle': createAisleFromBackend(point.lineData); break;
+ }
+ };
+
+ const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
+ switch (point.type) {
+ case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break;
+ case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break;
+ case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break;
+ case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break;
+ }
+ };
+
+ const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
+ if (!point.newData) return;
+ switch (point.type) {
+ case 'Wall': updateWallFromBackend(point.newData.wallUuid, point.newData); break;
+ case 'Floor': updateFloorFromBackend(point.newData.floorUuid, point.newData); break;
+ case 'Zone': updateZoneFromBackend(point.newData.zoneUuid, point.newData); break;
+ case 'Aisle': updateAisleFromBackend(point.newData.aisleUuid, point.newData); break;
+ }
+ };
+
+ const createWallFromBackend = (wallData: Wall) => {
+ addWall(wallData);
+ if (projectId) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', wallData);
+
+ // SOCKET
+
+ const data = {
+ wallData: wallData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ };
+
+ const removeWallFromBackend = (wallUuid: string) => {
+ removeWall(wallUuid);
+ if (projectId) {
+ // API
+
+ // deleteWallApi(projectId, selectedVersion?.versionId || '', wallUuid);
+
+ // SOCKET
+
+ const data = {
+ wallUuid: wallUuid,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:delete', data);
+ }
+ };
+
+ const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => {
+ updateWall(wallUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ wallData: updatedData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ };
+
+ const createFloorFromBackend = (floorData: Floor) => {
+ addFloor(floorData);
+ if (projectId) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', floorData);
+
+ // SOCKET
+
+ const data = {
+ floorData: floorData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ };
+
+ const removeFloorFromBackend = (floorUuid: string) => {
+ removeFloor(floorUuid);
+ if (projectId) {
+ // API
+
+ // deleteFloorApi(projectId, selectedVersion?.versionId || '', floorUuid);
+
+ // SOCKET
+
+ const data = {
+ floorUuid: floorUuid,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:delete', data);
+ }
+ };
+
+ const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => {
+ updateFloor(floorUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ floorData: updatedData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ };
+
+ const createZoneFromBackend = (zoneData: Zone) => {
+ addZone(zoneData);
+ if (projectId) {
+ // API
+
+ // upsertZoneApi(projectId, selectedVersion?.versionId || '', zoneData);
+
+ // SOCKET
+
+ const data = {
+ zoneData: zoneData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:zone:add', data);
+ }
+ };
+
+ const removeZoneFromBackend = (zoneUuid: string) => {
+ removeZone(zoneUuid);
+ if (projectId) {
+ // API
+
+ // deleteZoneApi(projectId, selectedVersion?.versionId || '', zoneUuid);
+
+ // SOCKET
+
+ const data = {
+ zoneUuid,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:zone:delete', data);
+ }
+ };
+
+ const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => {
+ updateZone(zoneUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ zoneData: updatedData,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:zone:add', data);
+ }
+ };
+
+ const createAisleFromBackend = (aisleData: Aisle) => {
+ addAisle(aisleData);
+ if (projectId) {
+ // API
+
+ // upsertAisleApi(projectId, selectedVersion?.versionId || '', aisleData);
+
+ // SOCKET
+
+ const data = {
+ aisleData,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:model-aisle:add', data);
+ }
+ };
+
+ const removeAisleFromBackend = (aisleUuid: string) => {
+ removeAisle(aisleUuid);
+ if (projectId) {
+ // API
+
+ // deleteAisleApi(projectId, selectedVersion?.versionId || '', aisleUuid);
+
+ // SOCKET
+
+ const data = {
+ aisleUuid,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:model-aisle:delete', data);
+ }
+ };
+
+ const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => {
+ updateAisle(aisleUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertAisleApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ aisleData: updatedData,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:model-aisle:add', data);
+ }
+ };
+
+ return { handleRedo };
+}
+
+export default useRedoHandler;
diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts
new file mode 100644
index 0000000..357f36e
--- /dev/null
+++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts
@@ -0,0 +1,356 @@
+import { useParams } from "react-router-dom";
+import { getUserData } from "../../../../../functions/getUserData";
+import { useVersionContext } from "../../../../builder/version/versionContext";
+import { useSceneContext } from "../../../sceneContext";
+import { useSocketStore } from "../../../../../store/builder/store";
+
+// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
+// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
+
+// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
+// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
+
+// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
+// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi";
+
+// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
+// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
+
+function useUndoHandler() {
+ const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
+ const { undo2D, peekUndo2D } = undoRedo2DStore();
+ const { addWall, removeWall, updateWall } = wallStore();
+ const { addFloor, removeFloor, updateFloor } = floorStore();
+ const { addZone, removeZone, updateZone } = zoneStore();
+ const { addAisle, removeAisle, updateAisle } = aisleStore();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+ const { userId, organization } = getUserData();
+ const { projectId } = useParams();
+ const { socket } = useSocketStore();
+
+ const handleUndo = () => {
+ const unDoData = peekUndo2D();
+ if (!unDoData) return;
+
+ if (unDoData.type === 'Draw') {
+ const { actions } = unDoData;
+
+ actions.forEach(action => {
+ const { actionType } = action;
+
+ if ('point' in action) {
+ const point = action.point;
+
+ if (actionType === 'Line-Create') {
+ handleRemove(point);
+ } else if (actionType === 'Line-Update') {
+ handleUpdate(point);
+ } else if (actionType === 'Line-Delete') {
+ handleCreate(point);
+ }
+
+ } else if ('points' in action) {
+ const points = action.points;
+
+ if (actionType === 'Lines-Create') {
+ points.forEach(handleRemove);
+ } else if (actionType === 'Lines-Update') {
+ points.forEach(handleUpdate);
+ } else if (actionType === 'Lines-Delete') {
+ points.forEach(handleCreate);
+ }
+ }
+ });
+ } else if (unDoData.type === 'UI') {
+ // Handle UI actions if needed
+ }
+
+ undo2D();
+
+ };
+
+ const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
+ switch (point.type) {
+ case 'Wall': createWallFromBackend(point.lineData); break;
+ case 'Floor': createFloorFromBackend(point.lineData); break;
+ case 'Zone': createZoneFromBackend(point.lineData); break;
+ case 'Aisle': createAisleFromBackend(point.lineData); break;
+ }
+ };
+
+ const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
+ switch (point.type) {
+ case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break;
+ case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break;
+ case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break;
+ case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break;
+ }
+ };
+
+ const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
+ switch (point.type) {
+ case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break;
+ case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break;
+ case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break;
+ case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break;
+ }
+ };
+
+
+ const createWallFromBackend = (wallData: Wall) => {
+ addWall(wallData);
+ if (projectId) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', wallData);
+
+ // SOCKET
+
+ const data = {
+ wallData: wallData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ };
+
+ const removeWallFromBackend = (wallUuid: string) => {
+ removeWall(wallUuid);
+ if (projectId) {
+ // API
+
+ // deleteWallApi(projectId, selectedVersion?.versionId || '', wallUuid);
+
+ // SOCKET
+
+ const data = {
+ wallUuid: wallUuid,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:delete', data);
+ }
+ };
+
+ const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => {
+ updateWall(wallUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ wallData: updatedData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Wall:add', data);
+ }
+ };
+
+ const createFloorFromBackend = (floorData: Floor) => {
+ addFloor(floorData);
+ if (projectId) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', floorData);
+
+ // SOCKET
+
+ const data = {
+ floorData: floorData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ };
+
+ const removeFloorFromBackend = (floorUuid: string) => {
+ removeFloor(floorUuid);
+ if (projectId) {
+ // API
+
+ // deleteFloorApi(projectId, selectedVersion?.versionId || '', floorUuid);
+
+ // SOCKET
+
+ const data = {
+ floorUuid: floorUuid,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:delete', data);
+ }
+ };
+
+ const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => {
+ updateFloor(floorUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ floorData: updatedData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:model-Floor:add', data);
+ }
+ };
+
+ const createZoneFromBackend = (zoneData: Zone) => {
+ addZone(zoneData);
+ if (projectId) {
+ // API
+
+ // upsertZoneApi(projectId, selectedVersion?.versionId || '', zoneData);
+
+ // SOCKET
+
+ const data = {
+ zoneData: zoneData,
+ projectId: projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId: userId,
+ organization: organization
+ }
+
+ socket.emit('v1:zone:add', data);
+ }
+ };
+
+ const removeZoneFromBackend = (zoneUuid: string) => {
+ removeZone(zoneUuid);
+ if (projectId) {
+ // API
+
+ // deleteZoneApi(projectId, selectedVersion?.versionId || '', zoneUuid);
+
+ // SOCKET
+
+ const data = {
+ zoneUuid,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:zone:delete', data);
+ }
+ };
+
+ const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => {
+ updateZone(zoneUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ zoneData: updatedData,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:zone:add', data);
+ }
+ };
+
+ const createAisleFromBackend = (aisleData: Aisle) => {
+ addAisle(aisleData);
+ if (projectId) {
+ // API
+
+ // upsertAisleApi(projectId, selectedVersion?.versionId || '', aisleData);
+
+ // SOCKET
+
+ const data = {
+ aisleData,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:model-aisle:add', data);
+ }
+ };
+
+ const removeAisleFromBackend = (aisleUuid: string) => {
+ removeAisle(aisleUuid);
+ if (projectId) {
+ // API
+
+ // deleteAisleApi(projectId, selectedVersion?.versionId || '', aisleUuid);
+
+ // SOCKET
+
+ const data = {
+ aisleUuid,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:model-aisle:delete', data);
+ }
+ };
+
+ const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => {
+ updateAisle(aisleUuid, updatedData);
+ if (projectId) {
+ // API
+
+ // upsertAisleApi(projectId, selectedVersion?.versionId || '', updatedData);
+
+ // SOCKET
+
+ const data = {
+ aisleData: updatedData,
+ projectId,
+ versionId: selectedVersion?.versionId || '',
+ userId,
+ organization
+ };
+
+ socket.emit('v1:model-aisle:add', data);
+ }
+ };
+
+ return { handleUndo };
+}
+
+export default useUndoHandler;
\ No newline at end of file
diff --git a/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx
new file mode 100644
index 0000000..d45518c
--- /dev/null
+++ b/app/src/modules/scene/controls/undoRedoControls/undoRedo2D/undoRedo2DControls.tsx
@@ -0,0 +1,49 @@
+import { useEffect } from 'react'
+import { useSceneContext } from '../../../sceneContext'
+import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
+import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
+import { useVersionContext } from '../../../../builder/version/versionContext';
+
+import useUndoHandler from '../handlers/useUndoHandler';
+import useRedoHandler from '../handlers/useRedoHandler';
+
+function UndoRedo2DControls() {
+ const { undoRedo2DStore } = useSceneContext();
+ const { undoStack, redoStack } = undoRedo2DStore();
+ const { toggleView } = useToggleView();
+ const { handleUndo } = useUndoHandler();
+ const { handleRedo } = useRedoHandler();
+ const { socket } = useSocketStore();
+ const { selectedVersionStore } = useVersionContext();
+ const { selectedVersion } = selectedVersionStore();
+
+ useEffect(() => {
+ console.log(undoStack, redoStack);
+ }, [undoStack, redoStack]);
+
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ const keyCombination = detectModifierKeys(event);
+
+ if (keyCombination === 'Ctrl+Z') {
+ handleUndo();
+ }
+
+ if (keyCombination === 'Ctrl+Y') {
+ handleRedo();
+ }
+ };
+
+ if (toggleView) {
+ window.addEventListener('keydown', handleKeyDown);
+ }
+
+ return () => {
+ window.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [toggleView, undoStack, redoStack, socket, selectedVersion]);
+
+ return null;
+}
+
+export default UndoRedo2DControls;
diff --git a/app/src/modules/scene/environment/shadow.tsx b/app/src/modules/scene/environment/shadow.tsx
index 3321673..03cf942 100644
--- a/app/src/modules/scene/environment/shadow.tsx
+++ b/app/src/modules/scene/environment/shadow.tsx
@@ -9,21 +9,17 @@ import {
useTileDistance,
} from "../../../store/builder/store";
import * as CONSTANTS from "../../../types/world/worldConstants";
-const shadowWorker = new Worker(
- new URL(
- "../../../services/factoryBuilder/webWorkers/shadowWorker",
- import.meta.url
- )
-);
+
+const shadowWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/shadowWorker", import.meta.url));
export default function Shadows() {
- const { shadows, setShadows } = useShadows();
- const { sunPosition, setSunPosition } = useSunPosition();
+ const { shadows } = useShadows();
+ const { sunPosition } = useSunPosition();
const lightRef = useRef(null);
const targetRef = useRef(null);
const { controls, gl } = useThree();
- const { elevation, setElevation } = useElevation();
- const { azimuth, setAzimuth } = useAzimuth();
+ const { elevation } = useElevation();
+ const { azimuth } = useAzimuth();
const { planeValue } = useTileDistance();
useEffect(() => {
diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx
index 67811c4..bf65c36 100644
--- a/app/src/modules/scene/sceneContext.tsx
+++ b/app/src/modules/scene/sceneContext.tsx
@@ -1,4 +1,4 @@
-import { createContext, useContext, useMemo } from 'react';
+import { createContext, useContext, useMemo, useRef } from 'react';
import { createAssetStore, AssetStoreType } from '../../store/builder/useAssetStore';
import { createWallAssetStore, WallAssetStoreType } from '../../store/builder/useWallAssetStore';
@@ -7,6 +7,8 @@ import { createAisleStore, AisleStoreType } from '../../store/builder/useAisleSt
import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore';
import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore';
+import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore';
+
import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore';
import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore';
@@ -27,6 +29,8 @@ type SceneContextValue = {
zoneStore: ZoneStoreType,
floorStore: FloorStoreType,
+ undoRedo2DStore: UndoRedo2DStoreType,
+
eventStore: EventStoreType,
productStore: ProductStoreType,
@@ -38,6 +42,8 @@ type SceneContextValue = {
storageUnitStore: StorageUnitStoreType;
humanStore: HumanStoreType;
+ humanEventManagerRef: React.RefObject;
+
clearStores: () => void;
layout: 'Main Layout' | 'Comparison Layout';
@@ -60,6 +66,8 @@ export function SceneProvider({
const zoneStore = useMemo(() => createZoneStore(), []);
const floorStore = useMemo(() => createFloorStore(), []);
+ const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []);
+
const eventStore = useMemo(() => createEventStore(), []);
const productStore = useMemo(() => createProductStore(), []);
@@ -71,6 +79,8 @@ export function SceneProvider({
const storageUnitStore = useMemo(() => createStorageUnitStore(), []);
const humanStore = useMemo(() => createHumanStore(), []);
+ const humanEventManagerRef = useRef({ humanStates: [] });
+
const clearStores = useMemo(() => () => {
assetStore.getState().clearAssets();
wallAssetStore.getState().clearWallAssets();
@@ -78,6 +88,7 @@ export function SceneProvider({
aisleStore.getState().clearAisles();
zoneStore.getState().clearZones();
floorStore.getState().clearFloors();
+ undoRedo2DStore.getState().clearUndoRedo2D();
eventStore.getState().clearEvents();
productStore.getState().clearProducts();
materialStore.getState().clearMaterials();
@@ -87,7 +98,8 @@ export function SceneProvider({
vehicleStore.getState().clearVehicles();
storageUnitStore.getState().clearStorageUnits();
humanStore.getState().clearHumans();
- }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]);
+ humanEventManagerRef.current.humanStates = [];
+ }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]);
const contextValue = useMemo(() => (
{
@@ -97,6 +109,7 @@ export function SceneProvider({
aisleStore,
zoneStore,
floorStore,
+ undoRedo2DStore,
eventStore,
productStore,
materialStore,
@@ -106,10 +119,11 @@ export function SceneProvider({
vehicleStore,
storageUnitStore,
humanStore,
+ humanEventManagerRef,
clearStores,
layout
}
- ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]);
+ ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]);
return (
diff --git a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts
index 600fae5..f2dc047 100644
--- a/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts
+++ b/app/src/modules/simulation/actions/conveyor/useConveyorActions.ts
@@ -37,7 +37,7 @@ export function useConveyorActions() {
switch (action.actionType) {
case 'default':
- handleDefaultAction(action);
+ handleDefaultAction(action, materialId);
break;
case 'spawn':
handleSpawnAction(action);
diff --git a/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts
index efefc39..67132e7 100644
--- a/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts
+++ b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts
@@ -8,7 +8,7 @@ export function useWorkerHandler() {
const { getModelUuidByActionUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
- const { incrementHumanLoad, incrementLoadCount, addCurrentMaterial, addCurrentAction } = humanStore();
+ const { incrementHumanLoad, addCurrentMaterial, addCurrentAction } = humanStore();
const workerLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
@@ -24,7 +24,6 @@ export function useWorkerHandler() {
if (!modelUuid) return;
incrementHumanLoad(modelUuid, 1);
- incrementLoadCount(modelUuid, 1);
addCurrentAction(modelUuid, action.actionUuid);
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts
index 79d6401..0373fd0 100644
--- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts
+++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts
@@ -3,25 +3,30 @@ import { useFrame } from "@react-three/fiber";
import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
+import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager";
export function useRetrieveHandler() {
- const { materialStore, armBotStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext();
+ const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, productStore, humanStore, assetStore } = useSceneContext();
const { selectedProductStore } = useProductContext();
const { addMaterial } = materialStore();
const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore();
const { getStorageUnitById, getLastMaterial, updateCurrentLoad, removeLastMaterial } = storageUnitStore();
const { getVehicleById, incrementVehicleLoad, addCurrentMaterial } = vehicleStore();
- const { getHumanById, incrementHumanLoad, incrementLoadCount, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
+ const { getHumanById, incrementHumanLoad, addCurrentMaterial: addCurrentMaterialToHuman } = humanStore();
const { getAssetById, setCurrentAnimation } = assetStore();
const { selectedProduct } = selectedProductStore();
const { getArmBotById, addCurrentAction } = armBotStore();
+ const { getMachineById } = machineStore();
const { isPlaying } = usePlayButtonStore();
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore();
+ const { addHumanToMonitor } = useHumanEventManager();
const [activeRetrievals, setActiveRetrievals] = useState