diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx index 3e11e54..be50990 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -137,7 +137,7 @@ function MachineMechanics() {
- +
)} diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx index df5c358..3e8aa67 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -280,7 +280,7 @@ function RoboticArmMechanics() { />
- +
)} diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx index 3273ce4..34e35ae 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -146,7 +146,7 @@ function StorageMechanics() {
- +
)} diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx index 7839168..0f52437 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -235,7 +235,7 @@ function VehicleMechanics() {
- +
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx index 86e4e7b..306865f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import * as THREE from "three"; import { AddIcon, RemoveIcon, @@ -9,6 +10,7 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../../../functions/handleResizePannel"; import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import { useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; type TriggerProps = { selectedPointData?: PointsScheme | undefined; @@ -18,44 +20,223 @@ type TriggerProps = { const Trigger = ({ selectedPointData, type }: TriggerProps) => { const [currentAction, setCurrentAction] = useState(); const { selectedProduct } = useSelectedProduct(); - const { getActionByUuid } = useProductStore(); + const { getActionByUuid, addTrigger, removeTrigger, updateTrigger, renameTrigger, getProductById } = useProductStore(); const [triggers, setTriggers] = useState([]); const [selectedTrigger, setSelectedTrigger] = useState(); - const [activeOption, setActiveOption] = useState("onComplete"); + const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete"); const triggersContainerRef = useRef(null); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + useEffect(() => { - if (!selectedPointData) return; + if (!selectedPointData || !selectedProduct) return; + + let actionUuid: string | undefined; + if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') { - setCurrentAction((selectedPointData as ConveyorPointSchema).action.actionUuid); + actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid; + } else if (type === 'RoboticArm') { + actionUuid = (selectedPointData as RoboticArmPointSchema).actions[0]?.actionUuid; } - }, [selectedPointData]); + + setCurrentAction(actionUuid); + }, [selectedPointData, selectedProduct, type]); + + + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } useEffect(() => { if (!currentAction || !selectedProduct) return; const action = getActionByUuid(selectedProduct.productId, currentAction); - setTriggers(action?.triggers || []); - setSelectedTrigger(action?.triggers[0] || undefined); + const actionTriggers = action?.triggers || []; + setTriggers(actionTriggers); + setSelectedTrigger(actionTriggers[0]); }, [currentAction, selectedProduct]); - const addTrigger = (): void => { + const handleAddTrigger = () => { + if (!selectedProduct || !currentAction) return; + + const newTrigger: TriggerSchema = { + triggerUuid: THREE.MathUtils.generateUUID(), + triggerName: `New Trigger ${triggers.length + 1}`, + triggerType: activeOption, + delay: 0, + triggeredAsset: null + }; + + addTrigger(selectedProduct.productId, currentAction, newTrigger); + setSelectedTrigger(newTrigger); }; - const removeTrigger = (triggerUuid: string): void => { + const handleRemoveTrigger = (triggerUuid: string) => { + if (!selectedProduct) return; + removeTrigger(selectedProduct.productId, triggerUuid); + if (selectedTrigger?.triggerUuid === triggerUuid) { + const remainingTriggers = triggers.filter(t => t.triggerUuid !== triggerUuid); + setSelectedTrigger(remainingTriggers[0]); + } + }; + + const handleTriggerRename = (triggerUuid: string, newName: string) => { + if (!selectedProduct) return; + renameTrigger(selectedProduct.productId, triggerUuid, newName); + }; + + const handleTriggerTypeChange = (option: string) => { + if (!selectedTrigger || !selectedProduct) return; + + const validTypes: Array = ["onComplete", "onStart", "onStop", "delay", "onError"]; + if (!validTypes.includes(option as TriggerSchema['triggerType'])) return; + + setActiveOption(option as TriggerSchema['triggerType']); + updateTrigger(selectedProduct.productId, selectedTrigger.triggerUuid, { + triggerType: option as TriggerSchema['triggerType'] + }); }; const triggeredModel = selectedTrigger?.triggeredAsset?.triggeredModel || { modelName: "Select Model", modelUuid: "" }; const triggeredPoint = selectedTrigger?.triggeredAsset?.triggeredPoint || { pointName: "Select Point", pointUuid: "" }; const triggeredAction = selectedTrigger?.triggeredAsset?.triggeredAction || { actionName: "Select Action", actionUuid: "" }; + const modelOptions = getProductById(selectedProduct.productId)?.eventDatas || []; + + const pointOptions: PointsScheme[] = useMemo(() => { + if (!triggeredModel.modelUuid) return []; + + const model = modelOptions.find(m => m.modelUuid === triggeredModel.modelUuid); + if (!model) return []; + + if ('points' in model) { + return (model as ConveyorEventSchema).points; + } else if ('point' in model) { + return [(model as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point]; + } + return []; + }, [triggeredModel.modelUuid, modelOptions]); + + const actionOptions: any = useMemo(() => { + if (!triggeredPoint.pointUuid) return []; + const point = pointOptions.find((p) => p.uuid === triggeredPoint.pointUuid); + if (!point) return []; + + if ('action' in point) { + const typedPoint = point as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema; + return typedPoint.action ? [typedPoint.action] : []; + } else if ('actions' in point) { + const typedPoint = point as RoboticArmPointSchema; + return typedPoint.actions; + } + return []; + }, [triggeredPoint.pointUuid, pointOptions]); + + const handleModelSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct) return; + + const selectedModel = modelOptions.find(m => m.modelName === option); + if (!selectedModel) return; + + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + triggeredModel: { + modelName: selectedModel.modelName, + modelUuid: selectedModel.modelUuid + }, + triggeredPoint: null, + triggeredAction: null + } + }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; + + const handlePointSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct || !selectedTrigger) return; + + const pointUuid = pointOptions.find(p => `Point ${p.uuid.slice(0, 5)}` === option)?.uuid; + + if (!pointUuid) return; + + if (selectedTrigger.triggeredAsset?.triggeredModel) { + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + ...selectedTrigger.triggeredAsset, + triggeredPoint: { + pointName: option, + pointUuid: pointUuid + }, + triggeredAction: null + } + }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + } + }; + + const handleActionSelect = (option: string, triggerUuid: string) => { + if (!selectedProduct || !selectedTrigger) return; + + const selectedAction = actionOptions.find((a: any) => a.actionName === option); + + if (!selectedAction) return; + + if (selectedTrigger.triggeredAsset?.triggeredPoint) { + const event = updateTrigger(selectedProduct.productId, triggerUuid, { + triggeredAsset: { + ...selectedTrigger.triggeredAsset, + triggeredAction: { + actionName: option, + actionUuid: selectedAction.actionUuid + } + } + }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + } + }; + return (
Trigger
@@ -73,13 +254,19 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { className={`list-item ${selectedTrigger?.triggerUuid === trigger.triggerUuid ? "active" : ""}`} onClick={() => setSelectedTrigger(trigger)} > - {triggers.length > 1 && ( @@ -95,35 +282,39 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
-
-
{selectedTrigger?.triggerName}
- setActiveOption(option)} - /> -
+ + {selectedTrigger && ( +
+
{selectedTrigger.triggerName}
{ }} - /> - { }} - /> - { }} + label="Trigger Type" + defaultOption={selectedTrigger.triggerType} + options={["onComplete", "onStart", "onStop", "delay", "onError"]} + onSelect={handleTriggerTypeChange} /> + +
+ (option.modelName))]} + onSelect={(option) => { handleModelSelect(option, selectedTrigger.triggerUuid) }} + /> + (`Point ${option.uuid.slice(0, 5)}`))]} + onSelect={(option) => { handlePointSelect(option, selectedTrigger.triggerUuid) }} + /> + (option.actionName))]} + onSelect={(option) => { handleActionSelect(option, selectedTrigger.triggerUuid) }} + /> +
-
+ )}
); diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index 1280693..7799858 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -1,15 +1,15 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useRef, useState } from "react"; import { - AddIcon, - ArrowIcon, - RemoveIcon, - ResizeHeightIcon, + AddIcon, + ArrowIcon, + RemoveIcon, + ResizeHeightIcon, } from "../../../icons/ExportCommonIcons"; import RenameInput from "../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../functions/handleResizePannel"; import { - useSelectedAsset, - useSelectedProduct, + useSelectedAsset, + useSelectedProduct, } from "../../../../store/simulation/useSimulationStore"; import { useProductStore } from "../../../../store/simulation/useProductStore"; import { generateUUID } from "three/src/math/MathUtils"; @@ -22,206 +22,222 @@ import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertP import { deleteProductApi } from "../../../../services/simulation/deleteProductApi"; interface Event { - pathName: string; + pathName: string; } interface ListProps { - val: Event; + val: Event; } const List: React.FC = ({ val }) => { - return ( -
-
{val.pathName}
-
- ); + return ( +
+
{val.pathName}
+
+ ); }; const Simulations: React.FC = () => { - const productsContainerRef = useRef(null); - const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent, } = useProductStore(); - const { selectedProduct, setSelectedProduct } = useSelectedProduct(); - const { getEventByModelUuid } = useEventsStore(); - const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; - const [openObjects, setOpenObjects] = useState(true); + const productsContainerRef = useRef(null); + const { + products, + addProduct, + removeProduct, + renameProduct, + addEvent, + removeEvent, + } = useProductStore(); + const { selectedProduct, setSelectedProduct } = useSelectedProduct(); + const { getEventByModelUuid } = useEventsStore(); + const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; + const [openObjects, setOpenObjects] = useState(true); - const handleAddProduct = () => { - const id = generateUUID(); - const name = `Product ${products.length + 1}`; - addProduct(name, id); - upsertProductOrEventApi({ productName: name, productId: id, organization: organization }); - }; + const handleAddProduct = () => { + const id = generateUUID(); + const name = `Product ${products.length + 1}`; + addProduct(name, id); + upsertProductOrEventApi({ + productName: name, + productId: id, + organization: organization, + }); + }; - const handleRemoveProduct = (productId: string) => { - const currentIndex = products.findIndex((p) => p.productId === productId); - const isSelected = selectedProduct.productId === productId; + const handleRemoveProduct = (productId: string) => { + const currentIndex = products.findIndex((p) => p.productId === productId); + const isSelected = selectedProduct.productId === productId; - const updatedProducts = products.filter((p) => p.productId !== productId); + const updatedProducts = products.filter((p) => p.productId !== productId); - if (isSelected) { - if (updatedProducts.length > 0) { - let newSelectedIndex = currentIndex; - if (currentIndex >= updatedProducts.length) { - newSelectedIndex = updatedProducts.length - 1; - } - setSelectedProduct( - updatedProducts[newSelectedIndex].productId, - updatedProducts[newSelectedIndex].productName - ); - } else { - setSelectedProduct("", ""); - } + if (isSelected) { + if (updatedProducts.length > 0) { + let newSelectedIndex = currentIndex; + if (currentIndex >= updatedProducts.length) { + newSelectedIndex = updatedProducts.length - 1; } + setSelectedProduct( + updatedProducts[newSelectedIndex].productId, + updatedProducts[newSelectedIndex].productName + ); + } else { + setSelectedProduct("", ""); + } + } - removeProduct(productId); - deleteProductApi(productId, organization); - }; + removeProduct(productId); + deleteProductApi(productId, organization); + }; - const handleRenameProduct = (productId: string, newName: string) => { - renameProduct(productId, newName); - if (selectedProduct.productId === productId) { - setSelectedProduct(productId, newName); - } - }; + const handleRenameProduct = (productId: string, newName: string) => { + renameProduct(productId, newName); + if (selectedProduct.productId === productId) { + setSelectedProduct(productId, newName); + } + }; - const handleRemoveEventFromProduct = () => { - if (selectedAsset) { - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; - deleteEventDataApi({ - productId: selectedProduct.productId, - modelUuid: selectedAsset.modelUuid, - organization: organization - }); - removeEvent(selectedProduct.productId, selectedAsset.modelUuid); - clearSelectedAsset(); - } - }; + const handleRemoveEventFromProduct = () => { + if (selectedAsset) { + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; + deleteEventDataApi({ + productId: selectedProduct.productId, + modelUuid: selectedAsset.modelUuid, + organization: organization, + }); + removeEvent(selectedProduct.productId, selectedAsset.modelUuid); + clearSelectedAsset(); + } + }; - const selectedProductData = products.find( - (product) => product.productId === selectedProduct.productId - ); + const selectedProductData = products.find( + (product) => product.productId === selectedProduct.productId + ); - const events: Event[] = selectedProductData?.eventDatas.map((event) => ({ - pathName: event.modelName, + const events: Event[] = + selectedProductData?.eventDatas.map((event) => ({ + pathName: event.modelName, })) || []; - return ( -
-
Simulations
-
-
-
-
Products
-
- Add -
-
-
-
- {products.map((product, index) => ( -
-
- setSelectedProduct(product.productId, product.productName) - } - > - - - handleRenameProduct(product.productId, newName) - } - /> -
- {products.length > 1 && ( -
handleRemoveProduct(product.productId)} - > - -
- )} -
- ))} -
-
handleResize(e, productsContainerRef)} - > - -
-
-
- -
- - {openObjects && - events.map((event, index) => )} -
- -
-
- Need to Compare Layout? -
-
- Click 'Compare' to review and analyze the layout - differences between them. -
-
- -
-
-
- - {selectedAsset && ( - - { - if (option === "Add to Product") { - handleAddEventToProduct({ - event: getEventByModelUuid(selectedAsset.modelUuid), - addEvent, - selectedProduct, - clearSelectedAsset - }); - } else { - handleRemoveEventFromProduct(); - } - }} + return ( +
+
Simulations
+
+
+
+
Products
+ +
+
+
+ {products.map((product, index) => ( +
+ {/* eslint-disable-next-line */} +
+ setSelectedProduct(product.productId, product.productName) + } + > + - - )} + + handleRenameProduct(product.productId, newName) + } + /> +
+ {products.length > 1 && ( + + )} +
+ ))} +
+ +
- ) + +
+ + {openObjects && + events.map((event, index) => ( + + ))} +
+ +
+
+ Need to Compare Layout? +
+
+ Click 'Compare' to review and analyze the layout + differences between them. +
+
+ +
+
+
+ + {selectedAsset && ( + + { + if (option === "Add to Product") { + handleAddEventToProduct({ + event: getEventByModelUuid(selectedAsset.modelUuid), + addEvent, + selectedProduct, + clearSelectedAsset, + }); + } else { + handleRemoveEventFromProduct(); + } + }} + /> + + )} +
+ ); }; export default Simulations; diff --git a/app/src/components/ui/features/RenameTooltip.tsx b/app/src/components/ui/features/RenameTooltip.tsx new file mode 100644 index 0000000..180ba85 --- /dev/null +++ b/app/src/components/ui/features/RenameTooltip.tsx @@ -0,0 +1,50 @@ +import React, { useState } from "react"; +import { RenameIcon } from "../../icons/ContextMenuIcons"; +import { + useLeftData, + useTopData, +} from "../../../store/visualization/useZone3DWidgetStore"; + +type RenameTooltipProps = { + name: string; + onSubmit: (newName: string) => void; +}; + +const RenameTooltip: React.FC = ({ name, onSubmit }) => { + const [value, setValue] = useState(name); + + const { top } = useTopData(); + const { left } = useLeftData(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSubmit(value.trim()); + }; + + return ( +
+
+
+ +
+
Name
+
+
+ setValue(e.target.value)} + autoFocus + /> +
+
+ ); +}; + +export default RenameTooltip; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index eebcaa7..b5403e1 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -27,6 +27,7 @@ interface ZoneItem { id: string; name: string; assets?: Asset[]; + active?: boolean; } interface ListProps { @@ -157,7 +158,7 @@ const List: React.FC = ({ items = [], remove }) => { {items?.map((item) => (
  • -
    +
    ))}
    diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index a41447a..9edb0c9 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -64,8 +64,6 @@ const SimulationPlayer: React.FC = () => { const handleMouseDown = () => { isDragging.current = true; - document.addEventListener("mousemove", handleMouseMove); - document.addEventListener("mouseup", handleMouseUp); }; const handleMouseMove = (e: MouseEvent) => { @@ -80,11 +78,11 @@ const SimulationPlayer: React.FC = () => { const handleMouseUp = () => { isDragging.current = false; - document.removeEventListener("mousemove", handleMouseMove); - document.removeEventListener("mouseup", handleMouseUp); }; useEffect(() => { + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); @@ -109,24 +107,6 @@ const SimulationPlayer: React.FC = () => { { name: "process 9", completed: 90 }, // 90% completed { name: "process 10", completed: 30 }, // 30% completed ]; - // Move getRandomColor out of render - const getRandomColor = () => { - const letters = "0123456789ABCDEF"; - let color = "#"; - for (let i = 0; i < 6; i++) { - color += letters[Math.floor(Math.random() * 16)]; - } - return color; - }; - - // Store colors for each process item - const [_, setProcessColors] = useState([]); - - // Generate colors on mount or when process changes - useEffect(() => { - const generatedColors = process.map(() => getRandomColor()); - setProcessColors(generatedColors); - }, []); const intervals = [10, 20, 30, 40, 50, 60]; // in minutes const totalSegments = intervals.length; @@ -218,7 +198,7 @@ const SimulationPlayer: React.FC = () => {
  • )} - {subModule === "simulations" && ( + {subModule !== "analysis" && (
    {playSimulation @@ -281,7 +261,7 @@ const SimulationPlayer: React.FC = () => { const segmentProgress = (index / totalSegments) * 100; const isFilled = progress >= segmentProgress; return ( - +
    {label} mins
    { className="process-wrapper" style={{ padding: expand ? "0px" : "5px 35px" }} > + {/* eslint-disable-next-line */}
    { > {process.map((item, index) => (
    { URL.revokeObjectURL(blobUrl); @@ -103,7 +102,7 @@ async function loadInitialFloorItems( } // Fetch from Backend - // console.log(`[Backend] Fetching ${item.modelName}`); + // const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`; loader.load(modelUrl, async (gltf) => { const modelBlob = await fetch(modelUrl).then((res) => res.blob()); @@ -121,7 +120,7 @@ async function loadInitialFloorItems( ); }); } else { - // console.log(`Item ${item.modelName} is not near`); + // setFloorItems((prevItems) => [ ...(prevItems || []), { @@ -282,8 +281,8 @@ function processLoadedModel( actionName: "Action 1", actionType: "pickAndPlace", process: { - startPoint: [0, 0, 0], - endPoint: [0, 0, 0] + startPoint: null, + endPoint: null }, triggers: [] } diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index 64656a0..6e978dc 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -235,7 +235,7 @@ async function handleModelLoad( const nextPoint = ConveyorEvent.points[i + 1]; if (currentPoint.action.triggers.length > 0) { - currentPoint.action.triggers[0].triggeredAsset!.triggeredPoint.pointUuid = nextPoint.uuid; + currentPoint.action.triggers[0].triggeredAsset!.triggeredPoint!.pointUuid = nextPoint.uuid; currentPoint.action.triggers[0].triggeredAsset!.triggeredAction!.actionUuid = nextPoint.action.actionUuid; } } diff --git a/app/src/modules/builder/geomentries/assets/assetManager.ts b/app/src/modules/builder/geomentries/assets/assetManager.ts index 5f7798e..889d905 100644 --- a/app/src/modules/builder/geomentries/assets/assetManager.ts +++ b/app/src/modules/builder/geomentries/assets/assetManager.ts @@ -18,7 +18,7 @@ export default async function assetManager( const taskId = ++currentTaskId; // Increment taskId for each call activePromises.set(taskId, true); // Mark task as active - // console.log("Received message from worker:", data); + // if (data.toRemove.length > 0) { data.toRemove.forEach((uuid: string) => { @@ -58,7 +58,7 @@ export default async function assetManager( // Check Three.js Cache const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { - // console.log(`[Cache] Fetching ${item.modelName}`); + // processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve); return; } @@ -66,7 +66,7 @@ export default async function assetManager( // Check IndexedDB const indexedDBModel = await retrieveGLTF(item.modelfileID!); if (indexedDBModel) { - // console.log(`[IndexedDB] Fetching ${item.modelName}`); + // const blobUrl = URL.createObjectURL(indexedDBModel); loader.load( blobUrl, @@ -86,7 +86,7 @@ export default async function assetManager( } // Fetch from Backend - // console.log(`[Backend] Fetching ${item.modelName}`); + // loader.load( modelUrl, async (gltf) => { @@ -114,14 +114,14 @@ export default async function assetManager( const existingModel = itemsGroup?.current?.getObjectByProperty("uuid", item.modelUuid); if (existingModel) { - // console.log(`Model ${item.modelName} already exists in the scene.`); + // resolve(); return; } const model = gltf; model.uuid = item.modelUuid; - model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid }; + model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid, eventData: item.eventData }; model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); model.position.set(...item.position); model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z); diff --git a/app/src/modules/scene/controls/selectionControls/moveControls.tsx b/app/src/modules/scene/controls/selectionControls/moveControls.tsx index cc9ce50..5b33c14 100644 --- a/app/src/modules/scene/controls/selectionControls/moveControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/moveControls.tsx @@ -1,7 +1,7 @@ import * as THREE from "three"; -import { useEffect, useMemo, useRef } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store"; +import { useFloorItems, useSelectedAssets, useSocketStore, useStartSimulation, useToggleView } from "../../../../store/store"; // import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; import { toast } from "react-toastify"; import * as Types from "../../../../types/world/worldTypes"; @@ -10,6 +10,7 @@ import { useEventsStore } from "../../../../store/simulation/useEventsStore"; import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; +import { snapControls } from "../../../../utils/handleSnap"; function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); @@ -21,6 +22,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("") const email = localStorage.getItem('email') const organization = (email!.split("@")[1]).split(".")[0]; @@ -54,6 +56,15 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const onPointerMove = () => { isMoving = true; }; + const onKeyUp = (event: KeyboardEvent) => { + // When any modifier is released, reset snap + const isModifierKey = + event.key === "Control" || event.key === "Shift"; + + if (isModifierKey) { + setKeyEvent(""); + } + }; const onPointerUp = (event: PointerEvent) => { if (!isMoving && movedObjects.length > 0 && event.button === 0) { @@ -75,18 +86,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje setMovedObjects([]); itemsData.current = []; } + setKeyEvent("") }; const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; + + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + // update state here + setKeyEvent(keyCombination) + } else { + setKeyEvent("") + } + if (keyCombination === "G") { if (selectedAssets.length > 0) { moveAssets(); itemsData.current = floorItems.filter((item: { modelUuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modelUuid)); } } + if (keyCombination === "ESCAPE") { event.preventDefault(); @@ -109,6 +130,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); + canvasElement?.addEventListener("keyup", onKeyUp); } return () => { @@ -116,12 +138,11 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); + canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]); + }, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]); - const gridSize = 0.25; - const moveSpeed = 0.25; - const isGridSnap = false; + let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25; useFrame(() => { if (movedObjects.length > 0) { @@ -132,10 +153,17 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje if (point) { let targetX = point.x; let targetZ = point.z; + if (keyEvent === "Ctrl") { + targetX = snapControls(targetX, "Ctrl"); + targetZ = snapControls(targetZ, "Ctrl"); + } 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 { - if (isGridSnap) { - targetX = Math.round(point.x / gridSize) * gridSize; - targetZ = Math.round(point.z / gridSize) * gridSize; } const position = new THREE.Vector3(); @@ -222,6 +250,8 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje event ); } + + newFloorItem.eventData = eventData; } } @@ -278,6 +308,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje setMovedObjects([]); setRotatedObjects([]); setSelectedAssets([]); + setKeyEvent("") } return null; diff --git a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx index 7ea7045..58dab0c 100644 --- a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx @@ -222,6 +222,8 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo event ); } + + newFloorItem.eventData = eventData; } } diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index e178a81..94128ce 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -7,8 +7,6 @@ import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModi import { useSelectedEventSphere, useSelectedEventData, - useIsDragging, - useIsRotating, } from "../../../../../store/simulation/useSimulationStore"; import { useThree } from "@react-three/fiber"; @@ -21,15 +19,14 @@ function PointsCreator() { const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere(); - const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); - const { isDragging } = useIsDragging(); - const { isRotating } = useIsRotating(); + const { selectedEventData, setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); useEffect(() => { if (selectedEventSphere) { const eventData = getEventByModelUuid( selectedEventSphere.userData.modelUuid ); + if (eventData) { setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid); } else { @@ -58,24 +55,11 @@ function PointsCreator() { const updatePointToState = (selectedEventSphere: THREE.Mesh) => { let point = JSON.parse( - JSON.stringify( - getPointByUuid( - selectedEventSphere.userData.modelUuid, - selectedEventSphere.userData.pointUuid - ) - ) + JSON.stringify(getPointByUuid(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid)) ); if (point) { - point.position = [ - selectedEventSphere.position.x, - selectedEventSphere.position.y, - selectedEventSphere.position.z, - ]; - updatePoint( - selectedEventSphere.userData.modelUuid, - selectedEventSphere.userData.pointUuid, - point - ); + point.position = [selectedEventSphere.position.x, selectedEventSphere.position.y, selectedEventSphere.position.z,]; + updatePoint(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid, point); } }; @@ -134,8 +118,11 @@ function PointsCreator() { {events.map((event, i) => { if (event.type === "transfer") { return ( - - {event.points.map((point, j) => ( + + {event.points.map((point) => ( @@ -159,7 +147,10 @@ function PointsCreator() { ); } else if (event.type === "vehicle") { return ( - + @@ -181,7 +174,10 @@ function PointsCreator() { ); } else if (event.type === "roboticArm") { return ( - + @@ -203,7 +201,10 @@ function PointsCreator() { ); } else if (event.type === "machine") { return ( - + @@ -244,4 +247,4 @@ function PointsCreator() { ); } -export default PointsCreator; +export default PointsCreator; \ No newline at end of file diff --git a/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx new file mode 100644 index 0000000..6ef2b0e --- /dev/null +++ b/app/src/modules/simulation/machine/instances/animator/machineAnimator.tsx @@ -0,0 +1,102 @@ +import { useFrame } from '@react-three/fiber'; +import React, { useEffect, useRef } from 'react'; +import { useMachineStore } from '../../../../../store/simulation/useMachineStore'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; + + +interface MachineAnimatorProps { + currentPhase: string; + handleCallBack: () => void; + reset: () => void; + machineStatus: (modelId: string, status: string) => void; + processingTime: number; + machineUuid: string +} + +const MachineAnimator = ({ currentPhase, handleCallBack, processingTime, machineUuid, machineStatus, reset }: MachineAnimatorProps) => { + const animationStarted = useRef(false); + const isPausedRef = useRef(false); + const startTimeRef = useRef(0); + const animationFrameId = useRef(null); + const pauseTimeRef = useRef(null); + const { isPaused } = usePauseButtonStore(); + const { removeCurrentAction } = useMachineStore(); + const { isReset, setReset } = useResetButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const isPlayingRef = useRef(false); + const isResetRef = useRef(false) + + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + useEffect(() => { + isPlayingRef.current = isPlaying; + }, [isPlaying]); + useEffect(() => { + isResetRef.current = isReset; + }, [isReset]); + + + useEffect(() => { + + if (isReset || !isPlaying) { + reset(); + setReset(false); + startTimeRef.current = 0; + isPausedRef.current = false; + pauseTimeRef.current = 0; + animationFrameId.current = null; + animationStarted.current = false; + removeCurrentAction(machineUuid) + } + }, [isReset, isPlaying]) + + useEffect(() => { + if (currentPhase === 'processing' && !animationStarted.current && machineUuid) { + animationStarted.current = true; + startTimeRef.current = performance.now(); + animationFrameId.current = requestAnimationFrame(step); + } + }, [currentPhase]); + + function step(time: number) { + if (!isPausedRef.current || !isResetRef.current) { + if (animationFrameId.current) { + cancelAnimationFrame(animationFrameId.current); + animationFrameId.current = null; + } + if (isPausedRef.current) { + if (!pauseTimeRef.current) { + pauseTimeRef.current = performance.now(); + } + animationFrameId.current = requestAnimationFrame(step); + return; + } + + if (pauseTimeRef.current) { + const pauseDuration = performance.now() - pauseTimeRef.current; + startTimeRef.current += pauseDuration; + pauseTimeRef.current = null; + } + + const elapsed = time - startTimeRef.current; + const processedTime = processingTime * 1000; + if (elapsed < processedTime) { + machineStatus(machineUuid, "Machine is currently processing the task"); + animationFrameId.current = requestAnimationFrame(step); + } else { + removeCurrentAction(machineUuid); + animationStarted.current = false; + handleCallBack(); + + } + } + } + + + return null; +} + +export default MachineAnimator; diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx index edb825f..714bcdb 100644 --- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -1,8 +1,65 @@ -import React from 'react' +import React, { useEffect, useRef, useState } from 'react' +import { useMachineStore } from '../../../../../store/simulation/useMachineStore'; +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import MachineAnimator from '../animator/machineAnimator'; + +function MachineInstance({ machineDetail }: any) { + const [currentPhase, setCurrentPhase] = useState('idle'); + let isIncrememtable = useRef(true); + const { isPlaying } = usePlayButtonStore(); + const { machines, addCurrentAction, setMachineState, setMachineActive } = useMachineStore(); + + const reset = () => { + setMachineState(machineDetail.modelUuid, 'idle'); + setMachineActive(machineDetail.modelUuid, false); + isIncrememtable.current = true; + setCurrentPhase("idle"); + } + const increment = () => { + if (isIncrememtable.current) { + addCurrentAction(machineDetail.modelUuid, "machine-action-2468-1357-8024") + isIncrememtable.current = false; + } + } + function machineStatus(modelId: string, status: string) { + // console.log(`${modelId} , ${status}`); + + } + + useEffect(() => { + if (isPlaying) { + if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && !machineDetail.currentAction) { + setTimeout(() => { + increment(); + }, 2000); + machineStatus(machineDetail.modelUuid, 'Machine is idle and waiting for next instruction.') + } else if (!machineDetail.isActive && machineDetail.state === "idle" && currentPhase == "idle" && machineDetail.currentAction) { + setCurrentPhase("processing"); + setMachineState(machineDetail.modelUuid, 'running'); + setMachineActive(machineDetail.modelUuid, true); + machineStatus(machineDetail.modelUuid, "Machine started processing") + } + } else { + reset(); + } + }, [currentPhase, isPlaying, machines]) + + function handleCallBack() { + if (currentPhase == "processing") { + setMachineState(machineDetail.modelUuid, 'idle'); + setMachineActive(machineDetail.modelUuid, false); + setCurrentPhase("idle") + isIncrememtable.current = true; + machineStatus(machineDetail.modelUuid, "Machine has completed the processing") + } + } + // console.log('currentPhase: ', currentPhase); + + -function MachineInstance() { return ( <> + ) } diff --git a/app/src/modules/simulation/machine/instances/machineInstances.tsx b/app/src/modules/simulation/machine/instances/machineInstances.tsx index b0c2c9f..8536cac 100644 --- a/app/src/modules/simulation/machine/instances/machineInstances.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstances.tsx @@ -1,11 +1,14 @@ import React from 'react' import MachineInstance from './machineInstance/machineInstance' +import { useMachineStore } from '../../../../store/simulation/useMachineStore'; function MachineInstances() { + const { machines } = useMachineStore(); return ( <> - - + {machines.map((val: MachineStatus) => ( + + ))} ) diff --git a/app/src/modules/simulation/machine/machine.tsx b/app/src/modules/simulation/machine/machine.tsx index e9d2dea..3d24f61 100644 --- a/app/src/modules/simulation/machine/machine.tsx +++ b/app/src/modules/simulation/machine/machine.tsx @@ -4,7 +4,7 @@ import { useMachineStore } from '../../../store/simulation/useMachineStore' import { useSelectedProduct } from '../../../store/simulation/useSimulationStore'; function Machine() { - const { addMachine, addCurrentAction, removeMachine } = useMachineStore(); + const { addMachine, addCurrentAction, removeMachine, machines } = useMachineStore(); const { selectedProduct } = useSelectedProduct(); const machineSample: MachineEventSchema[] = [ @@ -38,6 +38,12 @@ function Machine() { // addCurrentAction(machineSample[0].modelUuid, machineSample[0].point.action.actionUuid); }, []) + + useEffect(() => { + + // console.log('machines: ', machines); + }, [machines]) + return ( <> diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index c82b73d..06d50a1 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -1,68 +1,261 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' -import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; -import { useFrame, useThree } from '@react-three/fiber'; -import * as THREE from "three" -import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import React, { useEffect, useRef, useState } from "react"; +import { useFrame } from "@react-three/fiber"; +import * as THREE from "three"; +import { Line } from "@react-three/drei"; +import { + useAnimationPlaySpeed, + usePauseButtonStore, + usePlayButtonStore, + useResetButtonStore, +} from "../../../../../store/usePlayButtonStore"; -function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) { - const { armBots } = useArmBotStore(); - const { scene } = useThree(); - const restSpeed = 0.1; - const restPosition = new THREE.Vector3(0, 1, -1.6); - const initialCurveRef = useRef(null); - const initialStartPositionRef = useRef(null); - const [initialProgress, setInitialProgress] = useState(0); - const [progress, setProgress] = useState(0); - const [needsInitialMovement, setNeedsInitialMovement] = useState(true); - const [isInitializing, setIsInitializing] = useState(true); - const { isPlaying } = usePlayButtonStore(); - const statusRef = useRef("idle"); - // Create a ref for initialProgress - const initialProgressRef = useRef(0); - const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); +function RoboticArmAnimator({ + HandleCallback, + restPosition, + ikSolver, + targetBone, + armBot, + logStatus, + path, +}: any) { + const progressRef = useRef(0); + const curveRef = useRef(null); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>( + [] + ); + const [circlePoints, setCirclePoints] = useState<[number, number, number][]>( + [] + ); + const [customCurvePoints, setCustomCurvePoints] = useState< + THREE.Vector3[] | null + >(null); - useEffect(() => { - - setCurrentPath(path) - }, [path]) + // Zustand stores + const { isPlaying } = usePlayButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { isReset } = useResetButtonStore(); + const { speed } = useAnimationPlaySpeed(); - useEffect(() => { + // Update path state whenever `path` prop changes + useEffect(() => { + setCurrentPath(path); + }, [path]); - }, [currentPath]) + // Reset logic when `isPlaying` changes + useEffect(() => { + if (!isPlaying) { + setCurrentPath([]); + curveRef.current = null; + } + }, [isPlaying]); - useFrame((_, delta) => { - if (!ikSolver || !currentPath || currentPath.length === 0) return; + // Handle circle points based on armBot position + useEffect(() => { + const points = generateRingPoints(1.6, 64); + setCirclePoints(points); + }, [armBot.position]); - const bone = ikSolver.mesh.skeleton.bones.find( - (b: any) => b.name === targetBone - ); - if (!bone) return; + function generateRingPoints(radius: any, segments: any) { + const points: [number, number, number][] = []; + for (let i = 0; i < segments; i++) { + // Calculate angle for current segment + const angle = (i / segments) * Math.PI * 2; + // Calculate x and z coordinates (y remains the same for a flat ring) + const x = Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + points.push([x, 1.5, z]); + } + return points; + } - // Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it - const curve = new THREE.CatmullRomCurve3( - currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2])) - ); + const findNearestIndex = ( + nearestPoint: [number, number, number], + points: [number, number, number][], + epsilon = 1e-6 + ) => { + for (let i = 0; i < points.length; i++) { + const [x, y, z] = points[i]; + if ( + Math.abs(x - nearestPoint[0]) < epsilon && + Math.abs(y - nearestPoint[1]) < epsilon && + Math.abs(z - nearestPoint[2]) < epsilon + ) { + return i; // Found the matching index + } + } + return -1; // Not found + }; + // Handle nearest points and final path (including arc points) + useEffect(() => { + if (circlePoints.length > 0 && currentPath.length > 0) { + const start = currentPath[0]; + const end = currentPath[currentPath.length - 1]; - const next = initialProgressRef.current + delta * 0.5; - if (next >= 1) { - // bone.position.copy(restPosition); - HandleCallback(); // Call the callback when the path is completed - initialProgressRef.current = 0; // Set ref to 1 when done + const raisedStart = [start[0], start[1] + 0.5, start[2]] as [ + number, + number, + number + ]; + const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [ + number, + number, + number + ]; + + const findNearest = (target: [number, number, number]) => { + return circlePoints.reduce((nearest, point) => { + const distance = Math.hypot( + target[0] - point[0], + target[1] - point[1], + target[2] - point[2] + ); + const nearestDistance = Math.hypot( + target[0] - nearest[0], + target[1] - nearest[1], + target[2] - nearest[2] + ); + return distance < nearestDistance ? point : nearest; + }, circlePoints[0]); + }; + + const nearestToStart = findNearest(raisedStart); + + const nearestToEnd = findNearest(raisedEnd); + + const indexOfNearestStart = findNearestIndex( + nearestToStart, + circlePoints + ); + + const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints); + + // Find clockwise and counter-clockwise distances + const clockwiseDistance = + (indexOfNearestEnd - indexOfNearestStart + 64) % 64; + + const counterClockwiseDistance = + (indexOfNearestStart - indexOfNearestEnd + 64) % 64; + + const clockwiseIsShorter = clockwiseDistance <= counterClockwiseDistance; + + // Collect arc points between start and end + let arcPoints: [number, number, number][] = []; + + if (clockwiseIsShorter) { + if (indexOfNearestStart <= indexOfNearestEnd) { + arcPoints = circlePoints.slice( + indexOfNearestStart, + indexOfNearestEnd + 1 + ); } else { - const point = curve.getPoint(next); // Get the interpolated point from the curve - bone.position.copy(point); // Update the bone position along the curve - initialProgressRef.current = next; // Update progress + // Wrap around + arcPoints = [ + ...circlePoints.slice(indexOfNearestStart, 64), + ...circlePoints.slice(0, indexOfNearestEnd + 1), + ]; } + } else if (indexOfNearestStart >= indexOfNearestEnd) { + for ( + let i = indexOfNearestStart; + i !== (indexOfNearestEnd - 1 + 64) % 64; + i = (i - 1 + 64) % 64 + ) { + arcPoints.push(circlePoints[i]); + } + } - ikSolver.update(); - }); + // Continue your custom path logic + const pathVectors = [ + new THREE.Vector3(start[0], start[1], start[2]), // start + new THREE.Vector3(raisedStart[0], raisedStart[1], raisedStart[2]), // lift up + new THREE.Vector3(nearestToStart[0], raisedStart[1], nearestToStart[2]), // move to arc start + ...arcPoints.map( + (point) => new THREE.Vector3(point[0], raisedStart[1], point[2]) + ), + new THREE.Vector3(nearestToEnd[0], raisedEnd[1], nearestToEnd[2]), // move from arc end + new THREE.Vector3(raisedEnd[0], raisedEnd[1], raisedEnd[2]), // lowered end + new THREE.Vector3(end[0], end[1], end[2]), // end + ]; + const customCurve = new THREE.CatmullRomCurve3( + pathVectors, + false, + "centripetal", + 1 + ); + const generatedPoints = customCurve.getPoints(100); + setCustomCurvePoints(generatedPoints); + } + }, [circlePoints, currentPath]); + // Frame update for animation + useFrame((_, delta) => { + if (!ikSolver) return; - return ( - <> - ) + const bone = ikSolver.mesh.skeleton.bones.find( + (b: any) => b.name === targetBone + ); + if (!bone) return; + + if (isPlaying) { + if (!isPaused && customCurvePoints && currentPath.length > 0) { + const curvePoints = customCurvePoints; + const speedAdjustedProgress = + progressRef.current + speed * armBot.speed; + const index = Math.floor(speedAdjustedProgress); + + if (index >= curvePoints.length) { + // Reached the end of the curve + HandleCallback(); + setCurrentPath([]); + curveRef.current = null; + progressRef.current = 0; + } else { + const point = curvePoints[index]; + bone.position.copy(point); + progressRef.current = speedAdjustedProgress; + } + } else if (isPaused) { + logStatus(armBot.modelUuid, "Simulation Paused"); + } + + ikSolver.update(); + } else if (!isPlaying && currentPath.length === 0) { + // Not playing anymore, reset to rest + bone.position.copy(restPosition); + ikSolver.update(); + } + }); + + return ( + <> + {customCurvePoints && currentPath && isPlaying && ( + + [p.x, p.y, p.z] as [number, number, number] + )} + color="green" + lineWidth={5} + dashed={false} + /> + + )} + + + + + + ); } -export default RoboticArmAnimator; \ No newline at end of file +export default RoboticArmAnimator; diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index af3ffe3..d2f2dba 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -1,61 +1,139 @@ import React, { useEffect, useRef, useState } from 'react' import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; -import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb"; import { useThree } from "@react-three/fiber"; -import { useFloorItems } from '../../../../../store/store'; import useModuleStore from '../../../../../store/useModuleStore'; -import { Vector3 } from "three"; import * as THREE from "three"; +import { useSelectedAction, useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; +import { useProductStore } from '../../../../../store/simulation/useProductStore'; -interface Process { - triggerId: string; - startPoint?: Vector3; - endPoint?: Vector3; - speed: number; -} -function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { +function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { - const { isPlaying } = usePlayButtonStore(); const [currentPhase, setCurrentPhase] = useState<(string)>("init"); - const { scene } = useThree(); - const targetBone = "Target"; - const { activeModule } = useModuleStore(); - const [ikSolver, setIkSolver] = useState(null); - const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); - const { floorItems } = useFloorItems(); - const groupRef = useRef(null); - const [processes, setProcesses] = useState([]); - const [armBotCurvePoints, setArmBotCurvePoints] = useState({ start: [], end: [] }) - const restPosition = new THREE.Vector3(0, 1, -1.6); - let armBotCurveRef = useRef(null) const [path, setPath] = useState<[number, number, number][]>([]); + const [ikSolver, setIkSolver] = useState(null); + const { scene } = useThree(); + const restPosition = new THREE.Vector3(0, 1.75, -1.6); + const targetBone = "Target"; + const groupRef = useRef(null); + const pauseTimeRef = useRef(null); + const isPausedRef = useRef(false); + let startTime: number; + //zustand + const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + const { getActionByUuid } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { activeModule } = useModuleStore(); + const { isPlaying } = usePlayButtonStore(); + const { isReset, setReset } = useResetButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { selectedAction } = useSelectedAction(); + + function firstFrame() { + startTime = performance.now(); + step(); + } + function step() { + if (isPausedRef.current) { + if (!pauseTimeRef.current) { + pauseTimeRef.current = performance.now(); + } + requestAnimationFrame(() => step()); + return; + } + if (pauseTimeRef.current) { + const pauseDuration = performance.now() - pauseTimeRef.current; + startTime += pauseDuration; + pauseTimeRef.current = null; + } + const elapsedTime = performance.now() - startTime; + if (elapsedTime < 1500) { + // Wait until 1500ms has passed + requestAnimationFrame(step); + return; + } + if (currentPhase === "picking") { + + setArmBotActive(armBot.modelUuid, true); + setArmBotState(armBot.modelUuid, "running"); + setCurrentPhase("start-to-end"); + startTime = 0 + const startPoint = armBot.point.actions[0].process.startPoint; + const endPoint = armBot.point.actions[0].process.endPoint; + if (startPoint && endPoint) { + let curve = createCurveBetweenTwoPoints( + new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), + new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])); + if (curve) { + logStatus(armBot.modelUuid, "picking the object"); + setPath(curve.points.map(point => [point.x, point.y, point.z])) + } + } + logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") + } else if (currentPhase === "dropping") { + + setArmBotActive(armBot.modelUuid, true); + setArmBotState(armBot.modelUuid, "running"); + setCurrentPhase("end-to-rest"); + startTime = 0; + const endPoint = armBot.point.actions[0].process.endPoint; + if (endPoint) { + + let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition); + if (curve) { + logStatus(armBot.modelUuid, "dropping the object"); + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") + } + + } + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); useEffect(() => { - let armItems = floorItems?.filter((val: any) => - val.modelUuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d" - ); - // Get the first matching item - let armItem = armItems?.[0]; - if (armItem) { - const targetMesh = scene?.getObjectByProperty("uuid", armItem.modelUuid); - if (targetMesh) { - targetMesh.visible = activeModule !== "simulation" - } + const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid); + + if (targetMesh) { + targetMesh.visible = activeModule !== "simulation" } + const targetBones = ikSolver?.mesh.skeleton.bones.find( (b: any) => b.name === targetBone ); + if (isReset) { + logStatus(armBot.modelUuid, "Simulation Play Reset Successfully") + removeCurrentAction(armBot.modelUuid) + setArmBotActive(armBot.modelUuid, true) + setArmBotState(armBot.modelUuid, "running") + setCurrentPhase("init-to-rest"); + isPausedRef.current = false + pauseTimeRef.current = null + isPausedRef.current = false + startTime = 0 + if (targetBones) { + let curve = createCurveBetweenTwoPoints(targetBones.position, targetBones.position) + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + setReset(false); + logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") + } if (isPlaying) { - //Moving armBot from initial point to rest position. - if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") { - setArmBotActive(robot.modelUuid, true) - setArmBotState(robot.modelUuid, "running") + //Moving armBot from initial point to rest position. + if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") { + + setArmBotActive(armBot.modelUuid, true) + setArmBotState(armBot.modelUuid, "running") setCurrentPhase("init-to-rest"); if (targetBones) { let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) @@ -63,21 +141,26 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { setPath(curve.points.map(point => [point.x, point.y, point.z])); } } - logStatus(robot.modelUuid, "Moving armBot from initial point to rest position.") + logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.") } //Waiting for trigger. - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) { - logStatus(robot.modelUuid, "Waiting to trigger CurrentAction") - setTimeout(() => { - addCurrentAction(robot.modelUuid, 'action-003'); + else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) { + logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction") + const timeoutId = setTimeout(() => { + addCurrentAction(armBot.modelUuid, selectedAction?.actionId); + console.log('selectedAction?.actionId: ', selectedAction?.actionId); }, 3000); + return () => clearTimeout(timeoutId); } - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) { - if (robot.currentAction) { - setArmBotActive(robot.modelUuid, true); - setArmBotState(robot.modelUuid, "running"); + else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) { + if (armBot.currentAction) { + + setArmBotActive(armBot.modelUuid, true); + setArmBotState(armBot.modelUuid, "running"); setCurrentPhase("rest-to-start"); - const startPoint = robot.point.actions[0].process.startPoint; + let actiondata = getActionByUuid(selectedProduct.productId, selectedAction.actionId) + console.log('actiondata: ', actiondata); + const startPoint = armBot.point.actions[0].process.startPoint; if (startPoint) { let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2])); if (curve) { @@ -85,105 +168,109 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { } } } - logStatus(robot.modelUuid, "Moving armBot from rest point to start position.") + logStatus(armBot.modelUuid, "Moving armBot from rest point to start position.") } - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "picking" && robot.currentAction) { - setArmBotActive(robot.modelUuid, true); - setArmBotState(robot.modelUuid, "running"); - setCurrentPhase("start-to-end"); - const startPoint = robot.point.actions[0].process.startPoint; - const endPoint = robot.point.actions[0].process.endPoint; - if (startPoint && endPoint) { - let curve = createCurveBetweenTwoPoints( - new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), - new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]) - ); - if (curve) { - setTimeout(() => { - logStatus(robot.modelUuid, "picking the object"); - setPath(curve.points.map(point => [point.x, point.y, point.z])); - }, 1500) - } - } - logStatus(robot.modelUuid, "Moving armBot from start point to end position.") + else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "picking" && armBot.currentAction) { + requestAnimationFrame(firstFrame); + // setArmBotActive(armBot.modelUuid, true); + // setArmBotState(armBot.modelUuid, "running"); + // setCurrentPhase("start-to-end"); + // const startPoint = armBot.point.actions[0].process.startPoint; + // const endPoint = armBot.point.actions[0].process.endPoint; + // if (startPoint && endPoint) { + // let curve = createCurveBetweenTwoPoints( + // new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), + // new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2])); + // if (curve) { + // setTimeout(() => { + // logStatus(armBot.modelUuid, "picking the object"); + // setPath(curve.points.map(point => [point.x, point.y, point.z])); + // }, 1500) + // } + // } + // logStatus(armBot.modelUuid, "Moving armBot from start point to end position.") } - else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) { - setArmBotActive(robot.modelUuid, true); - setArmBotState(robot.modelUuid, "running"); - setCurrentPhase("end-to-rest"); - const endPoint = robot.point.actions[0].process.endPoint; - if (endPoint) { - let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition - ); - if (curve) { - setTimeout(() => { - logStatus(robot.modelUuid, "dropping the object"); - setPath(curve.points.map(point => [point.x, point.y, point.z])); - }, 1500) - } - } - logStatus(robot.modelUuid, "Moving armBot from end point to rest position.") + else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "dropping" && armBot.currentAction) { + requestAnimationFrame(firstFrame); + // setArmBotActive(armBot.modelUuid, true); + // setArmBotState(armBot.modelUuid, "running"); + // setCurrentPhase("end-to-rest"); + // const endPoint = armBot.point.actions[0].process.endPoint; + // if (endPoint) { + // let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition); + // if (curve) { + // setTimeout(() => { + // logStatus(armBot.modelUuid, "dropping the object"); + // setPath(curve.points.map(point => [point.x, point.y, point.z])); + // }, 1500) + // } + // } + // logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.") } + } else { + logStatus(armBot.modelUuid, "Simulation Play Stopped") + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") + setCurrentPhase("init"); + setPath([]) + removeCurrentAction(armBot.modelUuid) + } - }, [currentPhase, robot, isPlaying, ikSolver]) + }, [currentPhase, armBot, isPlaying, ikSolver, isReset]) function createCurveBetweenTwoPoints(p1: any, p2: any) { const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5); - mid.y += 0.5; - + // mid.y += 0.5; const points = [p1, mid, p2]; return new THREE.CatmullRomCurve3(points); } const HandleCallback = () => { - if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") { - logStatus(robot.modelUuid, "Callback triggered: rest"); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + if (armBot.isActive && armBot.state == "running" && currentPhase == "init-to-rest") { + logStatus(armBot.modelUuid, "Callback triggered: rest"); + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("rest"); setPath([]) } - else if (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") { - logStatus(robot.modelUuid, "Callback triggered: pick."); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + else if (armBot.isActive && armBot.state == "running" && currentPhase == "rest-to-start") { + logStatus(armBot.modelUuid, "Callback triggered: pick."); + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("picking"); setPath([]) } - else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") { - logStatus(robot.modelUuid, "Callback triggered: drop."); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + else if (armBot.isActive && armBot.state == "running" && currentPhase == "start-to-end") { + logStatus(armBot.modelUuid, "Callback triggered: drop."); + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("dropping"); setPath([]) } - else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") { - logStatus(robot.modelUuid, "Callback triggered: rest, cycle completed."); - setArmBotActive(robot.modelUuid, false) - setArmBotState(robot.modelUuid, "idle") + else if (armBot.isActive && armBot.state == "running" && currentPhase == "end-to-rest") { + logStatus(armBot.modelUuid, "Callback triggered: rest, cycle completed."); + setArmBotActive(armBot.modelUuid, false) + setArmBotState(armBot.modelUuid, "idle") setCurrentPhase("rest"); setPath([]) - removeCurrentAction(robot.modelUuid) + removeCurrentAction(armBot.modelUuid) } } const logStatus = (id: string, status: string) => { // - + } return ( <> - - - + + ) } -export default RoboticArmInstance; \ No newline at end of file +export default RoboticArmInstance; diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index 645cbb5..4a8947b 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -3,20 +3,19 @@ import * as THREE from "three"; import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import { clone } from "three/examples/jsm/utils/SkeletonUtils"; -import { useFrame, useLoader, useThree } from "@react-three/fiber"; +import { useLoader, useThree } from "@react-three/fiber"; import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver"; +import { TransformControls } from '@react-three/drei'; + type IKInstanceProps = { modelUrl: string; ikSolver: any; setIkSolver: any - robot: any; - groupRef: React.RefObject; - processes: any; - setArmBotCurvePoints: any + armBot: any; + groupRef: any; }; -function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) { - - const { scene } = useThree(); +function IKInstance({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKInstanceProps) { + const { scene } = useThree() const gltf = useLoader(GLTFLoader, modelUrl, (loader) => { const draco = new DRACOLoader(); draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/"); @@ -25,6 +24,8 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe const cloned = useMemo(() => clone(gltf?.scene), [gltf]); const targetBoneName = "Target"; const skinnedMeshName = "link_0"; + const [selectedArm, setSelectedArm] = useState(); + useEffect(() => { if (!gltf) return; const OOI: any = {}; @@ -67,14 +68,17 @@ function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processe const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05) - // scene.add(helper) + setSelectedArm(OOI.Target_Bone); + // scene.add(helper); - }, [gltf]); + }, [cloned, gltf, setIkSolver]); return ( <> - + { + setSelectedArm(groupRef.current?.getObjectByName(targetBoneName)) + }}> + {/* {selectedArm && } */} ) } diff --git a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx index 1089fa5..0acc1d9 100644 --- a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx +++ b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx @@ -1,4 +1,3 @@ -import React from 'react' import RoboticArmInstance from './armInstance/roboticArmInstance'; import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; @@ -8,9 +7,8 @@ function RoboticArmInstances() { return ( <> {armBots?.map((robot: ArmBotStatus) => ( - + ))} - ) } diff --git a/app/src/modules/simulation/roboticArm/roboticArm.tsx b/app/src/modules/simulation/roboticArm/roboticArm.tsx index 69e6da0..446d1b0 100644 --- a/app/src/modules/simulation/roboticArm/roboticArm.tsx +++ b/app/src/modules/simulation/roboticArm/roboticArm.tsx @@ -1,177 +1,46 @@ import { useEffect } from "react"; import RoboticArmInstances from "./instances/roboticArmInstances"; import { useArmBotStore } from "../../../store/simulation/useArmBotStore"; -import { useFloorItems } from "../../../store/store"; +import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../store/simulation/useProductStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import ArmBotUI from "../ui/arm/armBotUI"; function RoboticArm() { - const { armBots, addArmBot, removeArmBot } = useArmBotStore(); - const { floorItems } = useFloorItems(); + const { armBots, addArmBot, clearArmBots } = useArmBotStore(); + const { products, getProductById } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { selectedEventData } = useSelectedEventData(); + const { isPlaying } = usePlayButtonStore(); + useEffect(() => { + if (selectedProduct.productId) { + const product = getProductById(selectedProduct.productId); + if (product) { + clearArmBots(); + product.eventDatas.forEach(events => { + if (events.type === 'roboticArm') { + addArmBot(selectedProduct.productId, events); + } + }); + } + } + }, [selectedProduct, products]); - const armBotStatusSample: RoboticArmEventSchema[] = [ - { - state: "idle", - modelUuid: "3abf5d46-b59e-4e6b-9c02-a4634b64b82d", - modelName: "ArmBot-X200", - position: [0.20849215906958463, 0, 0.32079278127773675], - rotation: [-1.3768690876192207e-15, 1.4883085074751308, 1.5407776675834467e-15], - type: "roboticArm", - speed: 1.5, - point: { - uuid: "point-123", - position: [0, 2.6, 0], - rotation: [0, 0, 0], - actions: [ - { - actionUuid: "action-003", - actionName: "Pick Component", - actionType: "pickAndPlace", - process: { - startPoint: [-1, 2, 1], - endPoint: [-2, 1, -1], - }, - // process: { - // "startPoint": [ - // 0.37114476008711866, - // 1.9999999999999998, - // 1.8418816116721384 - // ], - // "endPoint": [ - // -0.42197069459490777, - // 1, - // -3.159515927851809 - // ] - // }, - triggers: [ - { - triggerUuid: "trigger-001", - triggerName: "Start Trigger", - triggerType: "onStart", - delay: 0, - triggeredAsset: { - triggeredModel: { - modelName: "Conveyor A1", - modelUuid: "conveyor-01", - }, - triggeredPoint: { - pointName: "Start Point", - pointUuid: "conveyor-01-point-001", - }, - triggeredAction: { - actionName: "Move Forward", - actionUuid: "conveyor-action-01", - }, - }, - }, - { - triggerUuid: "trigger-002", - triggerName: "Complete Trigger", - triggerType: "onComplete", - delay: 0, - triggeredAsset: { - triggeredModel: { - modelName: "StaticMachine B2", - modelUuid: "machine-02", - }, - triggeredPoint: { - pointName: "Receive Point", - pointUuid: "machine-02-point-001", - }, - triggeredAction: { - actionName: "Process Part", - actionUuid: "machine-action-01", - }, - }, - }, - ], - }, - ], - }, - }, - { - state: "idle", - modelUuid: "armbot-xyz-002", - modelName: "ArmBot-X200", - position: [95.94347308985614, 0, 6.742905194869091], - rotation: [0, 0, 0], - type: "roboticArm", - speed: 1.5, - point: { - uuid: "point-123", - position: [0, 1.5, 0], - rotation: [0, 0, 0], - actions: [ - { - actionUuid: "action-001", - actionName: "Pick Component", - actionType: "pickAndPlace", - process: { - startPoint: [2.52543010919071, 0, 8.433681161200905], - endPoint: [95.3438373267953, 0, 9.0279187421610025], - }, - triggers: [ - { - triggerUuid: "trigger-001", - triggerName: "Start Trigger", - triggerType: "onStart", - delay: 0, - triggeredAsset: { - triggeredModel: { - modelName: "Conveyor A1", - modelUuid: "conveyor-01", - }, - triggeredPoint: { - pointName: "Start Point", - pointUuid: "conveyor-01-point-001", - }, - triggeredAction: { - actionName: "Move Forward", - actionUuid: "conveyor-action-01", - }, - }, - }, - { - triggerUuid: "trigger-002", - triggerName: "Complete Trigger", - triggerType: "onComplete", - delay: 0, - triggeredAsset: { - triggeredModel: { - modelName: "StaticMachine B2", - modelUuid: "machine-02", - }, - triggeredPoint: { - pointName: "Receive Point", - pointUuid: "machine-02-point-001", - }, - triggeredAction: { - actionName: "Process Part", - actionUuid: "machine-action-01", - }, - }, - }, - ], - }, - ], - }, - }, - ]; + useEffect(() => { + }, [armBots]) useEffect(() => { - removeArmBot(armBotStatusSample[0].modelUuid); - addArmBot('123', armBotStatusSample[0]); - // addArmBot('123', armBotStatusSample[1]); - // addCurrentAction('armbot-xyz-001', 'action-001'); - }, []); - - useEffect(() => { - - }, [armBots]); + }, [selectedEventData, selectedEventSphere, isPlaying]); return ( <> + {selectedEventSphere && selectedEventData?.data.type === "roboticArm" && + < ArmBotUI /> + } ); } diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 757a9ef..efacd53 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -29,14 +29,14 @@ function Simulation() { return ( <> + + {activeModule === 'simulation' && <> - - diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx index dcb46f4..f9fc30f 100644 --- a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx +++ b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx @@ -68,7 +68,7 @@ function TriggerConnector() { event.points.forEach(point => { if (point.action?.triggers) { point.action.triggers.forEach(trigger => { - if (trigger.triggeredAsset) { + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, @@ -85,7 +85,7 @@ function TriggerConnector() { const point = event.point; if (point.action?.triggers) { point.action.triggers.forEach(trigger => { - if (trigger.triggeredAsset) { + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, @@ -101,7 +101,7 @@ function TriggerConnector() { const point = event.point; point.actions?.forEach(action => { action.triggers?.forEach(trigger => { - if (trigger.triggeredAsset) { + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, @@ -117,7 +117,7 @@ function TriggerConnector() { const point = event.point; if (point.action?.triggers) { point.action.triggers.forEach(trigger => { - if (trigger.triggeredAsset) { + if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, startPointUuid: point.uuid, diff --git a/app/src/modules/simulation/ui/arm/PickDropPoints.tsx b/app/src/modules/simulation/ui/arm/PickDropPoints.tsx index 4544a46..c44eee8 100644 --- a/app/src/modules/simulation/ui/arm/PickDropPoints.tsx +++ b/app/src/modules/simulation/ui/arm/PickDropPoints.tsx @@ -9,7 +9,7 @@ interface PickDropProps { actionType: "pick" | "drop"; actionUuid: string; gltfScene: THREE.Group; - selectedPoint: THREE.Mesh | null; + handlePointerDown: (e: ThreeEvent) => void; isSelected: boolean; } @@ -21,12 +21,10 @@ const PickDropPoints: React.FC = ({ actionType, actionUuid, gltfScene, - selectedPoint, handlePointerDown, isSelected, }) => { const groupRef = useRef(null); - return ( = ({ : new THREE.Vector3(0, 0, 0) } onPointerDown={(e) => { - e.stopPropagation(); // Important to prevent event bubbling + + e.stopPropagation(); // Prevent event bubbling if (!isSelected) return; handlePointerDown(e); }} userData={{ modelUuid, pointUuid, actionType, actionUuid }} > { + const cloned = gltfScene.clone(); + cloned.traverse((child: any) => { + if (child.isMesh) { + child.userData = { modelUuid, pointUuid, actionType, actionUuid }; + } + }); + return cloned; + })()} + position={[0, 0, 0]} scale={[0.5, 0.5, 0.5]} /> diff --git a/app/src/modules/simulation/ui/arm/armBotUI.tsx b/app/src/modules/simulation/ui/arm/armBotUI.tsx new file mode 100644 index 0000000..11d36e2 --- /dev/null +++ b/app/src/modules/simulation/ui/arm/armBotUI.tsx @@ -0,0 +1,219 @@ +import React, { useEffect, useState } from 'react'; +import { useSelectedAction, useSelectedEventData, useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { useGLTF } from '@react-three/drei'; +import { useThree } from '@react-three/fiber'; +import { useProductStore } from '../../../../store/simulation/useProductStore'; +import PickDropPoints from './PickDropPoints'; +import useDraggableGLTF from './useDraggableGLTF'; +import * as THREE from 'three'; + +import armPick from "../../../../assets/gltf-glb/arm_ui_pick.glb"; +import armDrop from "../../../../assets/gltf-glb/arm_ui_drop.glb"; +import { upsertProductOrEventApi } from '../../../../services/simulation/UpsertProductOrEventApi'; + +type Positions = { + pick: [number, number, number]; + drop: [number, number, number]; + default: [number, number, number]; +}; + +const ArmBotUI = () => { + const { getEventByModelUuid, updateAction, getActionByUuid } = useProductStore(); + const { selectedEventData } = useSelectedEventData(); + const { selectedProduct } = useSelectedProduct(); + const { scene } = useThree(); + const { selectedAction } = useSelectedAction(); + + const armUiPick = useGLTF(armPick) as any; + const armUiDrop = useGLTF(armDrop) as any; + + const [startPosition, setStartPosition] = useState<[number, number, number] | null>([0, 0, 0]); + const [endPosition, setEndPosition] = useState<[number, number, number] | null>([0, 0, 0]); + const [selectedArmBotData, setSelectedArmBotData] = useState(null); + + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + + // Fetch and setup selected ArmBot data + useEffect(() => { + if (selectedEventData?.data.type === "roboticArm") { + const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid); + + if (selectedArmBot?.type === "roboticArm") { + setSelectedArmBotData(selectedArmBot); + const defaultPositions = getDefaultPositions(selectedArmBot.modelUuid); + const matchingAction = getActionByUuid(selectedProduct.productId, selectedAction.actionId); + if (matchingAction) { + const startPoint = (matchingAction as RoboticArmPointSchema["actions"][0]).process.startPoint; + const pickPosition = (!startPoint || (Array.isArray(startPoint) && startPoint.every(v => v === 0))) + ? defaultPositions.pick + : startPoint; + + const endPoint = (matchingAction as RoboticArmPointSchema["actions"][0]).process.endPoint; + const dropPosition = (!endPoint || (Array.isArray(endPoint) && endPoint.every(v => v === 0))) + ? defaultPositions.drop + : endPoint; + + setStartPosition(pickPosition); + setEndPosition(dropPosition); + } + } + } + }, [selectedEventData, selectedProduct, getEventByModelUuid, selectedAction]); + + + function getDefaultPositions(modelUuid: string): Positions { + const modelData = getEventByModelUuid(selectedProduct.productId, modelUuid); + + if (modelData?.type === "roboticArm") { + const baseX = modelData.point.position?.[0] || 0; + const baseY = modelData.point.position?.[1] || 0;; + const baseZ = modelData.point.position?.[2] || 0; + return { + pick: [baseX, baseY, baseZ + 0.5], + drop: [baseX, baseY, baseZ - 0.5], + default: [baseX, baseY, baseZ], + }; + } + + return { + pick: [0.5, 1.5, 0], + drop: [-0.5, 1.5, 0], + default: [0, 1.5, 0], + }; + } + + function getLocalPosition(parentUuid: string, worldPosArray: [number, number, number] | null): [number, number, number] | null { + if (worldPosArray) { + const worldPos = new THREE.Vector3(...worldPosArray); + const parentObject = scene.getObjectByProperty('uuid', parentUuid); + + if (parentObject) { + const localPos = worldPos.clone(); + parentObject.worldToLocal(localPos); + return [localPos.x, localPos.y, localPos.z]; + } + } + return null; + } + + const updatePointToState = (obj: THREE.Object3D) => { + const { modelUuid, actionType, actionUuid } = obj.userData; + const newPosition = new THREE.Vector3(); + obj.getWorldPosition(newPosition); + const worldPositionArray = newPosition.toArray() as [number, number, number]; + + if (selectedEventData?.data.type === "roboticArm") { + const selectedArmBot = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid); + + const armBot = selectedArmBot?.modelUuid === modelUuid ? selectedArmBot : null; + if (!armBot) return; + + if (armBot.type === "roboticArm") { + armBot?.point?.actions?.map((action) => { + if (action.actionUuid === actionUuid) { + const updatedProcess = { ...action.process }; + + if (actionType === "pick") { + updatedProcess.startPoint = getLocalPosition(modelUuid, worldPositionArray); + setStartPosition(updatedProcess.startPoint) + + } else if (actionType === "drop") { + updatedProcess.endPoint = getLocalPosition(modelUuid, worldPositionArray); + setEndPosition(updatedProcess.endPoint) + } + + const event = updateAction(selectedProduct.productId, + actionUuid, + { + actionUuid: action.actionUuid, + process: updatedProcess, + } + ) + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + return { + ...action, + process: updatedProcess, + }; + } + return action; + }); + + } + } + } + + const { handlePointerDown } = useDraggableGLTF(updatePointToState); + + if (!selectedArmBotData || !Array.isArray(selectedArmBotData.point?.actions)) { + return null; // avoid rendering if no data yet + } + return ( + <> + {selectedArmBotData.point.actions.map((action: any) => { + if (action.actionUuid === selectedAction.actionId) { + return ( + + + {startPosition && endPosition && ( + <> + + + + )} + + + ); + } else { + return null; // important! must return something + } + })} + + ); + +}; + +export default ArmBotUI; diff --git a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts index b7e9272..e450bfd 100644 --- a/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts +++ b/app/src/modules/simulation/ui/arm/useDraggableGLTF.ts @@ -1,11 +1,11 @@ -import { useRef } from "react"; +import { useRef, useState } from "react"; import * as THREE from "three"; import { ThreeEvent, useThree } from "@react-three/fiber"; type OnUpdateCallback = (object: THREE.Object3D) => void; export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { - const { camera, gl, controls, scene } = useThree(); + const { camera, gl, controls } = useThree(); const activeObjRef = useRef(null); const planeRef = useRef( new THREE.Plane(new THREE.Vector3(0, 1, 0), 0) @@ -15,6 +15,7 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { const raycaster = new THREE.Raycaster(); const pointer = new THREE.Vector2(); + const [objectWorldPos, setObjectWorldPos] = useState(new THREE.Vector3()); const handlePointerDown = (e: ThreeEvent) => { e.stopPropagation(); @@ -33,10 +34,10 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { activeObjRef.current = obj; initialPositionRef.current.copy(obj.position); + // Get world position - const objectWorldPos = new THREE.Vector3(); - obj.getWorldPosition(objectWorldPos); + setObjectWorldPos(obj.getWorldPosition(objectWorldPos)); // Set plane at the object's Y level planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y); @@ -61,52 +62,56 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { const handlePointerMove = (e: PointerEvent) => { if (!activeObjRef.current) return; - + // Check if Shift key is pressed const isShiftKeyPressed = e.shiftKey; - + // Get the mouse position relative to the canvas const rect = gl.domElement.getBoundingClientRect(); pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; - + // Update raycaster to point to the mouse position raycaster.setFromCamera(pointer, camera); - + // Create a vector to store intersection point const intersection = new THREE.Vector3(); - const intersects = raycaster.ray.intersectPlane(planeRef.current, intersection); + const intersects = raycaster.ray.intersectPlane( + planeRef.current, + intersection + ); if (!intersects) return; - + // Add offset for dragging intersection.add(offsetRef.current); - console.log('intersection: ', intersection); - + // Get the parent's world matrix if exists const parent = activeObjRef.current.parent; const targetPosition = new THREE.Vector3(); - + + // OnPointerDown + initialPositionRef.current.copy(objectWorldPos); + + // OnPointerMove if (isShiftKeyPressed) { - console.log('isShiftKeyPressed: ', isShiftKeyPressed); - // For Y-axis only movement, maintain original X and Z - console.log('initialPositionRef: ', initialPositionRef); - console.log('intersection.y: ', intersection); - targetPosition.set( - initialPositionRef.current.x, - intersection.y, - initialPositionRef.current.z - ); + const { x: initialX, y: initialY } = initialPositionRef.current; + const { x: objectX, z: objectZ } = objectWorldPos; + + const deltaX = intersection.x - initialX; + + targetPosition.set(objectX, initialY + deltaX, objectZ); } else { // For free movement targetPosition.copy(intersection); } - + // Convert world position to local if object is nested inside a parent if (parent) { parent.worldToLocal(targetPosition); } - + // Update object position + activeObjRef.current.position.copy(targetPosition); }; @@ -126,6 +131,3 @@ export default function useDraggableGLTF(onUpdate: OnUpdateCallback) { return { handlePointerDown }; } - - - diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 9ca7355..aeadc4b 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -25,9 +25,9 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai const completedRef = useRef(false); const isPausedRef = useRef(false); const pauseTimeRef = useRef(null); + const [progress, setProgress] = useState(0); const [restRotation, setRestingRotation] = useState(true); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); - const [progress, setProgress] = useState(0); const { scene } = useThree(); let startTime: number; let fixedInterval: number; @@ -66,6 +66,8 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai setReset(false); setRestingRotation(true); decrementVehicleLoad(agvDetail.modelUuid, 0); + isPausedRef.current = false; + pauseTimeRef.current = 0; const object = scene.getObjectByProperty('uuid', agvUuid); if (object) { object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]); diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 91111cf..fcc840d 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -9,10 +9,8 @@ function VehicleInstances() { return ( <> - {vehicles.map((val: any, i: any) => - - - + {vehicles.map((val: VehicleStatus) => + )} diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 19f049c..3fbc8ef 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -29,7 +29,7 @@ function Vehicles() { }, [selectedProduct, products]); useEffect(() => { - // console.log('vehicles: ', vehicles); + // }, [vehicles]) return ( diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx index a8f44b4..2e1e536 100644 --- a/app/src/modules/visualization/RealTimeVisulization.tsx +++ b/app/src/modules/visualization/RealTimeVisulization.tsx @@ -249,7 +249,7 @@ const RealTimeVisulization: React.FC = () => { useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const editWidgetOptions = document.querySelector( - ".editWidgetOptions-wrapper" + ".context-menu-options-wrapper" ); if ( editWidgetOptions && diff --git a/app/src/services/simulation/renameProductApi.ts b/app/src/services/simulation/renameProductApi.ts new file mode 100644 index 0000000..afa493c --- /dev/null +++ b/app/src/services/simulation/renameProductApi.ts @@ -0,0 +1,26 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const renameProductApi = async (body: { productName: string, productId: string, organization: string }) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/productRename`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to rename product"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index b7448f2..3fbaa76 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -40,7 +40,7 @@ type ProductsStore = { updates: Partial ) => EventsSchema | undefined; - // Trigger-level actions + // Trigger-level actionss addTrigger: ( productId: string, actionUuid: string, @@ -51,7 +51,7 @@ type ProductsStore = { productId: string, triggerUuid: string, updates: Partial - ) => void; + ) => EventsSchema | undefined; // Renaming functions renameProduct: (productId: string, newName: string) => void; @@ -392,6 +392,7 @@ export const useProductStore = create()( }, updateTrigger: (productId, triggerUuid, updates) => { + let updatedEvent: EventsSchema | undefined; set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { @@ -402,6 +403,7 @@ export const useProductStore = create()( const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); if (trigger) { Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); return; } } @@ -412,6 +414,7 @@ export const useProductStore = create()( const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); if (trigger) { Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); return; } } else if ('actions' in point) { @@ -420,6 +423,7 @@ export const useProductStore = create()( const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); if (trigger) { Object.assign(trigger, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); return; } } @@ -429,6 +433,7 @@ export const useProductStore = create()( } } }); + return updatedEvent; }, // Renaming functions diff --git a/app/src/styles/components/form.scss b/app/src/styles/components/form.scss index e69de29..67accd2 100644 --- a/app/src/styles/components/form.scss +++ b/app/src/styles/components/form.scss @@ -0,0 +1,26 @@ +@use "../abstracts/variables" as *; +@use "../abstracts/mixins" as *; + +.rename-tool-tip { + position: absolute; + background: var(--background-color); + padding: 10px 16px; + width: 260px; + border-radius: #{$border-radius-large}; + outline: 1px solid var(--border-color); + z-index: 100; + .header { + @include flex-center; + gap: 8px; + .icon { + @include flex-center; + } + .name { + color: var(--text-color); + } + input { + width: 100%; + margin-top: 6px; + } + } +} diff --git a/app/src/styles/components/lists.scss b/app/src/styles/components/lists.scss index 894eac1..27ccda8 100644 --- a/app/src/styles/components/lists.scss +++ b/app/src/styles/components/lists.scss @@ -2,8 +2,6 @@ @use "../abstracts/mixins" as *; .dropdown-list-container { - border-bottom: 1px solid var(--border-color); - .lists-container { margin-bottom: 6px; } @@ -44,7 +42,7 @@ text-align: center; padding: 4px 8px; border-radius: #{$border-radius-large}; - .zone-header{ + .zone-header { @include flex-center; .value { width: 100%; @@ -62,16 +60,10 @@ cursor: pointer; } } - &:first-child{ - background: var(--highlight-accent-color); - .input-value{ - color: var(--highlight-text-color); - } - } } .active { background: var(--highlight-accent-color); - .input-value{ + .input-value { color: var(--highlight-text-color); } } diff --git a/app/src/styles/components/simulation/simulation.scss b/app/src/styles/components/simulation/simulation.scss index 88f85dc..9b6c93d 100644 --- a/app/src/styles/components/simulation/simulation.scss +++ b/app/src/styles/components/simulation/simulation.scss @@ -3,7 +3,7 @@ .simulation-player-wrapper { position: fixed; - bottom: 12px; + bottom: 32px; left: 50%; z-index: 2; transform: translate(-50%, 0); @@ -35,11 +35,11 @@ @include flex-space-between; gap: 12px; justify-content: space-between; - .header{ + .header { @include flex-center; gap: 6px; padding: 0 8px; - svg{ + svg { scale: 1.3; } } @@ -299,6 +299,37 @@ } } } + + .open { + .start-displayer, + .end-displayer { + display: none; + } + + .timmer { + display: none; + } + .progresser-wrapper { + padding-top: 4px; + } + + .time-displayer { + height: 0; + opacity: 0; + pointer-events: none; + display: none; + } + + .processDisplayer { + padding: 0 8px; + background: transparent; + + .process-player { + width: 0; + display: none !important; + } + } + } } .processDisplayer { @@ -314,22 +345,6 @@ font-size: var(--font-size-tiny); } - .timmer { - width: auto; - position: absolute; - bottom: 0; - font-size: var(--font-size-tiny); - } - - .start-displayer { - left: 8px; - } - - .end-displayer { - width: auto; - right: 8px; - } - .start-displayer { bottom: 4px; left: 16px; @@ -351,12 +366,12 @@ border-width: 1px; background: var(--background-color-accent, #6f42c1); } - .process-wrapper{ + .process-wrapper { .process-container { position: relative; display: flex; width: 100%; - + .process { height: 5px; border-radius: 4px; @@ -368,35 +383,3 @@ } } } - -.simulation-player-container.open { - - .start-displayer, - .end-displayer { - display: none; - } - - .timmer { - display: none; - } - .progresser-wrapper { - padding-top: 4px; - } - - .time-displayer { - height: 0; - opacity: 0; - pointer-events: none; - display: none; - } - - .processDisplayer { - padding: 0 8px; - background: transparent; - - .process-player { - width: 0; - display: none !important; - } - } -} diff --git a/app/src/styles/components/tools.scss b/app/src/styles/components/tools.scss index 29d37b4..902e0fb 100644 --- a/app/src/styles/components/tools.scss +++ b/app/src/styles/components/tools.scss @@ -14,7 +14,7 @@ width: fit-content; transition: width 0.2s; background: var(--background-color); - backdrop-filter: blur(8px); + backdrop-filter: blur(20px); z-index: 2; outline: 1px solid var(--border-color); outline-offset: -1px; @@ -31,6 +31,7 @@ .activeDropicon { @include flex-center; gap: 2px; + // stylelint-disable-next-line interpolate-size: allow-keywords; width: 0; opacity: 0; @@ -44,9 +45,11 @@ border-radius: #{$border-radius-medium}; &:hover { - background: color-mix(in srgb, - var(--highlight-accent-color) 60%, - transparent); + background: color-mix( + in srgb, + var(--highlight-accent-color) 60%, + transparent + ); } } @@ -70,9 +73,11 @@ position: relative; &:hover { - background: color-mix(in srgb, - var(--highlight-accent-color) 60%, - transparent); + background: color-mix( + in srgb, + var(--highlight-accent-color) 60%, + transparent + ); } .drop-down-container { @@ -179,11 +184,11 @@ font-weight: 500; background: var(--accent-color); color: var(--highlight-accent-color); - &::after{ + &::after { animation: pulse 1s ease-out infinite; } } - &::after{ + &::after { content: ""; position: absolute; height: 100%; @@ -195,14 +200,14 @@ } @keyframes pulse { - 0%{ + 0% { opacity: 0; - scale: .5; + scale: 0.5; } - 50%{ + 50% { opacity: 1; } - 100%{ + 100% { opacity: 0; scale: 2; } @@ -218,4 +223,4 @@ width: fit-content; opacity: 1; } -} \ No newline at end of file +} diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index f8c022f..01256a4 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -7,7 +7,7 @@ top: 32px; left: 8px; background: var(--background-color); - backdrop-filter: blur(15px); + backdrop-filter: blur(20px); border-radius: #{$border-radius-extra-large}; outline: 1px solid var(--border-color); box-shadow: #{$box-shadow-medium}; @@ -250,7 +250,7 @@ top: 32px; right: 8px; background: var(--background-color); - backdrop-filter: blur(15px); + backdrop-filter: blur(20px); border-radius: #{$border-radius-extra-large}; outline: 1px solid var(--border-color); box-shadow: #{$box-shadow-medium}; @@ -363,7 +363,7 @@ height: 34px; width: 34px; border-radius: #{$border-radius-circle}; - background: var(--background-color-secondary); + background: var(--background-color-solid-gradient); backdrop-filter: blur(12px); outline: 1px solid var(--border-color); outline-offset: -1px; @@ -416,7 +416,7 @@ outline: none; path { stroke: var(--text-button-color); - strokeWidth: 1.3; + stroke-width: 1.3; } } } @@ -682,7 +682,7 @@ path { stroke: var(--accent-color); - strokeWidth: 1.5px; + stroke-width: 1.5px; } &:hover { diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index e0b31c6..09b73a9 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -740,10 +740,10 @@ outline-color: #ffe3e0; } -.editWidgetOptions { +.context-menu-options { position: absolute; background: var(--background-color); - backdrop-filter: blur(10px); + backdrop-filter: blur(20px); z-index: 3; display: flex; flex-direction: column; @@ -751,6 +751,7 @@ overflow: hidden; padding: 4px; min-width: 150px; + outline: 1px solid var(--border-color); .option { padding: 4px 10px; diff --git a/app/src/styles/pages/userAuth.scss b/app/src/styles/pages/userAuth.scss index 9d07dcf..c27b66f 100644 --- a/app/src/styles/pages/userAuth.scss +++ b/app/src/styles/pages/userAuth.scss @@ -48,16 +48,16 @@ max-width: 350px; padding: 10px; margin-bottom: 20px; - border: 1px solid var(--accent-color); + border: 1px solid var(--highlight-text-color); border-radius: #{$border-radius-extra-large}; background: transparent; - color: var(--accent-color); + color: var(--highlight-text-color); font-size: 14px; outline: none; cursor: pointer; .google-icon { - color: var(--accent-color); + color: var(--highlight-text-color); font-weight: bold; font-size: 16px; margin-right: 10px; @@ -120,7 +120,7 @@ .continue-button { width: 100%; padding: 10px; - background: var(--accent-gradient-color); + background: var(--background-color-button); color: var(--background-color); font-size: 14px; border: none; @@ -158,7 +158,7 @@ text-align: center; .link { - color: var(--accent-color); + color: var(--highlight-text-color); text-decoration: none; &:hover { text-decoration: underline; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index c7ccd8b..11d5156 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -13,7 +13,7 @@ interface TriggerSchema { delay: number; triggeredAsset: { triggeredModel: { modelName: string, modelUuid: string }; - triggeredPoint: { pointName: string, pointUuid: string }; + triggeredPoint: { pointName: string, pointUuid: string } | null; triggeredAction: { actionName: string, actionUuid: string } | null; } | null; } diff --git a/app/src/utils/handleSnap.ts b/app/src/utils/handleSnap.ts new file mode 100644 index 0000000..bd6a74d --- /dev/null +++ b/app/src/utils/handleSnap.ts @@ -0,0 +1,22 @@ +export function snapControls(value: number, event: string): number { + const CTRL_DISTANCE = 1; // Snap to whole numbers when Ctrl is pressed + const SHIFT_DISTANCE = 0.01; // Snap to half-step increments when Shift is pressed + const CTRL_SHIFT_DISTANCE = 0.1; // Snap to fine increments when both Ctrl and Shift are pressed + + switch (event) { + case "Ctrl": + return Math.round(value / CTRL_DISTANCE) * CTRL_DISTANCE; + + case "Shift": + return Math.round(value / SHIFT_DISTANCE) * SHIFT_DISTANCE; + + case "Ctrl+Shift": + const base = Math.floor(value / CTRL_DISTANCE) * CTRL_DISTANCE; + const offset = + Math.round((value - base) / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE; + return base + offset; + + default: + return value; // No snapping if no modifier key is pressed + } +}