diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index ff28363..2b5df51 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -18,19 +18,22 @@ import { useVersionContext } from '../../../version/versionContext'; import { SkeletonUtils } from 'three-stdlib'; import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore'; import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; +import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; function Model({ asset }: { readonly asset: Asset }) { + const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const { camera, controls, gl } = useThree(); const { activeTool } = useActiveTool(); + const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); const { speed } = useAnimationPlaySpeed(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { removeAsset, setAnimations, resetAnimation, setAnimationComplete, setCurrentAnimation: setAnmationAnimation } = assetStore(); + const { removeAsset, setAnimations, resetAnimation, setAnimationComplete } = assetStore(); const { setTop } = useTopData(); const { setLeft } = useLeftData(); - const { getIsEventInProduct } = productStore(); + const { getIsEventInProduct, addPoint } = productStore(); const { getEventByModelUuid } = eventStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); @@ -40,21 +43,24 @@ function Model({ asset }: { readonly asset: Asset }) { 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 url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const [gltfScene, setGltfScene] = useState(null); const [boundingBox, setBoundingBox] = useState(null); const groupRef = useRef(null); - const { toolMode } = useToolMode(); - const { selectedVersionStore } = useVersionContext(); - const { selectedVersion } = selectedVersionStore(); - const { projectId } = useParams(); - const { userId, organization } = getUserData(); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); const [previousAnimation, setPreviousAnimation] = useState(null); + 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 updateBackend = ( productName: string, @@ -71,6 +77,16 @@ function Model({ asset }: { readonly asset: Asset }) { }); }; + useEffect(() => { + if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') { + getAssetIksApi(asset.assetId).then((data) => { + if (data.iks) { + setIkData(data.iks); + } + }) + } + }, [asset.modelUuid, ikData]) + useEffect(() => { setDeletableFloorItem(null); if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) { @@ -217,7 +233,8 @@ function Model({ asset }: { readonly asset: Asset }) { } }; - const handleClick = (asset: Asset) => { + const handleClick = (evt: ThreeEvent, asset: Asset) => { + if (leftDrag.current || toggleView) return; if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { //REST @@ -257,6 +274,40 @@ function Model({ asset }: { readonly asset: Asset }) { 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 = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + } + } + } } @@ -270,13 +321,14 @@ function Model({ asset }: { readonly asset: Asset }) { } }, [activeTool, activeModule, deletableFloorItem]); - const handlePointerOut = useCallback((asset: Asset) => { - if (activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { + 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; @@ -358,6 +410,50 @@ function Model({ asset }: { readonly asset: Asset }) { }; }, [asset.animationState?.current, asset.animationState?.isPlaying]); + useEffect(() => { + const canvasElement = gl.domElement; + + const onPointerDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = true; + leftDrag.current = false; + } + if (evt.button === 2) { + isRightMouseDown.current = true; + rightDrag.current = false; + } + }; + + const onPointerMove = () => { + if (isLeftMouseDown.current) { + leftDrag.current = true; + } + if (isRightMouseDown.current) { + rightDrag.current = true; + } + }; + + const onPointerUp = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = false; + } + if (evt.button === 2) { + isRightMouseDown.current = false; + } + }; + + canvasElement.addEventListener('pointerdown', onPointerDown); + canvasElement.addEventListener('pointermove', onPointerMove); + canvasElement.addEventListener('pointerup', onPointerUp); + + return () => { + canvasElement.removeEventListener('pointerdown', onPointerDown); + canvasElement.removeEventListener('pointermove', onPointerMove); + canvasElement.removeEventListener('pointerup', onPointerUp); + } + + }, [gl]) + return ( { + e.stopPropagation(); if (!toggleView) { - e.stopPropagation(); handleDblClick(asset); } }} onClick={(e) => { + e.stopPropagation(); if (!toggleView) { - e.stopPropagation(); - handleClick(asset); + handleClick(e, asset); } }} - onPointerEnter={(e) => { + onPointerOver={(e) => { + e.stopPropagation(); if (!toggleView) { - e.stopPropagation(); handlePointerOver(asset); } }} - onPointerOut={(e) => { + onPointerLeave={(e) => { + e.stopPropagation(); if (!toggleView) { - e.stopPropagation(); - handlePointerOut(asset); + handlePointerOut(e, asset); } }} onContextMenu={(e) => { @@ -404,7 +500,7 @@ function Model({ asset }: { readonly asset: Asset }) { ) )} - + ); } diff --git a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx index 8c72dcf..1a674c7 100644 --- a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx +++ b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useThree } from '@react-three/fiber'; +import { ThreeEvent, useThree } from '@react-three/fiber'; import { Base, Geometry, Subtraction } from '@react-three/csg'; import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; @@ -14,8 +14,8 @@ import { useVersionContext } from '../../../version/versionContext'; import { getUserData } from '../../../../../functions/getUserData'; import closestPointOnLineSegment from '../../../line/helpers/getClosestPointOnLineSegment'; -import { upsertWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi'; -import { deleteWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi'; +// import { upsertWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi'; +// import { deleteWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi'; function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -219,8 +219,8 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { } }, [activeTool, activeModule, deletableWallAsset]); - const handlePointerOut = useCallback((wallAsset: WallAsset) => { - if (activeTool === "delete" && deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) { + const handlePointerOut = useCallback((evt: ThreeEvent, wallAsset: WallAsset) => { + if (evt.intersections.length === 0 && activeTool === "delete" && deletableWallAsset && deletableWallAsset.userData.modelUuid === wallAsset.modelUuid) { setDeletableWallAsset(null); } }, [activeTool, deletableWallAsset]); @@ -281,7 +281,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { setSelectedWallAsset(null); } }} - onPointerEnter={(e) => { + onPointerOver={(e) => { if (!toggleView) { e.stopPropagation(); let currentObject = e.object as THREE.Object3D; @@ -294,10 +294,10 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { handlePointerOver(wallAsset, currentObject); } }} - onPointerOut={(e) => { + onPointerLeave={(e) => { if (!toggleView) { e.stopPropagation(); - handlePointerOut(wallAsset); + handlePointerOut(e, wallAsset); } }} userData={wallAsset} diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index 6c2f8d6..7c6c719 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -6,7 +6,7 @@ import { useSelectedFloorItem, } from "../../../store/builder/store"; import * as CONSTANTS from "../../../types/world/worldConstants"; -import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; +import { useDeletableEventSphere, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; import { useEffect } from "react"; import { useBuilderStore } from "../../../store/builder/useBuilderStore"; @@ -15,6 +15,7 @@ export default function PostProcessing() { const { selectedWallItem } = useSelectedWallItem(); const { selectedFloorItem } = useSelectedFloorItem(); const { selectedEventSphere } = useSelectedEventSphere(); + const { deletableEventSphere } = useDeletableEventSphere(); const { selectedAisle, selectedWall, selectedDecal, selectedFloor, selectedWallAsset, deletableWallAsset } = useBuilderStore(); function flattenChildren(children: any[]) { @@ -56,6 +57,10 @@ export default function PostProcessing() { // console.log('deletableWallAsset: ', deletableWallAsset); }, [deletableWallAsset]) + useEffect(() => { + // console.log('deletableEventSphere: ', deletableEventSphere); + }, [deletableEventSphere]) + return ( )} + {deletableEventSphere && ( + + )} ); } diff --git a/app/src/modules/simulation/events/arrows/arrows.tsx b/app/src/modules/simulation/events/arrows/arrows.tsx index 5b9d6fd..2053fe5 100644 --- a/app/src/modules/simulation/events/arrows/arrows.tsx +++ b/app/src/modules/simulation/events/arrows/arrows.tsx @@ -16,11 +16,11 @@ interface ConnectionLine { } export function Arrows({ connections }: { readonly connections: ConnectionLine[] }) { - const [hoveredLineKey, setHoveredLineKey] = useState(null); + const [hoveredArrowTrigger, setHoveredArrowTrigger] = useState(null); const groupRef = useRef(null); const { scene } = useThree(); const { toolMode } = useToolMode(); - const { eventStore, productStore } = useSceneContext(); + const { productStore } = useSceneContext(); const { removeTrigger } = productStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -103,15 +103,15 @@ export function Arrows({ connections }: { readonly connections: ConnectionLine[] return ( setHoveredLineKey(trigger.triggerUuid)} - onPointerOut={() => setHoveredLineKey(null)} + onPointerOver={() => setHoveredArrowTrigger(trigger.triggerUuid)} + onPointerOut={() => setHoveredArrowTrigger(null)} onClick={() => { removeConnection(trigger) }} > diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index 25e0b3d..9fb27a9 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -1,38 +1,46 @@ import { useEffect, useRef, useState } from "react"; import * as THREE from "three"; import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore"; +import { useToolMode } from "../../../../../store/builder/store"; import { TransformControls } from "@react-three/drei"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; -import { useSelectedEventSphere, useSelectedEventData, } from "../../../../../store/simulation/useSimulationStore"; +import { useSelectedEventSphere, useSelectedEventData, useDeletableEventSphere } from "../../../../../store/simulation/useSimulationStore"; import { useThree } from "@react-three/fiber"; import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; import { useProductContext } from "../../../products/productContext"; import { useParams } from "react-router-dom"; -import { useToolMode } from "../../../../../store/builder/store"; import { useVersionContext } from "../../../../builder/version/versionContext"; import { useSceneContext } from "../../../../scene/sceneContext"; +import PointInstances from "../instances/pointInstances"; + function PointsCreator() { const { gl, raycaster, scene, pointer, camera } = useThree(); const { subModule } = useSubModuleStore(); + const { toolMode } = useToolMode(); + const { activeModule } = useModuleStore(); const { selectedProductStore } = useProductContext(); const { eventStore, productStore } = useSceneContext(); - const { events, updatePoint, getPointByUuid, getEventByModelUuid } = eventStore(); - const { getEventByModelUuid: getEventByModelUuidFromProduct, updatePoint: updatePointFromProduct, getEventByModelUuid: getEventByModelUuidFromProduct2, getPointByUuid: getPointByUuidFromProduct } = productStore(); + const { getEventByModelUuid } = eventStore(); + const { getEventByModelUuid: getEventByModelUuidFromProduct, updatePoint: updatePointFromProduct, getPointByUuid: getPointByUuidFromProduct, getTriggersByTriggeredPointUuid, removeTrigger, removePoint } = productStore(); const { selectedProduct } = selectedProductStore(); - const { activeModule } = useModuleStore(); const transformRef = useRef(null); const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); - const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); - const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere(); + const { selectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere(); + const { clearDeletableEventSphere } = useDeletableEventSphere(); const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); const { isPlaying } = usePlayButtonStore(); - const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); + useEffect(() => { + clearSelectedEventSphere(); + clearSelectedEventData(); + clearDeletableEventSphere(); + }, [toolMode, activeModule]) + const updateBackend = ( productName: string, productUuid: string, @@ -75,65 +83,87 @@ function PointsCreator() { if (keyCombination === "R") { setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); } + if (keyCombination === 'DELETE') { + deletePointfromConveyor(selectedEventSphere); + } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [selectedEventSphere]); - const updatePointToState = (selectedEventSphere: THREE.Mesh) => { - let point: PointsScheme = JSON.parse( - JSON.stringify( - getPointByUuid( - selectedEventSphere.userData.modelUuid, - selectedEventSphere.userData.pointUuid - ) - ) - ); - if (point) { - point.position = [ - selectedEventSphere.position.x, - selectedEventSphere.position.y, - selectedEventSphere.position.z, - ]; - point.rotation = [ - selectedEventSphere.rotation.x, - selectedEventSphere.rotation.y, - selectedEventSphere.rotation.z, - ]; + const deletePointfromConveyor = (selectedEventSphere: THREE.Mesh) => { + const eventModel = getEventByModelUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid); + if (!eventModel || eventModel.type !== 'transfer' || eventModel.points.length < 2) return; - const event = getEventByModelUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid); + const triggers = getTriggersByTriggeredPointUuid(selectedProduct.productUuid, selectedEventSphere.userData.pointUuid); - if (event && selectedProduct.productUuid !== '') { + const updatedEvents: EventsSchema[] = []; + if (triggers.length > 0) { + triggers.map((trigger) => { + const event = removeTrigger(selectedProduct.productUuid, trigger.triggerUuid); + if (event) { + updatedEvents.push(event); + } + }) + } - const updatedPoint = JSON.parse( - JSON.stringify( - getPointByUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid) - ) + const event = removePoint(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid) + + if (event) { + updatedEvents.push(event); + } + + if (updatedEvents.length > 0) { + updatedEvents.map((updatedEvent) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + updatedEvent ); - if (updatedPoint) { - updatedPoint.position = point.position; - updatedPoint.rotation = point.rotation; + }) + } + } - const updatedEvent = updatePointFromProduct( + const updatePointToState = (selectedEventSphere: THREE.Mesh) => { + const position = [ + selectedEventSphere.position.x, + selectedEventSphere.position.y, + selectedEventSphere.position.z, + ]; + const rotation = [ + selectedEventSphere.rotation.x, + selectedEventSphere.rotation.y, + selectedEventSphere.rotation.z, + ]; + + const event = getEventByModelUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid); + + if (event && selectedProduct.productUuid !== '') { + + const updatedPoint = JSON.parse( + JSON.stringify( + getPointByUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid) + ) + ); + if (updatedPoint) { + updatedPoint.position = position; + updatedPoint.rotation = rotation; + + const updatedEvent = updatePointFromProduct( + selectedProduct.productUuid, + selectedEventSphere.userData.modelUuid, + selectedEventSphere.userData.pointUuid, + updatedPoint + ) + if (updatedEvent) { + updateBackend( + selectedProduct.productName, selectedProduct.productUuid, - selectedEventSphere.userData.modelUuid, - selectedEventSphere.userData.pointUuid, - updatedPoint - ) - if (updatedEvent) { - updatePoint( - selectedEventSphere.userData.modelUuid, - selectedEventSphere.userData.pointUuid, - point - ) - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - updatedEvent - ); - } + projectId || '', + updatedEvent + ); } } } @@ -153,9 +183,7 @@ function PointsCreator() { const onMouseUp = () => { if (selectedEventSphere && !drag) { raycaster.setFromCamera(pointer, camera); - const intersects = raycaster - .intersectObjects(scene.children, true) - .filter((intersect) => intersect.object.name === "Event-Sphere"); + const intersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.name === "Event-Sphere"); if (intersects.length === 0) { clearSelectedEventSphere(); setTransformMode(null); @@ -188,205 +216,7 @@ function PointsCreator() { {activeModule === "simulation" && ( <> - {events.map((event, index) => { - const updatedEvent = selectedProduct.productUuid !== '' - ? getEventByModelUuidFromProduct2(selectedProduct.productUuid, event.modelUuid) - : null; - - const usedEvent = updatedEvent || event; - - if (usedEvent.type === "transfer") { - return ( - - {usedEvent.points.map((point, j) => ( - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - if (toolMode === 'cursor') { - setSelectedEventSphere( - sphereRefs.current[point.uuid] - ); - } - }} - key={`${index}-${point.uuid}`} - position={new THREE.Vector3(...point.position)} - userData={{ - modelUuid: usedEvent.modelUuid, - pointUuid: point.uuid, - }} - > - - - - ))} - - ); - } else if (usedEvent.type === "vehicle") { - const point = usedEvent.point; - return ( - - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - if (toolMode === 'cursor') { - setSelectedEventSphere( - sphereRefs.current[point.uuid] - ); - } - }} - position={new THREE.Vector3(...point.position)} - userData={{ - modelUuid: usedEvent.modelUuid, - pointUuid: point.uuid, - }} - > - - - - - ); - } else if (usedEvent.type === "roboticArm") { - const point = usedEvent.point; - return ( - - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - if (toolMode === 'cursor') { - setSelectedEventSphere( - sphereRefs.current[point.uuid] - ); - } - }} - position={new THREE.Vector3(...point.position)} - userData={{ - modelUuid: usedEvent.modelUuid, - pointUuid: point.uuid, - }} - > - - - - - ); - } else if (usedEvent.type === "machine") { - const point = usedEvent.point; - return ( - - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - if (toolMode === 'cursor') { - setSelectedEventSphere( - sphereRefs.current[point.uuid] - ); - } - }} - position={new THREE.Vector3(...point.position)} - userData={{ - modelUuid: usedEvent.modelUuid, - pointUuid: point.uuid, - }} - > - - - - - ); - } else if (usedEvent.type === "storageUnit") { - const point = usedEvent.point; - return ( - - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - if (toolMode === 'cursor') { - setSelectedEventSphere( - sphereRefs.current[point.uuid] - ); - } - }} - position={new THREE.Vector3(...point.position)} - userData={{ - modelUuid: usedEvent.modelUuid, - pointUuid: point.uuid, - }} - > - - - - - ); - } else if (usedEvent.type === "human") { - const point = usedEvent.point; - return ( - - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - if (toolMode === 'cursor') { - setSelectedEventSphere( - sphereRefs.current[point.uuid] - ); - } - }} - position={new THREE.Vector3(...point.position)} - userData={{ - modelUuid: usedEvent.modelUuid, - pointUuid: point.uuid, - }} - > - - - - - ); - } else { - return null; - } - })} + {selectedEventSphere && transformMode && ( diff --git a/app/src/modules/simulation/events/points/instances/instance/pointInstance.tsx b/app/src/modules/simulation/events/points/instances/instance/pointInstance.tsx new file mode 100644 index 0000000..cfac4bb --- /dev/null +++ b/app/src/modules/simulation/events/points/instances/instance/pointInstance.tsx @@ -0,0 +1,121 @@ +import * as THREE from "three"; +import { MeshProps } from "@react-three/fiber"; +import { useRef } from "react"; +import { useToolMode } from "../../../../../../store/builder/store"; +import { useDeletableEventSphere, useSelectedEventSphere } from "../../../../../../store/simulation/useSimulationStore"; +import { useSceneContext } from "../../../../../scene/sceneContext"; +import { useProductContext } from "../../../../products/productContext"; +import { useVersionContext } from "../../../../../builder/version/versionContext"; +import { useParams } from "react-router-dom"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; + +interface PointInstanceProps extends Omit { + point: { + uuid: string; + position: [number, number, number]; + }; + modelUuid: string; + color: string; +} + +export default function PointInstance({ + point, + modelUuid, + color, + ...meshProps +}: PointInstanceProps) { + const ref = useRef(null); + const { setSelectedEventSphere } = useSelectedEventSphere(); + const { deletableEventSphere, setDeletableEventSphere, clearDeletableEventSphere } = useDeletableEventSphere(); + const { toolMode } = useToolMode(); + const { productStore } = useSceneContext(); + const { getEventByModelUuid, getTriggersByTriggeredPointUuid, removeTrigger, removePoint } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }) + } + + const handleEventPointDelete = () => { + const eventModel = getEventByModelUuid(selectedProduct.productUuid, modelUuid); + if (!eventModel || eventModel.type !== 'transfer' || eventModel.points.length < 2) return; + + const triggers = getTriggersByTriggeredPointUuid(selectedProduct.productUuid, point.uuid); + + const updatedEvents: EventsSchema[] = []; + if (triggers.length > 0) { + triggers.map((trigger) => { + const event = removeTrigger(selectedProduct.productUuid, trigger.triggerUuid); + if (event) { + updatedEvents.push(event); + } + }) + } + + const event = removePoint(selectedProduct.productUuid, modelUuid, point.uuid) + + if (event) { + updatedEvents.push(event); + } + + if (updatedEvents.length > 0) { + updatedEvents.map((updatedEvent) => { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + updatedEvent + ); + }) + } + } + + return ( + { + e.stopPropagation(); + if (toolMode === 'cursor') { + setSelectedEventSphere(ref.current); + } else if (toolMode === '3D-Delete') { + handleEventPointDelete() + } + }} + onPointerOver={(e) => { + e.stopPropagation(); + if (toolMode === '3D-Delete' && (!deletableEventSphere || deletableEventSphere.uuid !== point.uuid)) { + setDeletableEventSphere(ref.current); + } + }} + onPointerOut={(e) => { + e.stopPropagation(); + if (toolMode === '3D-Delete' && deletableEventSphere && deletableEventSphere.uuid === point.uuid) { + clearDeletableEventSphere(); + } + }} + userData={{ modelUuid, pointUuid: point.uuid }} + > + + + + ); +} diff --git a/app/src/modules/simulation/events/points/instances/pointInstances.tsx b/app/src/modules/simulation/events/points/instances/pointInstances.tsx new file mode 100644 index 0000000..0b7ca60 --- /dev/null +++ b/app/src/modules/simulation/events/points/instances/pointInstances.tsx @@ -0,0 +1,56 @@ +import { useProductContext } from "../../../products/productContext"; +import { useSceneContext } from "../../../../scene/sceneContext"; + +import PointInstance from "./instance/pointInstance"; + +function PointInstances() { + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + + const { eventStore, productStore } = useSceneContext(); + const { events } = eventStore(); + const { getEventByModelUuid } = productStore(); + + const colorByType: Record = { + transfer: "orange", + vehicle: "blue", + roboticArm: "green", + machine: "purple", + storageUnit: "red", + human: "white", + }; + + return ( + <> + {events.map((event, index) => { + const updatedEvent = selectedProduct.productUuid !== '' ? getEventByModelUuid(selectedProduct.productUuid, event.modelUuid) : null; + + const usedEvent = updatedEvent || event; + const color = colorByType[usedEvent.type]; + + if (!color) return null; + + const points = usedEvent.type === "transfer" ? usedEvent.points : [usedEvent.point]; + + return ( + + {points.map((point) => ( + + ))} + + ); + })} + + ); +} + +export default PointInstances; diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index 94ce8b3..4851c1c 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -54,9 +54,16 @@ export function useHumanEventManager() { callbacksRef.current.forEach(({ humanId, callback }) => { const human = getHumanById(humanId); - if (human && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { - callback(); - removeHumanFromMonitor(humanId); // Remove after triggering + if (human?.point.action.actionType === 'worker') { + if (human && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { + callback(); + removeHumanFromMonitor(humanId); // Remove after triggering + } + } else if (human?.point.action.actionType === 'assembly') { + if (human && human.isActive === false && human.state === 'idle') { + callback(); + removeHumanFromMonitor(humanId); // Remove after triggering + } } }); }); diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index db8cda2..b41bef8 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -212,7 +212,6 @@ function RoboticArmInstance({ armBot }: { readonly armBot: ArmBotStatus }) { } }, [isReset, isPlaying]) - function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; diff --git a/app/src/services/simulation/ik/getAssetIKs.ts b/app/src/services/simulation/ik/getAssetIKs.ts new file mode 100644 index 0000000..cfc6ad5 --- /dev/null +++ b/app/src/services/simulation/ik/getAssetIKs.ts @@ -0,0 +1,36 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; + +export const getAssetIksApi = async (assetId: string) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/getAssetIks/${assetId}`, + { + method: "GET", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + } + ); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to fetch assetIks"); + } + + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to get assetIks"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 1a37964..aa75d68 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -18,8 +18,8 @@ type ProductsStore = { updateEvent: (productUuid: string, modelUuid: string, updates: Partial) => EventsSchema | undefined; // Point-level actions - addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema) => void; - removePoint: (productUuid: string, modelUuid: string, pointUuid: string) => void; + addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema) => EventsSchema | undefined; + removePoint: (productUuid: string, modelUuid: string, pointUuid: string) => EventsSchema | undefined; updatePoint: ( productUuid: string, modelUuid: string, @@ -72,6 +72,7 @@ type ProductsStore = { getModelUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getPointUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getTriggerByUuid: (productUuid: string, triggerUuid: string) => TriggerSchema | undefined; + getTriggersByTriggeredPointUuid: (productUuid: string, triggeredPointUuid: string) => TriggerSchema[]; getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean; }; @@ -251,6 +252,7 @@ export const createProductStore = () => { // Point-level actions addPoint: (productUuid, modelUuid, point) => { + let updatedEvent: EventsSchema | undefined = undefined; set((state) => { const product = state.products.find(p => p.productUuid === productUuid); if (product) { @@ -259,29 +261,35 @@ export const createProductStore = () => { const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid); if (!existingPoint) { (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); + updatedEvent = JSON.parse(JSON.stringify(event)); } } else if (event && 'point' in event) { const existingPoint = (event as any).point?.uuid === point.uuid; if (!existingPoint) { (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any; + updatedEvent = JSON.parse(JSON.stringify(event)); } } } }); + return updatedEvent; }, removePoint: (productUuid, modelUuid, pointUuid) => { + let updatedEvent: EventsSchema | undefined = undefined; set((state) => { const product = state.products.find(p => p.productUuid === productUuid); if (product) { const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { (event as ConveyorEventSchema).points = (event as ConveyorEventSchema).points.filter(p => p.uuid !== pointUuid); + updatedEvent = JSON.parse(JSON.stringify(event)); } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { // For events with single point, we can't remove it, only reset to empty } } }); + return updatedEvent; }, updatePoint: (productUuid, modelUuid, pointUuid, updates) => { @@ -881,6 +889,48 @@ export const createProductStore = () => { return undefined; }, + getTriggersByTriggeredPointUuid: (productUuid, triggeredPointUuid) => { + const product = get().products.find(p => p.productUuid === productUuid); + if (!product) return []; + + const triggers: TriggerSchema[] = []; + + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.triggers) { + for (const trigger of point.action.triggers) { + if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { + triggers.push(trigger); + } + } + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point && point.action?.triggers) { + for (const trigger of point.action.triggers) { + if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { + triggers.push(trigger); + } + } + } else if ('actions' in point) { + for (const action of point.actions) { + if (action.triggers) { + for (const trigger of action.triggers) { + if (trigger.triggeredAsset?.triggeredPoint?.pointUuid === triggeredPointUuid) { + triggers.push(trigger); + } + } + } + } + } + } + } + + return triggers; + }, + getIsEventInProduct: (productUuid, modelUuid) => { const product = get().getProductById(productUuid); if (!product) return false; diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts index b6cd076..9742795 100644 --- a/app/src/store/simulation/useSimulationStore.ts +++ b/app/src/store/simulation/useSimulationStore.ts @@ -24,6 +24,28 @@ export const useSelectedEventSphere = create()( })) ); +interface DeletableEventSphereState { + deletableEventSphere: THREE.Mesh | null; + setDeletableEventSphere: (mesh: THREE.Mesh | null) => void; + clearDeletableEventSphere: () => void; +} + +export const useDeletableEventSphere = create()( + immer((set) => ({ + deletableEventSphere: null, + setDeletableEventSphere: (mesh) => { + set((state) => { + state.deletableEventSphere = mesh; + }); + }, + clearDeletableEventSphere: () => { + set((state) => { + state.deletableEventSphere = null; + }); + }, + })) +); + interface SelectedEventDataState { selectedEventData: { data: EventsSchema; selectedPoint: string } | undefined; setSelectedEventData: (data: EventsSchema, selectedPoint: string) => void;