Merge pull request 'simulation' (#66) from simulation into main

Reviewed-on: http://185.100.212.76:7776/Dwinzo-Beta/Dwinzo_dev/pulls/66
This commit was merged in pull request #66.
This commit is contained in:
2025-04-16 13:05:20 +00:00
35 changed files with 2121 additions and 2150 deletions

View File

@@ -73,7 +73,7 @@ const Assets: React.FC = () => {
try { try {
const filt = await fetchAssets(); const filt = await fetchAssets();
setFiltereredAssets(filt); setFiltereredAssets(filt);
} catch {} } catch { }
}; };
filteredAssets(); filteredAssets();
}, [categoryAssets]); }, [categoryAssets]);
@@ -135,7 +135,7 @@ const Assets: React.FC = () => {
const res = await getCategoryAsset(asset); const res = await getCategoryAsset(asset);
setCategoryAssets(res); setCategoryAssets(res);
setFiltereredAssets(res); setFiltereredAssets(res);
} catch (error) {} } catch (error) { }
} }
}; };
return ( return (
@@ -234,6 +234,7 @@ const Assets: React.FC = () => {
src={categoryInfo?.categoryImage || ""} src={categoryInfo?.categoryImage || ""}
alt={category} alt={category}
className="category-image" className="category-image"
draggable={false}
/> />
<div className="category-name">{category}</div> <div className="category-name">{category}</div>
</div> </div>

View File

@@ -62,19 +62,25 @@ const ZoneProperties: React.FC = () => {
: zone : zone
) )
); );
}else{ } else {
// console.log(response?.message); // console.log(response?.message);
} }
} }
function handleVectorChange(key: "zoneViewPortTarget" | "zoneViewPortPosition", newValue: [number, number, number]) { function handleVectorChange(key: "zoneViewPortTarget" | "zoneViewPortPosition", newValue: [number, number, number]) {
setSelectedZone((prev) => ({ ...prev, [key]: newValue })); setSelectedZone((prev) => ({ ...prev, [key]: newValue }));
} }
const checkZoneNameDuplicate = (name: string) => {
return zones.some(
(zone: any) =>
zone.zoneName.trim().toLowerCase() === name.trim().toLowerCase() &&
zone.zoneId !== selectedZone.zoneId
);
};
return ( return (
<div className="zone-properties-container"> <div className="zone-properties-container">
<div className="header"> <div className="header">
<RenameInput value={selectedZone.zoneName} onRename={handleZoneNameChange} /> <RenameInput value={selectedZone.zoneName} onRename={handleZoneNameChange} checkDuplicate={checkZoneNameDuplicate} />
<div className="button" onClick={handleEditView}> <div className="button" onClick={handleEditView}>
{Edit ? "Cancel" : "Edit"} {Edit ? "Cancel" : "Edit"}
</div> </div>

View File

@@ -62,7 +62,7 @@ const BarChartInput = (props: Props) => {
fetchSavedInputes(); fetchSavedInputes();
}, [selectedChartId.id]); }, [selectedChartId]);
// Sync Zustand state when component mounts // Sync Zustand state when component mounts
useEffect(() => { useEffect(() => {
@@ -138,7 +138,7 @@ const BarChartInput = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.title || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.title} onRename={handleNameChange}/>
</div> </div>
{[...Array(3)].map((_, index) => { {[...Array(3)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -152,6 +152,7 @@ const BarChartInput = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -23,6 +23,10 @@ const FleetEfficiencyInputComponent = (props: Props) => {
const organization = email?.split("@")[1]?.split(".")[0] const organization = email?.split("@")[1]?.split(".")[0]
const [isLoading, setLoading] = useState<boolean>(true); const [isLoading, setLoading] = useState<boolean>(true);
const isSelected = () => {
}
useEffect(() => { useEffect(() => {
const fetchZoneData = async () => { const fetchZoneData = async () => {
try { try {
@@ -139,7 +143,7 @@ const FleetEfficiencyInputComponent = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.header || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.header} onRename={handleNameChange}/>
</div> </div>
{[...Array(1)].map((_, index) => { {[...Array(1)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -153,6 +157,7 @@ const FleetEfficiencyInputComponent = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -139,7 +139,7 @@ const FlotingWidgetInput = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.header || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.header} onRename={handleNameChange}/>
</div> </div>
{[...Array(6)].map((_, index) => { {[...Array(6)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -153,6 +153,7 @@ const FlotingWidgetInput = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -257,7 +257,7 @@ const LineGrapInput = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.title || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.title} onRename={handleNameChange}/>
</div> </div>
{[...Array(4)].map((_, index) => { {[...Array(4)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -271,6 +271,7 @@ const LineGrapInput = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -138,7 +138,7 @@ const PieChartInput = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.title || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.title} onRename={handleNameChange}/>
</div> </div>
{[...Array(2)].map((_, index) => { {[...Array(2)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -152,6 +152,7 @@ const PieChartInput = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -132,7 +132,7 @@ const Progress1Input = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.title || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.title} onRename={handleNameChange}/>
</div> </div>
{[...Array(1)].map((_, index) => { {[...Array(1)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -146,6 +146,7 @@ const Progress1Input = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -132,7 +132,7 @@ const Progress2Input = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.title || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.title} onRename={handleNameChange}/>
</div> </div>
{[...Array(2)].map((_, index) => { {[...Array(2)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -146,6 +146,7 @@ const Progress2Input = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -139,7 +139,7 @@ const WarehouseThroughputInputComponent = (props: Props) => {
<div className="inputs-wrapper"> <div className="inputs-wrapper">
<div className="datas"> <div className="datas">
<div className="datas__label">Title</div> <div className="datas__label">Title</div>
<RenameInput value={selectedChartId?.header || "untited"} onRename={handleNameChange}/> <RenameInput value={widgetName || selectedChartId?.header} onRename={handleNameChange}/>
</div> </div>
{[...Array(1)].map((_, index) => { {[...Array(1)].map((_, index) => {
const inputKey = `input${index + 1}`; const inputKey = `input${index + 1}`;
@@ -153,6 +153,7 @@ const WarehouseThroughputInputComponent = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -151,6 +151,7 @@ const Widget2InputCard3D = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -144,6 +144,7 @@ const Widget3InputCard3D = () => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -151,6 +151,7 @@ const Widget4InputCard3D = (props: Props) => {
onUnselect={() => handleSelect(inputKey, null)} onUnselect={() => handleSelect(inputKey, null)}
selectedValue={selections[inputKey]} // Load from Zustand selectedValue={selections[inputKey]} // Load from Zustand
isLoading={isLoading} isLoading={isLoading}
allSelections={selections}
/> />
<div className="icon"> <div className="icon">
<AddIcon /> <AddIcon />

View File

@@ -1,145 +1,3 @@
// import React, { useState, useRef, useEffect } from "react";
// // Dropdown Item Component
// const DropdownItem = ({
// label,
// href,
// onClick,
// }: {
// label: string;
// href?: string;
// onClick?: () => void;
// }) => (
// <a
// href={href || "#"}
// className="dropdown-item"
// onClick={(e) => {
// e.preventDefault();
// onClick?.();
// }}
// >
// {label}
// </a>
// );
// // Nested Dropdown Component
// const NestedDropdown = ({
// label,
// children,
// onSelect,
// }: {
// label: string;
// children: React.ReactNode;
// onSelect: (selectedLabel: string) => void;
// }) => {
// const [open, setOpen] = useState(false);
// return (
// <div className="nested-dropdown">
// {/* Dropdown Trigger */}
// <div
// className={`dropdown-trigger ${open ? "open" : ""}`}
// onClick={() => setOpen(!open)} // Toggle submenu on click
// >
// {label} <span className="icon">{open ? "▼" : "▶"}</span>
// </div>
// {/* Submenu */}
// {open && (
// <div className="submenu">
// {React.Children.map(children, (child) => {
// if (React.isValidElement(child)) {
// // Clone the element and pass the `onSelect` prop only if it's expected
// return React.cloneElement(child as React.ReactElement<any>, { onSelect });
// }
// return child; // Return non-element children as-is
// })}
// </div>
// )}
// </div>
// );
// };
// // Recursive Function to Render Nested Data
// const renderNestedData = (
// data: Record<string, any>,
// onSelect: (selectedLabel: string) => void
// ) => {
// return Object.entries(data).map(([key, value]) => {
// if (typeof value === "object" && !Array.isArray(value)) {
// // If the value is an object, render it as a nested dropdown
// return (
// <NestedDropdown key={key} label={key} onSelect={onSelect}>
// {renderNestedData(value, onSelect)}
// </NestedDropdown>
// );
// } else if (Array.isArray(value)) {
// // If the value is an array, render each item as a dropdown item
// return value.map((item, index) => (
// <DropdownItem key={index} label={item} onClick={() => onSelect(item)} />
// ));
// } else {
// // If the value is a simple string, render it as a dropdown item
// return (
// <DropdownItem key={key} label={value} onClick={() => onSelect(value)} />
// );
// }
// });
// };
// // Main Multi-Level Dropdown Component
// const MultiLevelDropdown = ({ data }: { data: Record<string, any> }) => {
// const [open, setOpen] = useState(false);
// const [selectedLabel, setSelectedLabel] = useState("Dropdown trigger");
// const dropdownRef = useRef<HTMLDivElement>(null);
// // Handle outer click to close the dropdown
// useEffect(() => {
// const handleClickOutside = (event: MouseEvent) => {
// if (
// dropdownRef.current &&
// !dropdownRef.current.contains(event.target as Node)
// ) {
// setOpen(false);
// }
// };
// document.addEventListener("mousedown", handleClickOutside);
// return () => {
// document.removeEventListener("mousedown", handleClickOutside);
// };
// }, []);
// // Handle selection of an item
// const handleSelect = (selectedLabel: string) => {
// setSelectedLabel(selectedLabel); // Update the dropdown trigger text
// setOpen(false); // Close the dropdown
// };
// return (
// <div className="multi-level-dropdown" ref={dropdownRef}>
// {/* Dropdown Trigger Button */}
// <button
// className={`dropdown-button ${open ? "open" : ""}`}
// onClick={() => setOpen(!open)} // Toggle main menu on click
// >
// {selectedLabel} <span className="icon">▾</span>
// </button>
// {/* Dropdown Menu */}
// {open && (
// <div className="dropdown-menu">
// <div className="dropdown-content">
// {renderNestedData(data, handleSelect)}
// </div>
// </div>
// )}
// </div>
// );
// };
// export default MultiLevelDropdown;
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { ArrowIcon } from "../../icons/ExportCommonIcons"; import { ArrowIcon } from "../../icons/ExportCommonIcons";
@@ -147,11 +5,19 @@ import { ArrowIcon } from "../../icons/ExportCommonIcons";
const DropdownItem = ({ const DropdownItem = ({
label, label,
onClick, onClick,
disabled = false,
}: { }: {
label: string; label: string;
onClick: () => void; onClick: () => void;
disabled?: boolean;
}) => ( }) => (
<div className="dropdown-item" onClick={onClick}> <div
className={`dropdown-item ${disabled ? "disabled" : ""}`}
onClick={() => {
if (!disabled) onClick();
}}
style={{ cursor: disabled ? "not-allowed": "default", opacity: disabled ? 0.5 : 1 }}
>
{label} {label}
</div> </div>
); );
@@ -161,10 +27,12 @@ const NestedDropdown = ({
label, label,
fields, fields,
onSelect, onSelect,
disabledFields = [],
}: { }: {
label: string; label: string;
fields: string[]; fields: string[];
onSelect: (selectedData: { name: string; fields: string }) => void; onSelect: (selectedData: { name: string; fields: string }) => void;
disabledFields?: string[];
}) => { }) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -184,13 +52,17 @@ const NestedDropdown = ({
</div> </div>
{open && ( {open && (
<div className="submenu"> <div className="submenu">
{fields.map((field) => ( {fields.map((field) => {
<DropdownItem const isDisabled = disabledFields.includes(`${label}-${field}`);
key={field} return (
label={field} <DropdownItem
onClick={() => onSelect({ name: label, fields: field })} key={field}
/> label={field}
))} onClick={() => onSelect({ name: label, fields: field })}
disabled={isDisabled}
/>
);
})}
</div> </div>
)} )}
</div> </div>
@@ -203,6 +75,7 @@ interface MultiLevelDropdownProps {
onSelect: (selectedData: { name: string; fields: string }) => void; onSelect: (selectedData: { name: string; fields: string }) => void;
onUnselect: () => void; onUnselect: () => void;
selectedValue?: { name: string; fields: string }; selectedValue?: { name: string; fields: string };
allSelections?: Record<string, { name: string; fields: string }>;
isLoading?: boolean; isLoading?: boolean;
} }
@@ -212,6 +85,7 @@ const MultiLevelDropdown = ({
onSelect, onSelect,
onUnselect, onUnselect,
selectedValue, selectedValue,
allSelections = {},
isLoading = false, isLoading = false,
}: MultiLevelDropdownProps) => { }: MultiLevelDropdownProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -249,6 +123,14 @@ const MultiLevelDropdown = ({
? `${selectedValue.name} - ${selectedValue.fields}` ? `${selectedValue.name} - ${selectedValue.fields}`
: "Dropdown trigger"; : "Dropdown trigger";
// Build list of disabled selections
const disabledFieldsList = Object.values(allSelections)
.filter(
(sel) =>
!(sel.name === selectedValue?.name && sel.fields === selectedValue?.fields)
)
.map((sel) => `${sel.name}-${sel.fields}`);
return ( return (
<div className="multi-level-dropdown" ref={dropdownRef}> <div className="multi-level-dropdown" ref={dropdownRef}>
<button <button
@@ -260,25 +142,23 @@ const MultiLevelDropdown = ({
</button> </button>
{open && ( {open && (
<div className="dropdown-menu"> <div className="dropdown-menu">
<div className="dropdown-content "> <div className="dropdown-content">
{isLoading ? (
{/* loading list */} <div className="loading" />
) : (
<>
{/* Unselect Option */} <DropdownItem label="Unselect" onClick={handleItemUnselect} />
<DropdownItem label="Unselect" onClick={handleItemUnselect} /> {Object.entries(data).map(([key, value]) => (
{/* Nested Dropdown Items */} <NestedDropdown
{ key={key}
isLoading ? <div className="loading" /> : label={key}
Object.entries(data).map(([key, value]) => ( fields={Object.keys(value)}
<NestedDropdown onSelect={handleItemSelect}
key={key} disabledFields={disabledFieldsList}
label={key} />
fields={Object.keys(value)} ))}
onSelect={handleItemSelect} </>
/> )}
))
}
</div> </div>
</div> </div>
)} )}

View File

@@ -1,26 +1,42 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
// interface RenameInputProps {
// value: string;
// onRename?: (newText: string) => void;
// }
interface RenameInputProps { interface RenameInputProps {
value: string; value: string;
onRename?: (newText: string) => void; onRename?: (newText: string) => void;
checkDuplicate?: (name: string) => boolean;
} }
const RenameInput: React.FC<RenameInputProps> = ({ value, onRename }) => { const RenameInput: React.FC<RenameInputProps> = ({ value, onRename, checkDuplicate }) => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [text, setText] = useState(value); const [text, setText] = useState(value);
const [isDuplicate, setIsDuplicate] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null); const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => { useEffect(() => {
setText(value); // Ensure state updates when parent value changes setText(value);
}, [value]); }, [value]);
useEffect(() => {
if (checkDuplicate) {
setIsDuplicate(checkDuplicate(text));
}
}, [text, checkDuplicate]);
const handleDoubleClick = () => { const handleDoubleClick = () => {
setIsEditing(true); setIsEditing(true);
setTimeout(() => inputRef.current?.focus(), 0); // Focus the input after rendering setTimeout(() => inputRef.current?.focus(), 0);
}; };
const handleBlur = () => { const handleBlur = () => {
if(isDuplicate) return
setIsEditing(false); setIsEditing(false);
if (onRename) { if (onRename && !isDuplicate) {
onRename(text); onRename(text);
} }
}; };
@@ -30,7 +46,7 @@ const RenameInput: React.FC<RenameInputProps> = ({ value, onRename }) => {
}; };
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") { if (e.key === "Enter" && !isDuplicate) {
setIsEditing(false); setIsEditing(false);
if (onRename) { if (onRename) {
onRename(text); onRename(text);
@@ -41,15 +57,18 @@ const RenameInput: React.FC<RenameInputProps> = ({ value, onRename }) => {
return ( return (
<> <>
{isEditing ? ( {isEditing ? (
<input <>
ref={inputRef} <input
type="text" ref={inputRef}
value={text} type="text"
onChange={handleChange} value={text}
onBlur={handleBlur} onChange={handleChange}
onKeyDown={handleKeyDown} onBlur={handleBlur}
className="rename-input" onKeyDown={handleKeyDown}
/> className={`rename-input ${isDuplicate ? "input-error" : ""}`}
/>
{/* {isDuplicate && <div className="error-msg">Name already exists!</div>} */}
</>
) : ( ) : (
<span onDoubleClick={handleDoubleClick} className="input-value"> <span onDoubleClick={handleDoubleClick} className="input-value">
{text} {text}
@@ -58,5 +77,4 @@ const RenameInput: React.FC<RenameInputProps> = ({ value, onRename }) => {
</> </>
); );
}; };
export default RenameInput
export default RenameInput;

View File

@@ -13,7 +13,7 @@ import {
RmoveIcon, RmoveIcon,
} from "../../icons/ExportCommonIcons"; } from "../../icons/ExportCommonIcons";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { useFloorItems, useZoneAssetId } from "../../../store/store"; import { useFloorItems, useZoneAssetId, useZones } from "../../../store/store";
import { zoneCameraUpdate } from "../../../services/realTimeVisulization/zoneData/zoneCameraUpdation"; import { zoneCameraUpdate } from "../../../services/realTimeVisulization/zoneData/zoneCameraUpdation";
import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi"; import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
@@ -40,7 +40,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
const { activeModule, setActiveModule } = useModuleStore(); const { activeModule, setActiveModule } = useModuleStore();
const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore();
const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { zones, setZones } = useZones();
const { setSubModule } = useSubModuleStore(); const { setSubModule } = useSubModuleStore();
const [expandedZones, setExpandedZones] = useState<Record<string, boolean>>( const [expandedZones, setExpandedZones] = useState<Record<string, boolean>>(
{} {}
@@ -100,19 +100,33 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
function handleAssetClick(asset: Asset) { function handleAssetClick(asset: Asset) {
setZoneAssetId(asset) setZoneAssetId(asset)
} }
async function handleZoneNameChange(newName: string) { async function handleZoneNameChange(newName: string) {
//zone apiiiiii
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0]; const organization = email?.split("@")[1]?.split(".")[0];
let zonesdata = {
const isDuplicate = zones.some(
(zone: any) =>
zone.zoneName.trim().toLowerCase() === newName.trim().toLowerCase() &&
zone.zoneId !== selectedZone.zoneId
);
if (isDuplicate) {
alert("Zone name already exists. Please choose a different name.");
return; // DO NOT update state
}
const zonesdata = {
zoneId: selectedZone.zoneId, zoneId: selectedZone.zoneId,
zoneName: newName zoneName: newName,
}; };
let response = await zoneCameraUpdate(zonesdata, organization);
const response = await zoneCameraUpdate(zonesdata, organization);
if (response.message === "updated successfully") { if (response.message === "updated successfully") {
setSelectedZone((prev) => ({ ...prev, zoneName: newName })); setSelectedZone((prev) => ({ ...prev, zoneName: newName }));
} }
} }
async function handleZoneAssetName(newName: string) { async function handleZoneAssetName(newName: string) {
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0]; const organization = email?.split("@")[1]?.split(".")[0];
@@ -128,10 +142,17 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
) )
); );
} }
console.log('newName: ', newName); console.log('newName: ', newName);
} }
const checkZoneNameDuplicate = (name: string) => {
return zones.some(
(zone: any) =>
zone.zoneName.trim().toLowerCase() === name.trim().toLowerCase() &&
zone.zoneId !== selectedZone.zoneId
);
};
return ( return (
<> <>
@@ -146,7 +167,12 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
className="value" className="value"
onClick={() => handleSelectZone(item.id)} onClick={() => handleSelectZone(item.id)}
> >
<RenameInput value={item.name} onRename={handleZoneNameChange} /> <RenameInput
value={item.name}
onRename={handleZoneNameChange}
checkDuplicate={checkZoneNameDuplicate}
/>
</div> </div>
</div> </div>
<div className="options-container"> <div className="options-container">

View File

@@ -79,8 +79,9 @@ const Agv: React.FC<ProcessContainerProps> = ({
return ( return (
<> <>
{pathPoints.map((pair, i) => ( {pathPoints.map((pair, i) => (
<group key={i} visible={!isPlaying}> <group key={i}>
<PathNavigator <PathNavigator
key={i}
navMesh={navMesh} navMesh={navMesh}
pathPoints={pair.points} pathPoints={pair.points}
id={pair.modelUuid} id={pair.modelUuid}

View File

@@ -19,7 +19,7 @@ function NavMeshCreator({ lines }: NavMeshCreatorProps) {
<NavMeshDetails lines={lines} setNavMesh={setNavMesh} groupRef={groupRef} /> <NavMeshDetails lines={lines} setNavMesh={setNavMesh} groupRef={groupRef} />
<group ref={groupRef} visible={false} name="Meshes"> <group ref={groupRef} visible={false} name="Meshes">
<mesh rotation-x={CONSTANTS.planeConfig.rotation} position={CONSTANTS.planeConfig.position3D} name="Plane" receiveShadow> <mesh rotation-x={CONSTANTS.planeConfig.rotation} position={CONSTANTS.planeConfig.position3D} receiveShadow>
<planeGeometry args={[300, 300]} /> <planeGeometry args={[300, 300]} />
<meshBasicMaterial color={CONSTANTS.planeConfig.color} /> <meshBasicMaterial color={CONSTANTS.planeConfig.color} />
</mesh> </mesh>

View File

@@ -3,7 +3,10 @@ import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { NavMeshQuery } from "@recast-navigation/core"; import { NavMeshQuery } from "@recast-navigation/core";
import { Line } from "@react-three/drei"; import { Line } from "@react-three/drei";
import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../store/usePlayButtonStore"; import {
useAnimationPlaySpeed,
usePlayButtonStore,
} from "../../../store/usePlayButtonStore";
import { usePlayAgv } from "../../../store/store"; import { usePlayAgv } from "../../../store/store";
interface PathNavigatorProps { interface PathNavigatorProps {
@@ -11,7 +14,7 @@ interface PathNavigatorProps {
pathPoints: any; pathPoints: any;
id: string; id: string;
speed: number; speed: number;
globalSpeed: number, globalSpeed: number;
bufferTime: number; bufferTime: number;
hitCount: number; hitCount: number;
processes: any[]; processes: any[];
@@ -40,11 +43,21 @@ export default function PathNavigator({
}: PathNavigatorProps) { }: PathNavigatorProps) {
const [currentPhase, setCurrentPhase] = useState<Phase>("initial"); const [currentPhase, setCurrentPhase] = useState<Phase>("initial");
const [path, setPath] = useState<[number, number, number][]>([]); const [path, setPath] = useState<[number, number, number][]>([]);
const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>([]); const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>(
const [pickupDropPath, setPickupDropPath] = useState<[number, number, number][]>([]); []
const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]); );
const [initialPosition, setInitialPosition] = useState<THREE.Vector3 | null>(null); const [pickupDropPath, setPickupDropPath] = useState<
const [initialRotation, setInitialRotation] = useState<THREE.Euler | null>(null); [number, number, number][]
>([]);
const [dropPickupPath, setDropPickupPath] = useState<
[number, number, number][]
>([]);
const [initialPosition, setInitialPosition] = useState<THREE.Vector3 | null>(
null
);
const [initialRotation, setInitialRotation] = useState<THREE.Euler | null>(
null
);
const [boxVisible, setBoxVisible] = useState(false); const [boxVisible, setBoxVisible] = useState(false);
const distancesRef = useRef<number[]>([]); const distancesRef = useRef<number[]>([]);
@@ -61,11 +74,14 @@ export default function PathNavigator({
const boxRef = useRef<THREE.Mesh | null>(null); const boxRef = useRef<THREE.Mesh | null>(null);
const baseMaterials = useMemo(() => ({ const baseMaterials = useMemo(
Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), () => ({
Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
Default: new THREE.MeshStandardMaterial({ color: 0xcccccc }) Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
}), []); Default: new THREE.MeshStandardMaterial({ color: 0xcccccc }),
}),
[]
);
useEffect(() => { useEffect(() => {
const object = scene.getObjectByProperty("uuid", id); const object = scene.getObjectByProperty("uuid", id);
@@ -135,7 +151,11 @@ export default function PathNavigator({
const pickupToDropPath = computePath(pickup, drop); const pickupToDropPath = computePath(pickup, drop);
const dropToPickupPath = computePath(drop, pickup); const dropToPickupPath = computePath(drop, pickup);
if (toPickupPath.length && pickupToDropPath.length && dropToPickupPath.length) { if (
toPickupPath.length &&
pickupToDropPath.length &&
dropToPickupPath.length
) {
setPickupDropPath(pickupToDropPath); setPickupDropPath(pickupToDropPath);
setDropPickupPath(dropToPickupPath); setDropPickupPath(dropToPickupPath);
setToPickupPath(toPickupPath); setToPickupPath(toPickupPath);
@@ -163,7 +183,10 @@ export default function PathNavigator({
}, [path]); }, [path]);
function logAgvStatus(id: string, status: string) { function logAgvStatus(id: string, status: string) {
// console.log(`AGV ${id}: ${status}`); // console.log(
// `AGV ${id}: ${status}`
// );
} }
function findProcessByTargetModelUUID(processes: any, targetModelUUID: any) { function findProcessByTargetModelUUID(processes: any, targetModelUUID: any) {
@@ -223,7 +246,9 @@ export default function PathNavigator({
}, [processes, MaterialRef, boxVisible, scene, id, baseMaterials]); }, [processes, MaterialRef, boxVisible, scene, id, baseMaterials]);
useFrame((_, delta) => { useFrame((_, delta) => {
const currentAgv = (agvRef.current || []).find((agv: AGVData) => agv.vehicleId === id); const currentAgv = (agvRef.current || []).find(
(agv: AGVData) => agv.vehicleId === id
);
if (!scene || !id || !isPlaying) return; if (!scene || !id || !isPlaying) return;
@@ -243,6 +268,7 @@ export default function PathNavigator({
const isAgvReady = () => { const isAgvReady = () => {
if (!agvRef.current || agvRef.current.length === 0) return false; if (!agvRef.current || agvRef.current.length === 0) return false;
if (!currentAgv) return false; if (!currentAgv) return false;
return currentAgv.isActive && hitCount >= currentAgv.maxHitCount; return currentAgv.isActive && hitCount >= currentAgv.maxHitCount;
}; };
@@ -266,12 +292,19 @@ export default function PathNavigator({
} }
if (isPlaying && currentPhase === "initial" && !hasReachedPickup.current) { if (isPlaying && currentPhase === "initial" && !hasReachedPickup.current) {
const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); const reached = moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
if (reached) { if (reached) {
hasReachedPickup.current = true; hasReachedPickup.current = true;
if (currentAgv) { if (currentAgv) {
currentAgv.status = 'picking'; currentAgv.status = "picking";
} }
logAgvStatus(id, "Reached pickup point, Waiting for material"); logAgvStatus(id, "Reached pickup point, Waiting for material");
} }
@@ -287,20 +320,28 @@ export default function PathNavigator({
progressRef.current = 0; progressRef.current = 0;
logAgvStatus(id, "Started from pickup point, heading to drop point"); logAgvStatus(id, "Started from pickup point, heading to drop point");
if (currentAgv) { if (currentAgv) {
currentAgv.status = 'toDrop'; currentAgv.status = "toDrop";
} }
}, 0) }, 0);
return; return;
} }
if (isPlaying && currentPhase === "toDrop") { if (isPlaying && currentPhase === "toDrop") {
const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); const reached = moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
if (reached && !isWaiting.current) { if (reached && !isWaiting.current) {
isWaiting.current = true; isWaiting.current = true;
logAgvStatus(id, "Reached drop point"); logAgvStatus(id, "Reached drop point");
if (currentAgv) { if (currentAgv) {
currentAgv.status = 'droping'; currentAgv.status = "droping";
currentAgv.hitCount = currentAgv.hitCount--;
} }
timeoutRef.current = setTimeout(() => { timeoutRef.current = setTimeout(() => {
setPath([...dropPickupPath]); setPath([...dropPickupPath]);
@@ -309,16 +350,26 @@ export default function PathNavigator({
isWaiting.current = false; isWaiting.current = false;
setBoxVisible(false); setBoxVisible(false);
if (currentAgv) { if (currentAgv) {
currentAgv.status = 'toPickup'; currentAgv.status = "toPickup";
} }
logAgvStatus(id, "Started from droping point, heading to pickup point"); logAgvStatus(
id,
"Started from droping point, heading to pickup point"
);
}, bufferTime * 1000); }, bufferTime * 1000);
} }
return; return;
} }
if (isPlaying && currentPhase === "toPickup") { if (isPlaying && currentPhase === "toPickup") {
const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); const reached = moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
if (reached) { if (reached) {
if (currentAgv) { if (currentAgv) {
@@ -326,14 +377,21 @@ export default function PathNavigator({
} }
setCurrentPhase("initial"); setCurrentPhase("initial");
if (currentAgv) { if (currentAgv) {
currentAgv.status = 'picking'; currentAgv.status = "picking";
} }
logAgvStatus(id, "Reached pickup point again, cycle complete"); logAgvStatus(id, "Reached pickup point again, cycle complete");
} }
return; return;
} }
moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef); moveAlongPath(
object,
path,
distancesRef.current,
speed,
delta,
progressRef
);
}); });
function moveAlongPath( function moveAlongPath(
@@ -379,7 +437,11 @@ export default function PathNavigator({
const targetRotationY = Math.atan2(targetDirection.x, targetDirection.z); const targetRotationY = Math.atan2(targetDirection.x, targetDirection.z);
const rotationSpeed = Math.min(5 * delta, 1); const rotationSpeed = Math.min(5 * delta, 1);
object.rotation.y = THREE.MathUtils.lerp(object.rotation.y, targetRotationY, rotationSpeed); object.rotation.y = THREE.MathUtils.lerp(
object.rotation.y,
targetRotationY,
rotationSpeed
);
} }
return false; return false;
@@ -394,7 +456,7 @@ export default function PathNavigator({
}, []); }, []);
return ( return (
<group name="path-navigator-lines" visible={!isPlaying}> <group name="path-navigator-lines">
{toPickupPath.length > 0 && ( {toPickupPath.length > 0 && (
<Line <Line
points={toPickupPath} points={toPickupPath}

View File

@@ -199,14 +199,12 @@ const SelectionControls: React.FC = () => {
setDuplicatedObjects([]); setDuplicatedObjects([]);
setSelectedAssets([]); setSelectedAssets([]);
}; };
const updateBackend = async (updatedPaths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => { const updateBackend = async (updatedPaths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
if (updatedPaths.length === 0) return; if (updatedPaths.length === 0) return;
const email = localStorage.getItem("email"); const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : ""; const organization = email ? email.split("@")[1].split(".")[0] : "";
updatedPaths.forEach(async (updatedPath) => { updatedPaths.forEach(async (updatedPath) => {
if (updatedPath.type === "Conveyor") { if (updatedPath.type === "Conveyor") {
// await setEventApi( // await setEventApi(
// organization, // organization,
@@ -225,9 +223,7 @@ const SelectionControls: React.FC = () => {
}; };
socket.emit("v2:model-asset:updateEventData", data); socket.emit("v2:model-asset:updateEventData", data);
} else if (updatedPath.type === "Vehicle") { } else if (updatedPath.type === "Vehicle") {
// await setEventApi( // await setEventApi(
// organization, // organization,
// updatedPath.modeluuid, // updatedPath.modeluuid,
@@ -241,9 +237,7 @@ const SelectionControls: React.FC = () => {
}; };
socket.emit("v2:model-asset:updateEventData", data); socket.emit("v2:model-asset:updateEventData", data);
} else if (updatedPath.type === "StaticMachine") { } else if (updatedPath.type === "StaticMachine") {
// await setEventApi( // await setEventApi(
// organization, // organization,
// updatedPath.modeluuid, // updatedPath.modeluuid,
@@ -257,9 +251,7 @@ const SelectionControls: React.FC = () => {
}; };
socket.emit("v2:model-asset:updateEventData", data); socket.emit("v2:model-asset:updateEventData", data);
} else if (updatedPath.type === "ArmBot") { } else if (updatedPath.type === "ArmBot") {
// await setEventApi( // await setEventApi(
// organization, // organization,
// updatedPath.modeluuid, // updatedPath.modeluuid,
@@ -274,239 +266,135 @@ const SelectionControls: React.FC = () => {
socket.emit("v2:model-asset:updateEventData", data); socket.emit("v2:model-asset:updateEventData", data);
} }
}); });
}; };
// const removeConnection = (modelUUID: any) => { const removeConnections = (deletedModelUUIDs: string[]) => {
//
// const removedPath = simulationStates?.flatMap((state) => {
// let shouldInclude = false;
// if (state.type === "Conveyor") { const deletedPointUUIDs = new Set<string>();
// state.points.forEach((point: any) => { simulationStates.forEach(state => {
// const sourceMatch = if (deletedModelUUIDs.includes(state.modeluuid)) {
// point.connections?.source?.modelUUID === modelUUID; if (state.type === "Conveyor" && state.points) {
// const targetMatch = point.connections?.targets?.some( state.points.forEach(point => {
// (target: any) => target.modelUUID === modelUUID deletedPointUUIDs.add(point.uuid);
// ); });
} else if (state.points && 'uuid' in state.points) {
// if (sourceMatch || targetMatch) shouldInclude = true; deletedPointUUIDs.add(state.points.uuid);
// });
// }
// if (state.type === "Vehicle") {
// const targetMatch = state.points.connections?.targets?.some(
// (target: any) => target.modelUUID === modelUUID
// );
// if (targetMatch) shouldInclude = true;
// }
// if (state.type === "StaticMachine") {
// const targetMatch = state.points.connections?.targets?.some(
// (target: any) => target.modelUUID === modelUUID
// );
// if (targetMatch) shouldInclude = true;
// }
// if (state.type === "ArmBot") {
// const sourceMatch =
// state.points.connections?.source?.modelUUID === modelUUID;
// const targetMatch = state.points.connections?.targets?.some(
// (target: any) => target.modelUUID === modelUUID
// );
// const processMatch =
// state.points.actions?.processes?.some(
// (process: any) =>
// process.startPoint === modelUUID || process.endPoint === modelUUID
// ) ?? false;
// if (sourceMatch || targetMatch || processMatch) shouldInclude = true;
// }
// return shouldInclude ? [state] : [];
// });
// updateBackend(removedPath);
//
// return removedPath;
// // updateBackend(updatedPaths);
// // setSimulationStates(updatedStates);
// };
// const removeConnection = (modelUUIDs: any[]) => {
//
// const removedPath = simulationStates?.flatMap((state) => {
// let shouldInclude = false;
// if (state.type === "Conveyor") {
// state.points.forEach((point: any) => {
// const sourceMatch = modelUUIDs.includes(
// point.connections?.source?.modelUUID
// );
// const targetMatch = point.connections?.targets?.some((target: any) =>
// modelUUIDs.includes(target.modelUUID)
// );
// if (sourceMatch || targetMatch) shouldInclude = true;
// });
// }
// if (state.type === "Vehicle") {
// const targetMatch = state.points.connections?.targets?.some(
// (target: any) => modelUUIDs.includes(target.modelUUID)
// );
// if (targetMatch) shouldInclude = true;
// }
// if (state.type === "StaticMachine") {
// const targetMatch = state.points.connections?.targets?.some(
// (target: any) => modelUUIDs.includes(target.modelUUID)
// );
// if (targetMatch) shouldInclude = true;
// }
// if (state.type === "ArmBot") {
// const sourceMatch = modelUUIDs.includes(
// state.points.connections?.source?.modelUUID
// );
// const targetMatch = state.points.connections?.targets?.some(
// (target: any) => modelUUIDs.includes(target.modelUUID)
// );
// const processMatch =
// state.points.actions?.processes?.some(
// (process: any) =>
// modelUUIDs.includes(process.startPoint) ||
// modelUUIDs.includes(process.endPoint)
// ) ?? false;
// if (sourceMatch || targetMatch || processMatch) shouldInclude = true;
// }
// return shouldInclude ? [state] : [];
// });
// updateBackend(removedPath);
//
// return removedPath;
// };
const removeConnection = (modelUUIDs: any[]) => {
const removedPath = simulationStates?.flatMap((state: any) => {
let shouldInclude = false;
// Conveyor type
if (state.type === "Conveyor") {
state.points.forEach((point: any) => {
const sourceMatch = modelUUIDs.includes(point.connections?.source?.modelUUID);
const targetMatch = point.connections?.targets?.some((target: any) => modelUUIDs.includes(target.modelUUID));
if (sourceMatch) {
point.connections.source = {};
shouldInclude = true;
}
if (targetMatch) {
point.connections.targets = [];
shouldInclude = true;
}
});
}
// Vehicle & StaticMachine types
if (state.type === "Vehicle") {
const targets = state.points?.connections?.targets || [];
const targetMatch = targets.some((target: any) => modelUUIDs.includes(target.modelUUID));
if (targetMatch) {
state.points.connections.targets = [];
shouldInclude = true;
} }
} }
if (state.type === "StaticMachine") {
const targets = state.points?.connections?.targets || [];
const targetMatch = targets.some((target: any) => modelUUIDs.includes(target.modelUUID));
if (targetMatch) {
state.points.connections.targets = [];
shouldInclude = true;
}
}
// ArmBot type
if (state.type === "ArmBot") {
const sourceMatch = modelUUIDs.includes(state.points.connections?.source?.modelUUID);
const targetMatch = state.points.connections?.targets?.some(
(target: any) => modelUUIDs.includes(target.modelUUID)
);
// state.points.actions.processes = state.points.actions.processes.filter(
// (process: any) =>
// console.log(
// !modelUUIDs.includes(process.startPoint),
// !modelUUIDs.includes(process.endPoint),
// modelUUIDs,
// process.startPoint,
// process.endPoint
// )
// );
// shouldInclude = true;
// const processMatches = state.points.actions?.processes?.some(
// (process: any) =>
// console.log(
// "process: ",
// process,
// modelUUIDs,
// process.startPoint,
// process.endPoint
// )
// // modelUUIDs.includes(process.startPoint) ||
// // modelUUIDs.includes(process.endPoint)
// );
// const processMatch = state.points.actions?.processes?.some(
// (process: any) =>
// modelUUIDs.includes(String(process.startPoint)) ||
// modelUUIDs.includes(String(process.endPoint))
// );
// console.log("processMatch: ", processMatch);
if (sourceMatch) {
state.points.connections.source = {};
shouldInclude = true;
}
if (targetMatch) {
state.points.connections.targets =
state.points.connections.targets.filter((target: any) => !modelUUIDs.includes(target.modelUUID));
shouldInclude = true;
}
// console.log("processMatch: ", processMatch);
// if (processMatch) {
// state.points.actions.processes =
// state.points.actions.processes.filter((process: any) => {
// const shouldRemove =
// modelUUIDs.includes(process.startPoint) ||
// modelUUIDs.includes(process.endPoint);
// console.log("shouldRemove: ", shouldRemove);
// return !shouldRemove;
// });
// shouldInclude = true;
// }
}
return shouldInclude ? [state] : [];
}); });
updateBackend(removedPath); const updatedStates = simulationStates.map((state) => {
return removedPath; // Handle Conveyor
if (state.type === "Conveyor") {
const updatedConveyor: SimulationTypes.ConveyorEventsSchema = {
...state,
points: state.points.map((point) => {
return {
...point,
connections: {
...point.connections,
targets: point.connections.targets.filter(
(target) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
};
}),
};
return updatedConveyor;
}
// Handle Vehicle
else if (state.type === "Vehicle") {
const updatedVehicle: SimulationTypes.VehicleEventsSchema = {
...state,
points: {
...state.points,
connections: {
...state.points.connections,
targets: state.points.connections.targets.filter(
(target) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
},
};
return updatedVehicle;
}
// Handle StaticMachine
else if (state.type === "StaticMachine") {
const updatedStaticMachine: SimulationTypes.StaticMachineEventsSchema =
{
...state,
points: {
...state.points,
connections: {
...state.points.connections,
targets: state.points.connections.targets.filter(
(target) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
},
};
return updatedStaticMachine;
}
// Handle ArmBot
else if (state.type === "ArmBot") {
const updatedArmBot: SimulationTypes.ArmBotEventsSchema = {
...state,
points: {
...state.points,
connections: {
...state.points.connections,
targets: state.points.connections.targets.filter(
(target: any) => !deletedModelUUIDs.includes(target.modelUUID)
),
},
actions: {
...state.points.actions,
processes: state.points.actions.processes?.filter((process) => {
// Check if trigger is from deleted model
const matchedStates = simulationStates.filter((s) => deletedModelUUIDs.includes(s.modeluuid));
if (matchedStates.length > 0) {
if (matchedStates[0]?.type === "StaticMachine") {
const trigPoints = matchedStates[0]?.points;
if (process.triggerId === trigPoints?.triggers?.uuid) {
return false;
}
} else if (matchedStates[0]?.type === "Conveyor") {
const trigPoints = matchedStates[0]?.points;
if (Array.isArray(trigPoints)) {
const nonEmptyTriggers = trigPoints.filter((point) => point && point.triggers && point.triggers.length > 0);
const allTriggerUUIDs = nonEmptyTriggers.flatMap((point) => point.triggers).map((trigger) => trigger.uuid);
if (allTriggerUUIDs.includes(process.triggerId)) {
return false;
}
}
}
}
// Check if startPoint or endPoint is from deleted model
if (deletedPointUUIDs.has(process.startPoint) || deletedPointUUIDs.has(process.endPoint)) {
return false;
}
return true;
}),
},
},
};
return updatedArmBot;
}
return state;
});
const filteredStates = updatedStates.filter((state) => !deletedModelUUIDs.includes(state.modeluuid));
updateBackend(filteredStates);
setSimulationStates(filteredStates);
}; };
const deleteSelection = () => { const deleteSelection = () => {
@@ -552,65 +440,17 @@ const SelectionControls: React.FC = () => {
}); });
setSimulationStates((prevEvents: (| SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => { setSimulationStates((prevEvents: (| SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => {
const updatedEvents = (prevEvents || []).filter( const updatedEvents = (prevEvents || []).filter((event) => event.modeluuid !== selectedMesh.uuid);
(event) => event.modeluuid !== selectedMesh.uuid
);
return updatedEvents; return updatedEvents;
} });
);
itemsGroupRef.current?.remove(selectedMesh); itemsGroupRef.current?.remove(selectedMesh);
}); });
const allUUIDs = selectedAssets.map((val: any) => val.uuid); const allUUIDs = selectedAssets.map((val: any) => val.uuid);
removeConnection(allUUIDs); removeConnections(allUUIDs);
// const removedPath = simulationStates?.flatMap((path: any) => { const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
// let shouldInclude = false;
// if (Array.isArray(path.points)) {
// path.points.forEach((point: any) => {
// const sourceMatch =
// point.connections?.source?.modelUUID === selectedAssets[0].uuid;
// const targetMatch = point.connections?.targets?.some(
// (target: any) => target.modelUUID === selectedAssets[0].uuid
// );
// if (sourceMatch) {
// point.connections.source = {};
// shouldInclude = true;
// }
// if (targetMatch) {
// point.connections.targets = [];
// shouldInclude = true;
// }
// });
// } else {
// const sourceMatch =
// path.connections?.source?.modelUUID === selectedAssets[0].uuid;
// const targetMatch = path.connections?.targets?.some(
// (target: any) => target.modelUUID === selectedAssets[0].uuid
// );
// if (sourceMatch) {
// path.connections.source = {};
// shouldInclude = true;
// }
// if (targetMatch) {
// path.connections.targets = [];
// shouldInclude = true;
// }
// }
// return shouldInclude ? [path] : [];
// });
// updateBackend(removedPath);
const updatedItems = floorItems.filter(
(item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid)
);
setFloorItems(updatedItems); setFloorItems(updatedItems);
} }
toast.success("Selected models removed!"); toast.success("Selected models removed!");

View File

@@ -4,6 +4,7 @@ import useModuleStore from "../../../store/useModuleStore";
import { useSimulationStates } from "../../../store/store"; import { useSimulationStates } from "../../../store/store";
import * as SimulationTypes from '../../../types/simulationTypes'; import * as SimulationTypes from '../../../types/simulationTypes';
import { ArmbotInstances } from "./ArmBotInstances"; import { ArmbotInstances } from "./ArmBotInstances";
import { useResetButtonStore } from "../../../store/usePlayButtonStore";
interface ArmBotState { interface ArmBotState {
uuid: string; uuid: string;
@@ -12,7 +13,12 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; }; actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
} }
interface StaticMachineState { interface StaticMachineState {
@@ -33,6 +39,7 @@ const ArmBot = ({ armBots, setArmBots, setStaticMachines }: ArmBotProps) => {
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { scene } = useThree(); const { scene } = useThree();
const { simulationStates } = useSimulationStates(); const { simulationStates } = useSimulationStates();
const { isReset } = useResetButtonStore();
useEffect(() => { useEffect(() => {
const filtered = simulationStates.filter((s): s is SimulationTypes.ArmBotEventsSchema => s.type === "ArmBot"); const filtered = simulationStates.filter((s): s is SimulationTypes.ArmBotEventsSchema => s.type === "ArmBot");
@@ -45,10 +52,12 @@ const ArmBot = ({ armBots, setArmBots, setStaticMachines }: ArmBotProps) => {
status: "idle", status: "idle",
material: "default", material: "default",
triggerId: '', triggerId: '',
actions: bot.points.actions actions: bot.points.actions,
connections: bot.points.connections,
isActive: false
})); }));
setArmBots(initialStates); setArmBots(initialStates);
}, [simulationStates]); }, [simulationStates, isReset]);
useEffect(() => { useEffect(() => {
armBots.forEach((bot) => { armBots.forEach((bot) => {

View File

@@ -18,16 +18,12 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
actions: { connections: {
uuid: string; source: { modelUUID: string; pointUUID: string };
name: string; targets: { modelUUID: string; pointUUID: string }[];
speed: number;
processes: {
triggerId: string;
startPoint: string;
endPoint: string;
}[];
}; };
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
} }
interface StaticMachineState { interface StaticMachineState {
@@ -50,7 +46,6 @@ export const ArmbotInstances: React.FC<ArmbotInstancesProps> = ({ index, armBot,
const [processes, setProcesses] = useState<Process[]>([]); const [processes, setProcesses] = useState<Process[]>([]);
useEffect(() => { useEffect(() => {
if (armBot.actions.processes.length > 0) { if (armBot.actions.processes.length > 0) {
const mappedProcesses = armBot.actions.processes.map((process) => { const mappedProcesses = armBot.actions.processes.map((process) => {
return { return {
@@ -87,6 +82,7 @@ export const ArmbotInstances: React.FC<ArmbotInstancesProps> = ({ index, armBot,
rotation={armBot.rotation} rotation={armBot.rotation}
processes={processes} processes={processes}
armBot={armBot} armBot={armBot}
setArmBots={setArmBots}
setStaticMachines={setStaticMachines} setStaticMachines={setStaticMachines}
updateArmBotStatus={updateArmBotStatus} updateArmBotStatus={updateArmBotStatus}
/> />

View File

@@ -1,8 +1,11 @@
import { useEffect, useMemo, useState, useRef } from "react"; import { useEffect, useMemo, useState, useRef } from "react";
import { useFrame } from "@react-three/fiber"; import { useFrame } from "@react-three/fiber";
import * as THREE from "three"; import * as THREE from "three";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore";
import { useSimulationStates } from "../../../store/store"; import { useSimulationStates } from "../../../store/store";
import MaterialInstances from "./MaterialInstances";
import { Line } from "react-chartjs-2";
import { QuadraticBezierLine } from "@react-three/drei";
interface StaticMachineState { interface StaticMachineState {
@@ -20,21 +23,17 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
actions: { connections: {
uuid: string; source: { modelUUID: string; pointUUID: string };
name: string; targets: { modelUUID: string; pointUUID: string }[];
speed: number;
processes: {
triggerId: string;
startPoint: string;
endPoint: string;
}[];
}; };
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
} }
type IKAnimationControllerProps = { type IKAnimationControllerProps = {
ikSolver: any; ikSolver: any;
process: { processes: {
triggerId: string; triggerId: string;
startPoint: THREE.Vector3; startPoint: THREE.Vector3;
endPoint: THREE.Vector3; endPoint: THREE.Vector3;
@@ -46,19 +45,21 @@ type IKAnimationControllerProps = {
logStatus: (status: string) => void; logStatus: (status: string) => void;
groupRef: React.RefObject<THREE.Group>; groupRef: React.RefObject<THREE.Group>;
armBot: ArmBotState; armBot: ArmBotState;
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>; setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
updateArmBotStatus: (status: string) => void; updateArmBotStatus: (status: string) => void;
} }
const IKAnimationController = ({ const IKAnimationController = ({
ikSolver, ikSolver,
process, processes,
selectedTrigger, selectedTrigger,
targetBoneName, targetBoneName,
uuid, uuid,
logStatus, logStatus,
groupRef, groupRef,
armBot, armBot,
setArmBots,
setStaticMachines, setStaticMachines,
updateArmBotStatus updateArmBotStatus
}: IKAnimationControllerProps) => { }: IKAnimationControllerProps) => {
@@ -68,19 +69,10 @@ const IKAnimationController = ({
const [isInitializing, setIsInitializing] = useState(true); const [isInitializing, setIsInitializing] = useState(true);
const restSpeed = 0.1; const restSpeed = 0.1;
const restPosition = new THREE.Vector3(0, 2, 1.6); const restPosition = new THREE.Vector3(0, 2, 1.6);
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();;
const statusRef = useRef("idle");
const { simulationStates } = useSimulationStates(); const { simulationStates } = useSimulationStates();
const { isReset } = useResetButtonStore();
// Track previous states for comparison
const prevStateRef = useRef({
isInitializing: true,
needsInitialMovement: true,
selectedTrigger: "",
progress: 0
});
// Track previous status for comparison
const prevStatusRef = useRef("");
const initialCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null); const initialCurveRef = useRef<THREE.CatmullRomCurve3 | null>(null);
const initialStartPositionRef = useRef<THREE.Vector3 | null>(null); const initialStartPositionRef = useRef<THREE.Vector3 | null>(null);
@@ -89,6 +81,13 @@ const IKAnimationController = ({
setProgress(0); setProgress(0);
}, [selectedTrigger]); }, [selectedTrigger]);
useEffect(() => {
setProgress(0);
setNeedsInitialMovement(true);
setInitialProgress(0);
setIsInitializing(true);
}, [isReset]);
useEffect(() => { useEffect(() => {
if (ikSolver) { if (ikSolver) {
const targetBone = ikSolver.mesh.skeleton.bones.find( const targetBone = ikSolver.mesh.skeleton.bones.find(
@@ -102,32 +101,6 @@ const IKAnimationController = ({
} }
}, [ikSolver]); }, [ikSolver]);
// Log state changes
useEffect(() => {
const prev = prevStateRef.current;
if (prev.isInitializing !== isInitializing) {
if (!isInitializing) {
logStatus(`[Arm ${uuid}] Completed initialization, now at rest position`);
}
}
if (prev.needsInitialMovement !== needsInitialMovement && !needsInitialMovement) {
logStatus(`[Arm ${uuid}] Reached rest position, ready for animation`);
}
if (prev.selectedTrigger !== selectedTrigger) {
logStatus(`[Arm ${uuid}] Processing new trigger: ${selectedTrigger}`);
}
// Update previous state
prevStateRef.current = {
isInitializing,
needsInitialMovement,
selectedTrigger,
progress
};
}, [isInitializing, needsInitialMovement, selectedTrigger, progress]);
const calculateInitialCurve = (startPosition: THREE.Vector3) => { const calculateInitialCurve = (startPosition: THREE.Vector3) => {
const direction = new THREE.Vector3().subVectors(restPosition, startPosition); const direction = new THREE.Vector3().subVectors(restPosition, startPosition);
@@ -154,56 +127,55 @@ const IKAnimationController = ({
}; };
const processedCurves = useMemo(() => { const processedCurves = useMemo(() => {
if (isPlaying) if (!isPlaying) return [];
return process.map((p) => {
const tempLift = 0.5;
const localStart = groupRef.current?.worldToLocal(p.startPoint.clone().add(new THREE.Vector3(0, tempLift, 0)));
const localEnd = groupRef.current?.worldToLocal(p.endPoint.clone().add(new THREE.Vector3(0, tempLift, 0)));
if (localStart && localEnd) { return processes.map(process => {
const localStart = groupRef.current?.worldToLocal(process.startPoint.clone());
const localEnd = groupRef.current?.worldToLocal(process.endPoint.clone());
const mid = new THREE.Vector3( if (!localStart || !localEnd) return null;
(localStart.x + localEnd.x) / 1,
Math.max(localStart.y, localEnd.y) + 0.8,
(localStart.z + localEnd.z) / 0.9
);
const points = [ const midPoint = new THREE.Vector3(
restPosition.clone(), (localStart.x + localEnd.x) / 2,
localStart.clone(), Math.max(localStart.y, localEnd.y) + 1,
mid.clone(), (localStart.z + localEnd.z) / 2
localEnd.clone(), );
restPosition.clone(), const restToStartCurve = new THREE.CatmullRomCurve3([
]; restPosition,
const curve = new THREE.CatmullRomCurve3(points); new THREE.Vector3().lerpVectors(restPosition, localStart, 0.5),
const restToStartDist = points[0].distanceTo(points[1]); localStart
const startToEndDist = points[1].distanceTo(points[3]); ]);
const endToRestDist = points[3].distanceTo(points[4]);
const totalDist = restToStartDist + startToEndDist + endToRestDist; const processCurve = new THREE.CatmullRomCurve3([
const restToStartRange = [0, restToStartDist / totalDist]; localStart,
const startToEndRange = [ midPoint,
restToStartRange[1], localEnd
restToStartRange[1] + startToEndDist / totalDist, ]);
];
const endToRestRange = [startToEndRange[1], 1];
return { const endToRestCurve = new THREE.CatmullRomCurve3([
trigger: p.triggerId, localEnd,
curve, new THREE.Vector3().lerpVectors(localEnd, restPosition, 0.5),
speed: p.speed, restPosition
restToStartRange, ]);
startToEndRange,
endToRestRange,
};
}
});
}, [process, groupRef, isPlaying]);
const activeCurve = useMemo(() => { return {
if (isPlaying && processedCurves) triggerId: process.triggerId,
return processedCurves.find((c) => c?.trigger === selectedTrigger); restToStartCurve,
}, [processedCurves, selectedTrigger, isPlaying]); processCurve,
endToRestCurve,
speed: process.speed,
totalDistance:
restPosition.distanceTo(localStart) +
localStart.distanceTo(localEnd) +
localEnd.distanceTo(restPosition)
};
}).filter(Boolean);
}, [processes, isPlaying]);
const activeProcess = useMemo(() => {
if (!selectedTrigger) return null;
return processedCurves.find(p => p?.triggerId === selectedTrigger);
}, [processedCurves, selectedTrigger]);
// Initial movement to rest position // Initial movement to rest position
useFrame((_, delta) => { useFrame((_, delta) => {
@@ -231,85 +203,177 @@ const IKAnimationController = ({
// Main animation loop // Main animation loop
useFrame((_, delta) => { useFrame((_, delta) => {
if (!ikSolver || !activeCurve || isInitializing || !isPlaying) return; if (isInitializing || !isPlaying || !selectedTrigger || !activeProcess || !ikSolver) return;
const { curve, speed, restToStartRange, startToEndRange, endToRestRange } = activeCurve; const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBoneName);
const targetBone = ikSolver.mesh.skeleton.bones.find( if (!bone) return;
(b: any) => b.name === targetBoneName
);
if (!targetBone) return;
let currentSpeed = restSpeed; const {
let currentStatus = "idle"; // Default status restToStartCurve,
processCurve,
endToRestCurve,
speed,
totalDistance
} = activeProcess;
// Determine current phase and status // Calculate current segment and progress
if (progress < restToStartRange[1]) { const restToStartDist = restPosition.distanceTo(restToStartCurve.points[2]);
currentSpeed = restSpeed; const processDist = processCurve.getLength();
currentStatus = "moving"; // Moving to start point const endToRestDist = endToRestCurve.getLength();
} else if (progress >= startToEndRange[0] && progress < startToEndRange[1]) {
currentSpeed = speed;
currentStatus = "moving"; // Moving between points
if (1 - progress < 0.05) {
// Find the process that matches the current trigger
const currentProcess = process.find(p => p.triggerId === selectedTrigger);
if (currentProcess) {
const triggerId = currentProcess.triggerId;
const endPoint = armBot.actions.processes.find((process) => process.triggerId === triggerId)?.endPoint; const restToStartEnd = restToStartDist / totalDistance;
const processEnd = (restToStartDist + processDist) / totalDistance;
// Search simulationStates for a StaticMachine that has a point matching this endPointId setProgress(prev => {
const matchedStaticMachine = simulationStates.find( let currentStatus = statusRef.current;
(state) => let currentPosition: THREE.Vector3;
state.type === "StaticMachine" && const newProgress = Math.min(prev + delta * ((currentStatus === 'returning to rest') ? restSpeed : speed), 1);
state.points?.uuid === endPoint// check for static machine with matching point uuid
) as any;
if (matchedStaticMachine) { if (newProgress < restToStartEnd) {
// Moving from rest to start position
currentStatus = "moving to start";
const segmentProgress = newProgress / restToStartEnd;
currentPosition = restToStartCurve.getPoint(segmentProgress);
} else if (newProgress < processEnd) {
// Processing - moving from start to end
currentStatus = "processing";
const segmentProgress = (newProgress - restToStartEnd) / (processEnd - restToStartEnd);
currentPosition = processCurve.getPoint(segmentProgress);
if (statusRef.current !== "processing") {
updateConveyorOrStaticMachineStatusOnStart(selectedTrigger);
}
} else {
// Returning to rest position
currentStatus = "returning to rest";
const segmentProgress = (newProgress - processEnd) / (1 - processEnd);
currentPosition = endToRestCurve.getPoint(segmentProgress);
}
// Update status if changed
if (currentStatus !== statusRef.current) {
statusRef.current = currentStatus;
// updateArmBotStatus(currentStatus);
logStatus(`[Arm ${uuid}] Status: ${currentStatus}`);
}
// Only trigger when the entire animation is complete (newProgress === 1)
if (newProgress === 1 && currentStatus === "returning to rest") {
updateConveyorOrStaticMachineStatusOnEnd(selectedTrigger);
}
bone.position.copy(currentPosition);
ikSolver.update();
return newProgress;
});
});
const updateConveyorOrStaticMachineStatusOnStart = (selectedTrigger: string) => {
const currentProcess = processes.find(p => p.triggerId === selectedTrigger);
if (currentProcess) {
const triggerId = currentProcess.triggerId;
const startPoint = armBot.actions.processes.find((process) => process.triggerId === triggerId)?.startPoint;
const matchedMachine = simulationStates.find((state) => {
if (state.type === "Conveyor") {
return (state).points.some(
(point) => point.uuid === startPoint
);
} else if (state.type === "StaticMachine") {
return state.points.uuid === startPoint;
}
return false;
});
if (matchedMachine) {
if (matchedMachine.type === "Conveyor") {
logStatus(`[Arm ${uuid}] start point which is a conveyor (${matchedMachine.modelName})`);
} else {
logStatus(`[Arm ${uuid}] started form start point which is a static machine (${matchedMachine.modelName})`);
}
setTimeout(() => {
if (matchedMachine.type === "StaticMachine") {
updateArmBotStatus('dropping');
}
if (matchedMachine.type === "Conveyor") {
updateArmBotStatus('picking');
}
}, 0);
}
}
}
const updateConveyorOrStaticMachineStatusOnEnd = (selectedTrigger: string) => {
const currentProcess = processes.find(p => p.triggerId === selectedTrigger);
if (currentProcess) {
const triggerId = currentProcess.triggerId;
const endPoint = armBot.actions.processes.find((process) => process.triggerId === triggerId)?.endPoint;
const matchedMachine = simulationStates.find((state) => {
if (state.type === "Conveyor") {
return (state).points.some(
(point) => point.uuid === endPoint
);
} else if (state.type === "StaticMachine") {
return state.points.uuid === endPoint;
}
return false;
});
if (matchedMachine) {
if (matchedMachine.type === "Conveyor") {
logStatus(`[Arm ${uuid}] Reached end point which is a conveyor (${matchedMachine.modelName})`);
} else {
logStatus(`[Arm ${uuid}] Reached end point which is a static machine (${matchedMachine.modelName})`);
}
setTimeout(() => {
if (matchedMachine.type === "StaticMachine") {
setStaticMachines((machines) => { setStaticMachines((machines) => {
return machines.map((machine) => { return machines.map((machine) => {
if (machine.uuid === matchedStaticMachine.modeluuid) { if (machine.uuid === matchedMachine.modeluuid) {
return { ...machine, status: "running" }; return { ...machine, status: "running" };
} else { } else {
return machine; return machine;
} }
}); });
}); });
updateArmBotStatus('idle');
} }
}
if (matchedMachine.type === "Conveyor") {
setArmBots((prev) =>
prev.map((arm) => {
if (arm.uuid === uuid && arm.isActive === true) {
return {
...arm,
isActive: false,
status: "idle",
};
}
else {
return arm;
}
})
);
}
}, 0);
} }
} else if (progress >= endToRestRange[0] && progress < 1) {
currentSpeed = restSpeed;
currentStatus = "moving"; // Returning to rest
} else if (progress >= 1) {
currentStatus = "idle"; // Completed cycle
} }
}
// Update status when it changes return (
if (prevStatusRef.current !== currentStatus) { <>
updateArmBotStatus(currentStatus); <MaterialInstances
prevStatusRef.current = currentStatus; statusRef={statusRef}
} ikSolver={ikSolver}
targetBoneName={targetBoneName}
// Only update progress if we're not already at the end />
if (progress < 1) { </>
setProgress((prev) => { );
const next = prev + delta * currentSpeed;
return Math.min(next, 1); // Cap at 1
});
}
// Update bone position based on progress
if (progress < 1) {
targetBone.position.copy(curve.getPoint(progress));
} else {
targetBone.position.copy(curve.getPoint(1));
}
ikSolver.update();
});
return null;
}; };
export default IKAnimationController; export default IKAnimationController;

View File

@@ -23,16 +23,12 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
actions: { connections: {
uuid: string; source: { modelUUID: string; pointUUID: string };
name: string; targets: { modelUUID: string; pointUUID: string }[];
speed: number;
processes: {
triggerId: string;
startPoint: string;
endPoint: string;
}[];
}; };
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
} }
const IkInstances = ({ const IkInstances = ({
@@ -43,6 +39,7 @@ const IkInstances = ({
position, position,
rotation, rotation,
armBot, armBot,
setArmBots,
setStaticMachines, setStaticMachines,
updateArmBotStatus updateArmBotStatus
}: { }: {
@@ -53,6 +50,7 @@ const IkInstances = ({
position: [number, number, number]; position: [number, number, number];
rotation: [number, number, number]; rotation: [number, number, number];
armBot: ArmBotState; armBot: ArmBotState;
setArmBots: React.Dispatch<React.SetStateAction<ArmBotState[]>>;
setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>; setStaticMachines: React.Dispatch<React.SetStateAction<StaticMachineState[]>>;
updateArmBotStatus: (status: string) => void; updateArmBotStatus: (status: string) => void;
}) => { }) => {
@@ -134,13 +132,14 @@ const IkInstances = ({
</group> </group>
<IKAnimationController <IKAnimationController
ikSolver={ikSolver} ikSolver={ikSolver}
process={processes} processes={processes}
selectedTrigger={selectedTrigger} selectedTrigger={selectedTrigger}
targetBoneName={targetBoneName} targetBoneName={targetBoneName}
uuid={uuid} uuid={uuid}
logStatus={logStatus} logStatus={logStatus}
groupRef={groupRef} groupRef={groupRef}
armBot={armBot} armBot={armBot}
setArmBots={setArmBots}
setStaticMachines={setStaticMachines} setStaticMachines={setStaticMachines}
updateArmBotStatus={updateArmBotStatus} updateArmBotStatus={updateArmBotStatus}
/> />

View File

@@ -0,0 +1,31 @@
import React from 'react';
import * as THREE from 'three';
import { Box } from '@react-three/drei';
type MaterialInstancesProps = {
statusRef: React.RefObject<string>;
ikSolver: any;
targetBoneName: string;
};
function MaterialInstances({
statusRef,
ikSolver,
targetBoneName
}: MaterialInstancesProps) {
if (!ikSolver) return null;
const targetBone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBoneName);
if (!targetBone) return null;
const worldPos = new THREE.Vector3();
targetBone.getWorldPosition(worldPos);
return (
<Box args={[0.5, 0.5, 0.5]} position={worldPos} visible={statusRef.current === 'processing'}>
<meshStandardMaterial color="orange" />
</Box>
);
}
export default MaterialInstances;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import React, { useRef, useEffect, useMemo } from "react"; import React, { useRef, useEffect, useMemo, useCallback } from "react";
import { useLoader, useFrame } from "@react-three/fiber"; import { useLoader, useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three-stdlib"; import { GLTFLoader } from "three-stdlib";
import * as THREE from "three"; import * as THREE from "three";
@@ -8,8 +8,6 @@ import crate from "../../../assets/gltf-glb/crate_box.glb";
import { useProcessAnimation } from "./useProcessAnimations"; import { useProcessAnimation } from "./useProcessAnimations";
import ProcessObject from "./processObject"; import ProcessObject from "./processObject";
import { ProcessData } from "./types"; import { ProcessData } from "./types";
import { useSimulationStates } from "../../../store/store";
import { retrieveGLTF } from "../../../utils/indexDB/idbUtils";
interface ArmBotState { interface ArmBotState {
uuid: string; uuid: string;
@@ -18,9 +16,18 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; }; connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: {
uuid: string;
name: string;
speed: number;
processes: { triggerId: string; startPoint: string; endPoint: string }[];
};
isActive?: boolean;
} }
interface ProcessContainerProps { interface ProcessContainerProps {
processes: ProcessData[]; processes: ProcessData[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>; setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
@@ -36,7 +43,7 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
agvRef, agvRef,
MaterialRef, MaterialRef,
armBots, armBots,
setArmBots setArmBots,
}) => { }) => {
const gltf = useLoader(GLTFLoader, crate) as GLTF; const gltf = useLoader(GLTFLoader, crate) as GLTF;
const groupRef = useRef<THREE.Group>(null); const groupRef = useRef<THREE.Group>(null);
@@ -58,19 +65,23 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
checkAndCountTriggers, checkAndCountTriggers,
} = useProcessAnimation(processes, setProcesses, agvRef, armBots, setArmBots); } = useProcessAnimation(processes, setProcesses, agvRef, armBots, setArmBots);
const baseMaterials = useMemo(() => ({ const baseMaterials = useMemo(
Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }), () => ({
Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }), Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
Default: new THREE.MeshStandardMaterial(), Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
}), []); Default: new THREE.MeshStandardMaterial(),
}),
[]
);
useEffect(() => { useEffect(() => {
// Update material references for all spawned objects // Update material references for all spawned objects
Object.entries(animationStates).forEach(([processId, processState]) => { Object.entries(animationStates).forEach(([processId, processState]) => {
Object.keys(processState.spawnedObjects).forEach((objectId) => { Object.keys(processState.spawnedObjects).forEach((objectId) => {
const entry = { processId, objectId, }; const entry = { processId, objectId };
const materialType = processState.spawnedObjects[objectId]?.currentMaterialType; const materialType =
processState.spawnedObjects[objectId]?.currentMaterialType;
if (!materialType) { if (!materialType) {
return; return;
@@ -79,13 +90,17 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
const matRefArray = MaterialRef.current; const matRefArray = MaterialRef.current;
// Find existing material group // Find existing material group
const existing = matRefArray.find((entryGroup: { material: string; objects: any[] }) => const existing = matRefArray.find(
entryGroup.material === materialType (entryGroup: { material: string; objects: any[] }) =>
entryGroup.material === materialType
); );
if (existing) { if (existing) {
// Check if this processId + objectId already exists // Check if this processId + objectId already exists
const alreadyExists = existing.objects.some((o: any) => o.processId === entry.processId && o.objectId === entry.objectId); const alreadyExists = existing.objects.some(
(o: any) =>
o.processId === entry.processId && o.objectId === entry.objectId
);
if (!alreadyExists) { if (!alreadyExists) {
existing.objects.push(entry); existing.objects.push(entry);
@@ -103,9 +118,31 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
// In processAnimator.tsx - only the relevant spawn logic part that needs fixes // In processAnimator.tsx - only the relevant spawn logic part that needs fixes
// Add this function to ProcessAnimator component
const isConnectedToActiveArmBot = useCallback(
(processId: any) => {
// Check if any active armbot is connected to this process
return armBots.some((armbot) => {
if (!armbot.isActive) return false;
// Check if this armbot is connected to the process
return armbot.connections?.targets?.some((connection) => {
// Find the process that owns this modelUUID
const connectedProcess = processes.find((p) =>
p.paths?.some((path) => path.modeluuid === connection.modelUUID)
);
return connectedProcess?.id === processId;
});
});
},
[armBots, processes]
);
// First useFrame for spawn logic
useFrame(() => { useFrame(() => {
// Spawn logic frame // Spawn logic frame
const currentTime = clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current; const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => { setAnimationStates((prev) => {
const newStates = { ...prev }; const newStates = { ...prev };
@@ -114,26 +151,47 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
const processState = newStates[process.id]; const processState = newStates[process.id];
if (!processState) return; if (!processState) return;
// Check connection status
const isConnected = isConnectedToActiveArmBot(process.id);
if (processState.isProcessDelaying) { if (processState.isProcessDelaying) {
// Existing delay handling logic... // Existing delay handling logic...
return; return;
} }
if (isConnected) {
newStates[process.id] = {
...processState,
nextSpawnTime: Infinity, // Prevent future spawns
};
return;
}
const spawnPoint = findSpawnPoint(process); const spawnPoint = findSpawnPoint(process);
if (!spawnPoint || !spawnPoint.actions) return; if (!spawnPoint || !spawnPoint.actions) {
// console.log(
// `Process ${process.id} has no valid spawn point or actions`
// );
return;
}
const spawnAction = spawnPoint.actions.find( const spawnAction = spawnPoint.actions.find(
(a) => a.isUsed && a.type === "Spawn" (a) => a.isUsed && a.type === "Spawn"
); );
if (!spawnAction) return; if (!spawnAction) {
return;
}
const spawnInterval = const spawnInterval =
typeof spawnAction.spawnInterval === "number" typeof spawnAction.spawnInterval === "number"
? spawnAction.spawnInterval ? spawnAction.spawnInterval
: parseFloat(spawnAction.spawnInterval as string) || 0; : parseFloat(spawnAction.spawnInterval || "0") || 0;
// Check if this is a zero interval spawn and we already spawned an object // Check if this is a zero interval spawn and we already spawned an object
if (spawnInterval === 0 && processState.hasSpawnedZeroIntervalObject === true) { if (
spawnInterval === 0 &&
processState.hasSpawnedZeroIntervalObject === true
) {
return; // Don't spawn more objects for zero interval return; // Don't spawn more objects for zero interval
} }
@@ -149,6 +207,15 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
baseMaterials baseMaterials
); );
// Initialize state properly to ensure animation
newObject.state = {
...newObject.state,
isAnimating: true,
isDelaying: false,
delayComplete: false,
progress: 0.005, // Start with tiny progress to ensure animation begins
};
// Update state with the new object and flag for zero interval // Update state with the new object and flag for zero interval
newStates[process.id] = { newStates[process.id] = {
...processState, ...processState,
@@ -171,6 +238,7 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
}); });
}); });
// Second useFrame for animation logic
useFrame((_, delta) => { useFrame((_, delta) => {
// Animation logic frame // Animation logic frame
const currentTime = const currentTime =
@@ -181,8 +249,42 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
processedProcesses.forEach((process) => { processedProcesses.forEach((process) => {
const processState = newStates[process.id]; const processState = newStates[process.id];
if (!processState) return; if (!processState) {
return;
}
// Check connection status with debugging
const isConnected = isConnectedToActiveArmBot(process.id);
// console.log(
// `Process ${process.id} animation - connected:`,
// isConnected
// );
if (isConnected) {
// Stop all animations when connected to active arm bot
newStates[process.id] = {
...processState,
spawnedObjects: Object.entries(processState.spawnedObjects).reduce(
(acc, [id, obj]) => ({
...acc,
[id]: {
...obj,
state: {
...obj.state,
isAnimating: false, // Stop animation
isDelaying: false, // Clear delays
delayComplete: false, // Reset delays
progress: 0, // Reset progress
},
},
}),
{}
),
};
return;
}
// Process delay handling
if (processState.isProcessDelaying) { if (processState.isProcessDelaying) {
const effectiveDelayTime = const effectiveDelayTime =
processState.processDelayDuration / speedRef.current; processState.processDelayDuration / speedRef.current;
@@ -191,6 +293,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
currentTime - processState.processDelayStartTime >= currentTime - processState.processDelayStartTime >=
effectiveDelayTime effectiveDelayTime
) { ) {
// console.log(
// `Process ${process.id} delay completed, resuming animation`
// );
newStates[process.id] = { newStates[process.id] = {
...processState, ...processState,
isProcessDelaying: false, isProcessDelaying: false,
@@ -214,26 +319,42 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
{} {}
), ),
}; };
return newStates; return;
} else { } else {
return newStates; return;
} }
} }
// Ensure we have a valid path to follow
const path = const path =
process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) ||
[]; [];
if (path.length < 2) return;
if (path.length < 2) {
// console.log(
// `Process ${process.id} has insufficient path points: ${path.length}`
// );
return;
}
const updatedObjects = { ...processState.spawnedObjects }; const updatedObjects = { ...processState.spawnedObjects };
let animationOccurring = false; // Track if any animation is happening
Object.entries(processState.spawnedObjects).forEach( Object.entries(processState.spawnedObjects).forEach(
([objectId, obj]) => { ([objectId, obj]) => {
if (!obj.visible) return; if (!obj.visible) {
return;
}
const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
if (!currentRef) return; if (!currentRef) {
// console.log(
// `No reference for object ${objectId}, skipping animation`
// );
return;
}
// Initialize position for new objects
if ( if (
obj.position && obj.position &&
obj.state.currentIndex === 0 && obj.state.currentIndex === 0 &&
@@ -244,11 +365,22 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
const stateRef = obj.state; const stateRef = obj.state;
// Ensure animation state is properly set for objects
if (!stateRef.isAnimating && !stateRef.isDelaying && !isConnected) {
stateRef.isAnimating = true;
stateRef.progress =
stateRef.progress > 0 ? stateRef.progress : 0.005;
}
// Handle delay logic
if (stateRef.isDelaying) { if (stateRef.isDelaying) {
const effectiveDelayTime = const effectiveDelayTime =
stateRef.currentDelayDuration / speedRef.current; stateRef.currentDelayDuration / speedRef.current;
if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) { if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) {
// console.log(
// `Delay complete for object ${objectId}, resuming animation`
// );
stateRef.isDelaying = false; stateRef.isDelaying = false;
stateRef.delayComplete = true; stateRef.delayComplete = true;
stateRef.isAnimating = true; stateRef.isAnimating = true;
@@ -274,8 +406,17 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
} }
} }
if (!stateRef.isAnimating) return; // Skip non-animating objects
if (!stateRef.isAnimating) {
// console.log(
// `Object ${objectId} not animating, skipping animation updates`
// );
return;
}
animationOccurring = true; // Mark that animation is happening
// Handle point actions
const currentPointData = getPointDataForAnimationIndex( const currentPointData = getPointDataForAnimationIndex(
process, process,
stateRef.currentIndex stateRef.currentIndex
@@ -300,51 +441,22 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
const nextPointIdx = stateRef.currentIndex + 1; const nextPointIdx = stateRef.currentIndex + 1;
const isLastPoint = nextPointIdx >= path.length; const isLastPoint = nextPointIdx >= path.length;
// if (isLastPoint) { // Handle objects at the last point
// if (currentPointData?.actions) {
// const shouldStop = !hasNonInheritActions(
// currentPointData.actions
// );
// if (shouldStop) {
// return;
// }
// }
// }
// if (isLastPoint) {
// if (currentPointData?.actions) {
// const hasNonInherit = hasNonInheritActions(
// currentPointData.actions
// );
// if (!hasNonInherit) {
// // Remove the object if all actions are inherit
// updatedObjects[objectId] = {
// ...obj,
// visible: false,
// state: { ...stateRef, isAnimating: false },
// };
// return;
// }
// } else {
// // No actions at last point - remove the object
// updatedObjects[objectId] = {
// ...obj,
// visible: false,
// state: { ...stateRef, isAnimating: false },
// };
// return;
// }
// }
if (isLastPoint) { if (isLastPoint) {
const isAgvPicking = agvRef.current.some( const isAgvPicking = agvRef.current.some(
(agv: any) => agv.processId === process.id && agv.status === "picking" (agv: any) =>
agv.processId === process.id && agv.status === "picking"
); );
const shouldHide = !currentPointData?.actions || !hasNonInheritActions(currentPointData.actions); const shouldHide =
!currentPointData?.actions ||
!hasNonInheritActions(currentPointData.actions);
if (shouldHide) { if (shouldHide) {
if (isAgvPicking) { if (isAgvPicking) {
// console.log(
// `AGV picking at last point for object ${objectId}, hiding object`
// );
updatedObjects[objectId] = { updatedObjects[objectId] = {
...obj, ...obj,
visible: false, visible: false,
@@ -370,9 +482,11 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
} }
} }
// Handle stacked objects when AGV picks
if (tempStackedObjectsRef.current[objectId]) { if (tempStackedObjectsRef.current[objectId]) {
const isAgvPicking = agvRef.current.some( const isAgvPicking = agvRef.current.some(
(agv: any) => agv.processId === process.id && agv.status === "picking" (agv: any) =>
agv.processId === process.id && agv.status === "picking"
); );
if (isAgvPicking) { if (isAgvPicking) {
@@ -386,22 +500,34 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
isAnimating: false, isAnimating: false,
}, },
}; };
return;
} }
} }
// Handle normal animation progress for objects not at last point
if (!isLastPoint) { if (!isLastPoint) {
const nextPoint = path[nextPointIdx]; const nextPoint = path[nextPointIdx];
const distance = path[stateRef.currentIndex].distanceTo(nextPoint); const distance =
path[stateRef.currentIndex].distanceTo(nextPoint);
const effectiveSpeed = stateRef.speed * speedRef.current; const effectiveSpeed = stateRef.speed * speedRef.current;
const movement = effectiveSpeed * delta; const movement = effectiveSpeed * delta;
// Ensure progress is always moving forward
if (stateRef.delayComplete && stateRef.progress < 0.01) { if (stateRef.delayComplete && stateRef.progress < 0.01) {
stateRef.progress = 0.05; stateRef.progress = 0.05;
stateRef.delayComplete = false; stateRef.delayComplete = false;
// console.log(
// `Boosting progress for object ${objectId} after delay`
// );
} else { } else {
stateRef.progress += movement / distance; stateRef.progress += movement / distance;
// console.log(
// `Object ${objectId} progress: ${stateRef.progress.toFixed(3)}`
// );
} }
// Handle point transition
if (stateRef.progress >= 1) { if (stateRef.progress >= 1) {
stateRef.currentIndex = nextPointIdx; stateRef.currentIndex = nextPointIdx;
stateRef.progress = 0; stateRef.progress = 0;
@@ -420,7 +546,10 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
process, process,
stateRef.currentIndex stateRef.currentIndex
); );
// No action needed with newPointData here - will be handled in next frame
} else { } else {
// Update position with lerp
currentRef.position.lerpVectors( currentRef.position.lerpVectors(
path[stateRef.currentIndex], path[stateRef.currentIndex],
nextPoint, nextPoint,
@@ -433,6 +562,13 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
} }
); );
// Log if no animation is occurring when it should
if (!animationOccurring && !isConnected) {
// console.log(
// `Warning: No animation occurring for process ${process.id} despite not being connected`
// );
}
newStates[process.id] = { newStates[process.id] = {
...processState, ...processState,
spawnedObjects: updatedObjects, spawnedObjects: updatedObjects,

View File

@@ -9,7 +9,12 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; }; actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
} }
interface ProcessContainerProps { interface ProcessContainerProps {

View File

@@ -1,450 +1,3 @@
// import React, {
// useEffect,
// useMemo,
// useState,
// useCallback,
// useRef,
// } from "react";
// import { useSimulationStates } from "../../../store/store";
// import * as THREE from "three";
// import { useThree } from "@react-three/fiber";
// import {
// ConveyorEventsSchema,
// VehicleEventsSchema,
// } from "../../../types/world/worldTypes";
// // Type definitions
// export interface PointAction {
// uuid: string;
// name: string;
// type: string;
// material: string;
// delay: number | string;
// spawnInterval: string | number;
// isUsed: boolean;
// }
// export interface PointTrigger {
// uuid: string;
// bufferTime: number;
// name: string;
// type: string;
// isUsed: boolean;
// }
// export interface PathPoint {
// uuid: string;
// position: [number, number, number];
// actions: PointAction[];
// triggers: PointTrigger[];
// connections: {
// targets: Array<{ modelUUID: string }>;
// };
// }
// export interface SimulationPath {
// type: string;
// modeluuid: string;
// points: PathPoint[];
// pathPosition: [number, number, number];
// speed?: number;
// }
// export interface Process {
// id: string;
// paths: SimulationPath[];
// animationPath: THREE.Vector3[];
// pointActions: PointAction[][];
// pointTriggers: PointTrigger[][];
// speed: number;
// isActive: boolean;
// }
// interface ProcessCreatorProps {
// onProcessesCreated: (processes: Process[]) => void;
// }
// // Convert event schemas to SimulationPath
// function convertToSimulationPath(
// path: ConveyorEventsSchema | VehicleEventsSchema
// ): SimulationPath {
// const { modeluuid } = path;
// // Normalized action handler
// const normalizeAction = (action: any): PointAction => {
// return { ...action }; // Return exact copy with no modifications
// };
// // Normalized trigger handler
// const normalizeTrigger = (trigger: any): PointTrigger => {
// return { ...trigger }; // Return exact copy with no modifications
// };
// if (path.type === "Conveyor") {
// return {
// type: path.type,
// modeluuid,
// points: path.points.map((point) => ({
// uuid: point.uuid,
// position: point.position,
// actions: Array.isArray(point.actions)
// ? point.actions.map(normalizeAction)
// : point.actions
// ? [normalizeAction(point.actions)]
// : [],
// triggers: Array.isArray(point.triggers)
// ? point.triggers.map(normalizeTrigger)
// : point.triggers
// ? [normalizeTrigger(point.triggers)]
// : [],
// connections: {
// targets: point.connections.targets.map((target) => ({
// modelUUID: target.modelUUID,
// })),
// },
// })),
// pathPosition: path.position,
// speed:
// typeof path.speed === "string"
// ? parseFloat(path.speed) || 1
// : path.speed || 1,
// };
// } else {
// // For vehicle paths, handle the case where triggers might not exist
// return {
// type: path.type,
// modeluuid,
// points: [
// {
// uuid: path.points.uuid,
// position: path.points.position,
// actions: Array.isArray(path.points.actions)
// ? path.points.actions.map(normalizeAction)
// : path.points.actions
// ? [normalizeAction(path.points.actions)]
// : [],
// // For vehicle paths, since triggers might not exist in the schema,
// // we always define default to an empty array
// triggers: [],
// connections: {
// targets: path.points.connections.targets.map((target) => ({
// modelUUID: target.modelUUID,
// })),
// },
// },
// ],
// pathPosition: path.position,
// speed: path.points.speed || 1,
// };
// }
// }
// // Helper function to create an empty process
// const createEmptyProcess = (): Process => ({
// id: `process-${Math.random().toString(36).substring(2, 11)}`,
// paths: [],
// animationPath: [],
// pointActions: [],
// pointTriggers: [], // Added point triggers array
// speed: 1,
// isActive: false,
// });
// // Enhanced connection checking function
// function shouldReverseNextPath(
// currentPath: SimulationPath,
// nextPath: SimulationPath
// ): boolean {
// if (nextPath.points.length !== 3) return false;
// const currentLastPoint = currentPath.points[currentPath.points.length - 1];
// const nextFirstPoint = nextPath.points[0];
// const nextLastPoint = nextPath.points[nextPath.points.length - 1];
// // Check if current last connects to next last (requires reversal)
// const connectsToLast = currentLastPoint.connections.targets.some(
// (target) =>
// target.modelUUID === nextPath.modeluuid &&
// nextLastPoint.connections.targets.some(
// (t) => t.modelUUID === currentPath.modeluuid
// )
// );
// // Check if current last connects to next first (no reversal needed)
// const connectsToFirst = currentLastPoint.connections.targets.some(
// (target) =>
// target.modelUUID === nextPath.modeluuid &&
// nextFirstPoint.connections.targets.some(
// (t) => t.modelUUID === currentPath.modeluuid
// )
// );
// // Only reverse if connected to last point and not to first point
// return connectsToLast && !connectsToFirst;
// }
// // Check if a point has a spawn action
// function hasSpawnAction(point: PathPoint): boolean {
// return point.actions.some((action) => action.type.toLowerCase() === "spawn");
// }
// // Ensure spawn point is always at the beginning of the path
// function ensureSpawnPointIsFirst(path: SimulationPath): SimulationPath {
// if (path.points.length !== 3) return path;
// // If the third point has spawn action and first doesn't, reverse the array
// if (hasSpawnAction(path.points[2]) && !hasSpawnAction(path.points[0])) {
// return {
// ...path,
// points: [...path.points].reverse(),
// };
// }
// return path;
// }
// // Updated path adjustment function
// function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] {
// if (paths.length < 1) return paths;
// const adjustedPaths = [...paths];
// // First ensure all paths have spawn points at the beginning
// for (let i = 0; i < adjustedPaths.length; i++) {
// adjustedPaths[i] = ensureSpawnPointIsFirst(adjustedPaths[i]);
// }
// // Then handle connections between paths
// for (let i = 0; i < adjustedPaths.length - 1; i++) {
// const currentPath = adjustedPaths[i];
// const nextPath = adjustedPaths[i + 1];
// if (shouldReverseNextPath(currentPath, nextPath)) {
// const reversedPoints = [
// nextPath.points[2],
// nextPath.points[1],
// nextPath.points[0],
// ];
// adjustedPaths[i + 1] = {
// ...nextPath,
// points: reversedPoints,
// };
// }
// }
// return adjustedPaths;
// }
// // Main hook for process creation
// export function useProcessCreation() {
// const { scene } = useThree();
// const [processes, setProcesses] = useState<Process[]>([]);
// const hasSpawnAction = useCallback((path: SimulationPath): boolean => {
// if (path.type !== "Conveyor") return false;
// return path.points.some((point) =>
// point.actions.some((action) => action.type.toLowerCase() === "spawn")
// );
// }, []);
// const createProcess = useCallback(
// (paths: SimulationPath[]): Process => {
// if (!paths || paths.length === 0) {
// return createEmptyProcess();
// }
// const animationPath: THREE.Vector3[] = [];
// const pointActions: PointAction[][] = [];
// const pointTriggers: PointTrigger[][] = []; // Added point triggers collection
// const processSpeed = paths[0]?.speed || 1;
// for (const path of paths) {
// for (const point of path.points) {
// if (path.type === "Conveyor") {
// const obj = scene.getObjectByProperty("uuid", point.uuid);
// if (!obj) {
// console.warn(`Object with UUID ${point.uuid} not found in scene`);
// continue;
// }
// const position = obj.getWorldPosition(new THREE.Vector3());
// animationPath.push(position.clone());
// pointActions.push(point.actions);
// pointTriggers.push(point.triggers); // Collect triggers for each point
// }
// }
// }
// return {
// id: `process-${Math.random().toString(36).substring(2, 11)}`,
// paths,
// animationPath,
// pointActions,
// pointTriggers,
// speed: processSpeed,
// isActive: false,
// };
// },
// [scene]
// );
// const getAllConnectedPaths = useCallback(
// (
// initialPath: SimulationPath,
// allPaths: SimulationPath[],
// visited: Set<string> = new Set()
// ): SimulationPath[] => {
// const connectedPaths: SimulationPath[] = [];
// const queue: SimulationPath[] = [initialPath];
// visited.add(initialPath.modeluuid);
// const pathMap = new Map<string, SimulationPath>();
// allPaths.forEach((path) => pathMap.set(path.modeluuid, path));
// while (queue.length > 0) {
// const currentPath = queue.shift()!;
// connectedPaths.push(currentPath);
// // Process outgoing connections
// for (const point of currentPath.points) {
// for (const target of point.connections.targets) {
// if (!visited.has(target.modelUUID)) {
// const targetPath = pathMap.get(target.modelUUID);
// if (targetPath) {
// visited.add(target.modelUUID);
// queue.push(targetPath);
// }
// }
// }
// }
// // Process incoming connections
// for (const [uuid, path] of pathMap) {
// if (!visited.has(uuid)) {
// const hasConnectionToCurrent = path.points.some((point) =>
// point.connections.targets.some(
// (t) => t.modelUUID === currentPath.modeluuid
// )
// );
// if (hasConnectionToCurrent) {
// visited.add(uuid);
// queue.push(path);
// }
// }
// }
// }
// return connectedPaths;
// },
// []
// );
// const createProcessesFromPaths = useCallback(
// (paths: SimulationPath[]): Process[] => {
// if (!paths || paths.length === 0) return [];
// const visited = new Set<string>();
// const processes: Process[] = [];
// const pathMap = new Map<string, SimulationPath>();
// paths.forEach((path) => pathMap.set(path.modeluuid, path));
// for (const path of paths) {
// if (!visited.has(path.modeluuid) && hasSpawnAction(path)) {
// const connectedPaths = getAllConnectedPaths(path, paths, visited);
// const adjustedPaths = adjustPathPointsOrder(connectedPaths);
// const process = createProcess(adjustedPaths);
// processes.push(process);
// }
// }
// return processes;
// },
// [createProcess, getAllConnectedPaths, hasSpawnAction]
// );
// return {
// processes,
// createProcessesFromPaths,
// setProcesses,
// };
// }
// const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
// ({ onProcessesCreated }) => {
// const { simulationStates } = useSimulationStates();
// const { createProcessesFromPaths } = useProcessCreation();
// const prevPathsRef = useRef<SimulationPath[]>([]);
// const prevProcessesRef = useRef<Process[]>([]);
// const convertedPaths = useMemo((): SimulationPath[] => {
// if (!simulationStates) return [];
// return simulationStates.map((path) =>
// convertToSimulationPath(
// path as ConveyorEventsSchema | VehicleEventsSchema
// )
// );
// }, [simulationStates]);
// // Enhanced dependency tracking that includes action and trigger types
// const pathsDependency = useMemo(() => {
// if (!convertedPaths) return null;
// return convertedPaths.map((path) => ({
// id: path.modeluuid,
// // Track all action types for each point
// actionSignature: path.points
// .map((point, index) =>
// point.actions.map((action) => `${index}-${action.type}`).join("|")
// )
// .join(","),
// // Track all trigger types for each point
// triggerSignature: path.points
// .map((point, index) =>
// point.triggers
// .map((trigger) => `${index}-${trigger.type}`)
// .join("|")
// )
// .join(","),
// connections: path.points
// .flatMap((p: PathPoint) =>
// p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID)
// )
// .join(","),
// isActive: false,
// }));
// }, [convertedPaths]);
// // Force process recreation when paths change
// useEffect(() => {
// if (!convertedPaths || convertedPaths.length === 0) {
// if (prevProcessesRef.current.length > 0) {
// onProcessesCreated([]);
// prevProcessesRef.current = [];
// }
// return;
// }
// // Always regenerate processes if the pathsDependency has changed
// // This ensures action and trigger type changes will be detected
// const newProcesses = createProcessesFromPaths(convertedPaths);
// prevPathsRef.current = convertedPaths;
// // Always update processes when action or trigger types change
// onProcessesCreated(newProcesses);
// prevProcessesRef.current = newProcesses;
// }, [
// pathsDependency, // This now includes action and trigger types
// onProcessesCreated,
// convertedPaths,
// createProcessesFromPaths,
// ]);
// return null;
// }
// );
// export default ProcessCreator;
import React, { import React, {
useEffect, useEffect,
useMemo, useMemo,
@@ -456,6 +9,7 @@ import { useSimulationStates } from "../../../store/store";
import * as THREE from "three"; import * as THREE from "three";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { import {
ArmBotEventsSchema,
ConveyorEventsSchema, ConveyorEventsSchema,
VehicleEventsSchema, VehicleEventsSchema,
} from "../../../types/simulationTypes"; } from "../../../types/simulationTypes";
@@ -480,13 +34,14 @@ export interface PointTrigger {
isUsed: boolean; isUsed: boolean;
} }
// Update the connections type in your interfaces
export interface PathPoint { export interface PathPoint {
uuid: string; uuid: string;
position: [number, number, number]; position: [number, number, number];
actions: PointAction[]; actions: PointAction[];
triggers: PointTrigger[]; triggers: PointTrigger[];
connections: { connections: {
targets: Array<{ modelUUID: string }>; targets: Array<{ modelUUID: string; pointUUID?: string }>;
}; };
} }
@@ -498,6 +53,14 @@ export interface SimulationPath {
speed?: number; speed?: number;
isActive: boolean; isActive: boolean;
} }
export interface ArmBot {
type: string;
modeluuid: string;
points: PathPoint[];
pathPosition: [number, number, number];
speed?: number;
isActive: boolean;
}
export interface Process { export interface Process {
id: string; id: string;
@@ -515,7 +78,7 @@ interface ProcessCreatorProps {
// Convert event schemas to SimulationPath // Convert event schemas to SimulationPath
function convertToSimulationPath( function convertToSimulationPath(
path: ConveyorEventsSchema | VehicleEventsSchema path: ConveyorEventsSchema | VehicleEventsSchema | ArmBotEventsSchema
): SimulationPath { ): SimulationPath {
const { modeluuid } = path; const { modeluuid } = path;
@@ -539,13 +102,13 @@ function convertToSimulationPath(
actions: Array.isArray(point.actions) actions: Array.isArray(point.actions)
? point.actions.map(normalizeAction) ? point.actions.map(normalizeAction)
: point.actions : point.actions
? [normalizeAction(point.actions)] ? [normalizeAction(point.actions)]
: [], : [],
triggers: Array.isArray(point.triggers) triggers: Array.isArray(point.triggers)
? point.triggers.map(normalizeTrigger) ? point.triggers.map(normalizeTrigger)
: point.triggers : point.triggers
? [normalizeTrigger(point.triggers)] ? [normalizeTrigger(point.triggers)]
: [], : [],
connections: { connections: {
targets: point.connections.targets.map((target) => ({ targets: point.connections.targets.map((target) => ({
modelUUID: target.modelUUID, modelUUID: target.modelUUID,
@@ -559,6 +122,36 @@ function convertToSimulationPath(
: path.speed || 1, : path.speed || 1,
isActive: false, // Added missing property isActive: false, // Added missing property
}; };
} else if (path.type === "ArmBot") {
return {
type: path.type,
modeluuid,
points: [
{
uuid: path.points.uuid,
position: path.points.position,
actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction)
: path.points.actions
? [normalizeAction(path.points.actions)]
: [],
triggers: Array.isArray(path.points.triggers)
? path.points.triggers.map(normalizeTrigger)
: path.points.triggers
? [normalizeTrigger(path.points.triggers)]
: [],
connections: {
targets: path.points.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
pointUUID: target.pointUUID, // Include if available
})),
},
},
],
pathPosition: path.position,
speed: path.points.actions?.speed || 1,
isActive: false,
};
} else { } else {
// For vehicle paths, handle the case where triggers might not exist // For vehicle paths, handle the case where triggers might not exist
return { return {
@@ -571,8 +164,8 @@ function convertToSimulationPath(
actions: Array.isArray(path.points.actions) actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction) ? path.points.actions.map(normalizeAction)
: path.points.actions : path.points.actions
? [normalizeAction(path.points.actions)] ? [normalizeAction(path.points.actions)]
: [], : [],
triggers: [], triggers: [],
connections: { connections: {
targets: path.points.connections.targets.map((target) => ({ targets: path.points.connections.targets.map((target) => ({
@@ -831,7 +424,10 @@ const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
if (!simulationStates) return []; if (!simulationStates) return [];
return simulationStates.map((path) => return simulationStates.map((path) =>
convertToSimulationPath( convertToSimulationPath(
path as ConveyorEventsSchema | VehicleEventsSchema path as
| ConveyorEventsSchema
| VehicleEventsSchema
| ArmBotEventsSchema
) )
); );
}, [simulationStates]); }, [simulationStates]);

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,12 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; }; actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
} }
interface StaticMachineState { interface StaticMachineState {

View File

@@ -2,6 +2,7 @@ import React, { useEffect } from 'react'
import * as SimulationTypes from '../../../types/simulationTypes'; import * as SimulationTypes from '../../../types/simulationTypes';
import { useSimulationStates } from '../../../store/store'; import { useSimulationStates } from '../../../store/store';
import StaticMachineInstances from './staticMachineInstances'; import StaticMachineInstances from './staticMachineInstances';
import { useResetButtonStore } from '../../../store/usePlayButtonStore';
interface ArmBotState { interface ArmBotState {
uuid: string; uuid: string;
@@ -10,9 +11,13 @@ interface ArmBotState {
status: string; status: string;
material: string; material: string;
triggerId: string; triggerId: string;
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; }; actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[]; };
isActive?: boolean;
} }
interface StaticMachineState { interface StaticMachineState {
uuid: string; uuid: string;
status: string; status: string;
@@ -30,6 +35,7 @@ type StaticMachineProps = {
function StaticMachine({ setArmBots, staticMachines, setStaticMachines }: StaticMachineProps) { function StaticMachine({ setArmBots, staticMachines, setStaticMachines }: StaticMachineProps) {
const { simulationStates } = useSimulationStates(); const { simulationStates } = useSimulationStates();
const { isReset } = useResetButtonStore();
useEffect(() => { useEffect(() => {
const filtered = simulationStates.filter((s): s is SimulationTypes.StaticMachineEventsSchema => s.type === "StaticMachine"); const filtered = simulationStates.filter((s): s is SimulationTypes.StaticMachineEventsSchema => s.type === "StaticMachine");
@@ -43,7 +49,7 @@ function StaticMachine({ setArmBots, staticMachines, setStaticMachines }: Static
connectedArmBot: machine.points.connections.targets[0].modelUUID connectedArmBot: machine.points.connections.targets[0].modelUUID
})); }));
setStaticMachines(initialStates); setStaticMachines(initialStates);
}, [simulationStates]); }, [simulationStates, isReset]);
const updateArmBotTriggerAndMachineStatus = (armBotUuid: string, triggerId: string, machineId: string) => { const updateArmBotTriggerAndMachineStatus = (armBotUuid: string, triggerId: string, machineId: string) => {
setArmBots((prevArmBots) => { setArmBots((prevArmBots) => {

View File

@@ -104,8 +104,8 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
setShowLeftArrow(isOverflowing && canScrollLeft); setShowLeftArrow(isOverflowing && canScrollLeft);
setShowRightArrow(isOverflowing && canScrollRight); setShowRightArrow(isOverflowing && canScrollRight);
console.log('canScrollRight: ', canScrollRight); // console.log('canScrollRight: ', canScrollRight);
console.log('isOverflowing: ', isOverflowing); // console.log('isOverflowing: ', isOverflowing);
} }
}, []); }, []);

View File

@@ -520,37 +520,37 @@ const DroppedObjects: React.FC = () => {
onPointerUp={handlePointerUp} onPointerUp={handlePointerUp}
className="floating-wrapper" className="floating-wrapper"
> >
{zone.objects.map((obj, index) => { {zone?.objects?.map((obj, index) => {
const topPosition = const topPosition =
typeof obj.position.top === "number" typeof obj?.position?.top === "number"
? `calc(${obj.position.top}px + ${ ? `calc(${obj?.position?.top}px + ${
isPlaying && selectedZone.activeSides.includes("top") isPlaying && selectedZone?.activeSides?.includes("top")
? `${heightMultiplier - 55}px` ? `${heightMultiplier - 55}px`
: "0px" : "0px"
})` })`
: "auto"; : "auto";
const leftPosition = const leftPosition =
typeof obj.position.left === "number" typeof obj?.position?.left === "number"
? `calc(${obj.position.left}px + ${ ? `calc(${obj?.position?.left}px + ${
isPlaying && selectedZone.activeSides.includes("left") isPlaying && selectedZone?.activeSides?.includes("left")
? `${widthMultiplier - 150}px` ? `${widthMultiplier - 150}px`
: "0px" : "0px"
})` })`
: "auto"; : "auto";
const rightPosition = const rightPosition =
typeof obj.position.right === "number" typeof obj?.position?.right === "number"
? `calc(${obj.position.right}px + ${ ? `calc(${obj?.position?.right}px + ${
isPlaying && selectedZone.activeSides.includes("right") isPlaying && selectedZone?.activeSides?.includes("right")
? `${widthMultiplier - 150}px` ? `${widthMultiplier - 150}px`
: "0px" : "0px"
})` })`
: "auto"; : "auto";
const bottomPosition = const bottomPosition =
typeof obj.position.bottom === "number" typeof obj?.position?.bottom === "number"
? `calc(${obj.position.bottom}px + ${ ? `calc(${obj?.position?.bottom}px + ${
isPlaying && selectedZone.activeSides.includes("bottom") isPlaying && selectedZone?.activeSides?.includes("bottom")
? `${heightMultiplier - 55}px` ? `${heightMultiplier - 55}px`
: "0px" : "0px"
})` })`
@@ -558,7 +558,7 @@ const DroppedObjects: React.FC = () => {
return ( return (
<div <div
key={`${zoneName}-${index}`} key={obj.id}
className={`${obj.className} ${ className={`${obj.className} ${
selectedChartId?.id === obj.id && "activeChart" selectedChartId?.id === obj.id && "activeChart"
} `} } `}

View File

@@ -56,6 +56,12 @@ input {
padding: 0 8px; padding: 0 8px;
} }
.input-error {
border: 1px solid #f65648 !important;
outline: none !important;
color: #f65648;
}
.toggle-header-container { .toggle-header-container {
@include flex-center; @include flex-center;
padding: 6px 12px; padding: 6px 12px;
@@ -344,7 +350,6 @@ input {
padding: 10px; padding: 10px;
} }
.loading { .loading {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@@ -364,9 +369,7 @@ input {
left: -50%; left: -50%;
height: 100%; height: 100%;
width: 50%; width: 50%;
background: linear-gradient(to right, background: linear-gradient(to right, var(--accent-color), transparent);
var(--accent-color),
transparent);
animation: loadingAnimation 1.2s linear infinite; animation: loadingAnimation 1.2s linear infinite;
border-radius: 4px; border-radius: 4px;
} }
@@ -381,8 +384,6 @@ input {
} }
} }
.dropdown-item { .dropdown-item {
display: block; display: block;
padding: 5px 10px; padding: 5px 10px;
@@ -710,4 +711,4 @@ input {
.multi-email-invite-input.active { .multi-email-invite-input.active {
border: 1px solid var(--accent-color); border: 1px solid var(--accent-color);
} }
} }