12 Commits

Author SHA1 Message Date
686c4e60c6 feat: Update visibility logic for path points and add new event schemas in simulation types 2025-04-17 17:47:27 +05:30
967f1741b0 Merge pull request 'simulation' (#66) from simulation into main
Reviewed-on: http://185.100.212.76:7776/Dwinzo-Beta/Dwinzo_dev/pulls/66
2025-04-16 13:05:20 +00:00
1e901c327d Merge remote-tracking branch 'origin/rtViz' into simulation 2025-04-16 18:31:41 +05:30
cc074a5913 Merge branch 'realTimeVisulization' into simulation 2025-04-16 18:31:21 +05:30
8e491a0002 feat: Add isActive property to ArmBot state and update IKAnimationController logic
refactor: Remove commented console logs in ProcessAnimator and useProcessAnimations
2025-04-16 18:30:43 +05:30
f7e4f5c580 Merge remote-tracking branch 'origin/simulation-animation' into simulation 2025-04-16 18:05:43 +05:30
64885f246e refactor: Improve error handling and variable naming in Assets and IKAnimationController components 2025-04-16 18:05:23 +05:30
16cf1b96cc bug fix for data selection tab 2025-04-16 18:04:29 +05:30
ee319c28e4 refactor: Remove unnecessary console logs and improve connection limit checks in simulation components 2025-04-16 16:33:48 +05:30
939f6e5086 Merge remote-tracking branch 'origin/simulation' into rtViz 2025-04-16 10:17:22 +05:30
318ac6d939 Merge branch 'main' into rtViz 2025-04-16 09:22:15 +05:30
db9c9fb8b5 duplicate zone rename bug resolved 2025-04-15 18:28:37 +05:30
31 changed files with 584 additions and 317 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

@@ -150,7 +150,6 @@ const ArmBotMechanics: React.FC = () => {
modeluuid: updatedPath.modeluuid, modeluuid: updatedPath.modeluuid,
eventData: { type: "ArmBot", points: updatedPath.points } eventData: { type: "ArmBot", points: updatedPath.points }
} }
console.log('data: ', data);
socket.emit('v2:model-asset:updateEventData', data); socket.emit('v2:model-asset:updateEventData', data);
} }

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

@@ -95,7 +95,7 @@ const Agv: React.FC<ProcessContainerProps> = ({
/> />
{pair.points.slice(1).map((point, idx) => ( {pair.points.slice(1).map((point, idx) => (
<mesh position={[point.x, point.y, point.z]} key={idx}> <mesh position={[point.x, point.y, point.z]} key={idx} visible={!isPlaying}>
<sphereGeometry args={[0.3, 15, 15]} /> <sphereGeometry args={[0.3, 15, 15]} />
<meshStandardMaterial color="red" /> <meshStandardMaterial color="red" />
</mesh> </mesh>

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

@@ -456,7 +456,7 @@ export default function PathNavigator({
}, []); }, []);
return ( return (
<group name="path-navigator-lines"> <group name="path-navigator-lines" visible={!isPlaying} >
{toPickupPath.length > 0 && ( {toPickupPath.length > 0 && (
<Line <Line
points={toPickupPath} points={toPickupPath}

View File

@@ -53,7 +53,8 @@ const ArmBot = ({ armBots, setArmBots, setStaticMachines }: ArmBotProps) => {
material: "default", material: "default",
triggerId: '', triggerId: '',
actions: bot.points.actions, actions: bot.points.actions,
connections: bot.points.connections connections: bot.points.connections,
isActive: false
})); }));
setArmBots(initialStates); setArmBots(initialStates);
}, [simulationStates, isReset]); }, [simulationStates, isReset]);

View File

@@ -46,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 {

View File

@@ -1,9 +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 MaterialInstances from "./MaterialInstances";
import { Line } from "react-chartjs-2";
import { QuadraticBezierLine } from "@react-three/drei";
interface StaticMachineState { interface StaticMachineState {
@@ -70,6 +72,7 @@ const IKAnimationController = ({
const { isPlaying } = usePlayButtonStore();; const { isPlaying } = usePlayButtonStore();;
const statusRef = useRef("idle"); const statusRef = useRef("idle");
const { simulationStates } = useSimulationStates(); const { simulationStates } = useSimulationStates();
const { isReset } = useResetButtonStore();
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);
@@ -78,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(
@@ -116,7 +126,7 @@ const IKAnimationController = ({
]); ]);
}; };
const processCurves = useMemo(() => { const processedCurves = useMemo(() => {
if (!isPlaying) return []; if (!isPlaying) return [];
return processes.map(process => { return processes.map(process => {
@@ -164,8 +174,8 @@ const IKAnimationController = ({
const activeProcess = useMemo(() => { const activeProcess = useMemo(() => {
if (!selectedTrigger) return null; if (!selectedTrigger) return null;
return processCurves.find(p => p?.triggerId === selectedTrigger); return processedCurves.find(p => p?.triggerId === selectedTrigger);
}, [processCurves, selectedTrigger]); }, [processedCurves, selectedTrigger]);
// Initial movement to rest position // Initial movement to rest position
useFrame((_, delta) => { useFrame((_, delta) => {
@@ -229,6 +239,9 @@ const IKAnimationController = ({
currentStatus = "processing"; currentStatus = "processing";
const segmentProgress = (newProgress - restToStartEnd) / (processEnd - restToStartEnd); const segmentProgress = (newProgress - restToStartEnd) / (processEnd - restToStartEnd);
currentPosition = processCurve.getPoint(segmentProgress); currentPosition = processCurve.getPoint(segmentProgress);
if (statusRef.current !== "processing") {
updateConveyorOrStaticMachineStatusOnStart(selectedTrigger);
}
} else { } else {
// Returning to rest position // Returning to rest position
currentStatus = "returning to rest"; currentStatus = "returning to rest";
@@ -245,7 +258,7 @@ const IKAnimationController = ({
// Only trigger when the entire animation is complete (newProgress === 1) // Only trigger when the entire animation is complete (newProgress === 1)
if (newProgress === 1 && currentStatus === "returning to rest") { if (newProgress === 1 && currentStatus === "returning to rest") {
updateConveyorOrStaticMachineStatus(selectedTrigger); updateConveyorOrStaticMachineStatusOnEnd(selectedTrigger);
} }
bone.position.copy(currentPosition); bone.position.copy(currentPosition);
@@ -254,7 +267,45 @@ const IKAnimationController = ({
}); });
}); });
const updateConveyorOrStaticMachineStatus = (selectedTrigger: string) => { 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); const currentProcess = processes.find(p => p.triggerId === selectedTrigger);
if (currentProcess) { if (currentProcess) {
const triggerId = currentProcess.triggerId; const triggerId = currentProcess.triggerId;
@@ -296,7 +347,7 @@ const IKAnimationController = ({
if (matchedMachine.type === "Conveyor") { if (matchedMachine.type === "Conveyor") {
setArmBots((prev) => setArmBots((prev) =>
prev.map((arm) => { prev.map((arm) => {
if (arm.uuid === uuid) { if (arm.uuid === uuid && arm.isActive === true) {
return { return {
...arm, ...arm,
isActive: false, isActive: false,
@@ -314,7 +365,15 @@ const IKAnimationController = ({
} }
} }
return null; return (
<>
<MaterialInstances
statusRef={statusRef}
ikSolver={ikSolver}
targetBoneName={targetBoneName}
/>
</>
);
}; };
export default IKAnimationController; export default IKAnimationController;

View File

@@ -3,37 +3,26 @@ import * as THREE from 'three';
import { Box } from '@react-three/drei'; import { Box } from '@react-three/drei';
type MaterialInstancesProps = { type MaterialInstancesProps = {
groupRef: React.RefObject<THREE.Group>; statusRef: React.RefObject<string>;
activeCurve: any;
progress: number;
ikSolver: any; ikSolver: any;
targetBoneName: string; targetBoneName: string;
}; };
function MaterialInstances({ function MaterialInstances({
groupRef, statusRef,
activeCurve,
progress,
ikSolver, ikSolver,
targetBoneName targetBoneName
}: MaterialInstancesProps) { }: MaterialInstancesProps) {
const { endToRestRange, startToEndRange } = activeCurve; if (!ikSolver) return null;
// Show the box from when we reach the start point until we leave the end point const targetBone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBoneName);
const shouldShow = (progress >= startToEndRange[0] && progress < startToEndRange[1] && progress >= endToRestRange[0]);
if (!shouldShow || !ikSolver) return null;
const targetBone = ikSolver.mesh.skeleton.bones.find(
(b: any) => b.name === targetBoneName
);
if (!targetBone) return null; if (!targetBone) return null;
const worldPos = new THREE.Vector3(); const worldPos = new THREE.Vector3();
targetBone.getWorldPosition(worldPos); targetBone.getWorldPosition(worldPos);
return ( return (
<Box args={[0.5, 0.5, 0.5]} position={worldPos}> <Box args={[0.5, 0.5, 0.5]} position={worldPos} visible={statusRef.current === 'processing'}>
<meshStandardMaterial color="orange" /> <meshStandardMaterial color="orange" />
</Box> </Box>
); );

View File

@@ -40,6 +40,37 @@ function PathConnector({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObje
}; };
const existingTargets = point.connections.targets || []; const existingTargets = point.connections.targets || [];
// Check connection limits
const toPath = simulationStates.find(p => p.modeluuid === toModelUUID);
if (toPath) {
// Check if we already have this type of connection
const hasConveyor = existingTargets.some(t => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === "Conveyor";
});
const hasArmBot = existingTargets.some(t => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === "ArmBot";
});
const hasVehicle = existingTargets.some(t => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === "Vehicle";
});
if (toPath.type === "Conveyor" && hasConveyor) {
console.log("Conveyor can only connect to one other conveyor");
return point;
}
if (toPath.type === "ArmBot" && hasArmBot) {
console.log("Conveyor can only connect to one ArmBot");
return point;
}
if (toPath.type === "Vehicle" && hasVehicle) {
console.log("Conveyor can only connect to one Vehicle");
return point;
}
}
if (!existingTargets.some((target) => target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID)) { if (!existingTargets.some((target) => target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID)) {
return { return {
...point, ...point,
@@ -66,6 +97,36 @@ function PathConnector({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObje
}; };
const existingTargets = point.connections.targets || []; const existingTargets = point.connections.targets || [];
// Check connection limits
const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID);
if (fromPath) {
const hasConveyor = existingTargets.some(t => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === "Conveyor";
});
const hasArmBot = existingTargets.some(t => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === "ArmBot";
});
const hasVehicle = existingTargets.some(t => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === "Vehicle";
});
if (fromPath.type === "Conveyor" && hasConveyor) {
console.log("Conveyor can only connect to one other conveyor");
return point;
}
if (fromPath.type === "ArmBot" && hasArmBot) {
console.log("Conveyor can only connect to one ArmBot");
return point;
}
if (fromPath.type === "Vehicle" && hasVehicle) {
console.log("Conveyor can only connect to one Vehicle");
return point;
}
}
if (!existingTargets.some((target) => target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID)) { if (!existingTargets.some((target) => target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID)) {
return { return {
...point, ...point,
@@ -494,21 +555,59 @@ function PathConnector({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObje
} }
} }
// For non-Vehicle paths, check if already connected // For Conveyors, check connection limits in BOTH DIRECTIONS
if (intersected.userData.path.type !== "Vehicle") { if (firstSelected && (firstPath?.type === "Conveyor" || secondPath?.type === "Conveyor")) {
const isAlreadyConnected = simulationStates.some((path) => { const checkConveyorLimits = (path: any, pointUUID: string) => {
if (path.type === "Conveyor") { if (path?.type === "Conveyor") {
return path.points.some( const point = path.points.find((p: { uuid: string }) => p.uuid === pointUUID);
(point) => if (point) {
point.uuid === sphereUUID && return {
point.connections.targets.length > 0 hasConveyor: point.connections.targets.some((t: { modelUUID: string }) => {
); const targetPath = simulationStates.find((p: { modeluuid: string }) => p.modeluuid === t.modelUUID);
return targetPath?.type === "Conveyor";
}),
hasArmBot: point.connections.targets.some((t: { modelUUID: string }) => {
const targetPath = simulationStates.find((p: { modeluuid: string }) => p.modeluuid === t.modelUUID);
return targetPath?.type === "ArmBot";
}),
hasVehicle: point.connections.targets.some((t: { modelUUID: string }) => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === "Vehicle";
})
};
}
} }
return false; return { hasConveyor: false, hasArmBot: false, hasVehicle: false };
}); };
if (isAlreadyConnected) { const firstConveyorLimits = checkConveyorLimits(firstPath, firstSelected?.sphereUUID);
console.log("Conveyor point is already connected. Ignoring."); const secondConveyorLimits = checkConveyorLimits(secondPath, sphereUUID);
// Check if trying to connect two conveyors
if (firstPath?.type === "Conveyor" && secondPath?.type === "Conveyor") {
if (firstConveyorLimits.hasConveyor || secondConveyorLimits.hasConveyor) {
console.log("Conveyor can only connect to one other conveyor");
return;
}
}
// Check if trying to connect to an ArmBot when already connected to one
if (secondPath?.type === "ArmBot" && firstConveyorLimits.hasArmBot) {
console.log("Conveyor can only connect to one ArmBot");
return;
}
if (firstPath?.type === "ArmBot" && secondConveyorLimits.hasArmBot) {
console.log("Conveyor can only connect to one ArmBot");
return;
}
// Check if trying to connect to a Vehicle when already connected to one
if (secondPath?.type === "Vehicle" && firstConveyorLimits.hasVehicle) {
console.log("Conveyor can only connect to one Vehicle");
return;
}
if (firstPath?.type === "Vehicle" && secondConveyorLimits.hasVehicle) {
console.log("Conveyor can only connect to one Vehicle");
return; return;
} }
} }
@@ -762,6 +861,45 @@ function PathConnector({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObje
!(firstPath?.type === 'Conveyor' || firstPath?.type === 'StaticMachine' || !(firstPath?.type === 'Conveyor' || firstPath?.type === 'StaticMachine' ||
secondPath?.type === 'Conveyor' || secondPath?.type === 'StaticMachine'); secondPath?.type === 'Conveyor' || secondPath?.type === 'StaticMachine');
// NEW: Check conveyor connection limits
let isConveyorAtMaxConnections = false;
if (firstPath?.type === 'Conveyor' || secondPath?.type === 'Conveyor') {
const conveyorPath = firstPath?.type === 'Conveyor' ? firstPath : secondPath;
const otherPath = firstPath?.type === 'Conveyor' ? secondPath : firstPath;
if (conveyorPath) {
const conveyorPoint = Array.isArray(conveyorPath.points)
? conveyorPath.points.find((p: { uuid: string }) => p.uuid ===
(firstPath?.type === 'Conveyor' ? firstSelected.sphereUUID : sphereUUID))
: undefined;
if (conveyorPoint) {
const hasConveyor = conveyorPoint.connections.targets.some((t: { modelUUID: string }) => {
const targetPath = simulationStates.find((p: { modeluuid: string }) => p.modeluuid === t.modelUUID);
return targetPath?.type === 'Conveyor';
});
const hasArmBot = conveyorPoint.connections.targets.some((t: { modelUUID: string }) => {
const targetPath = simulationStates.find((p: { modeluuid: string }) => p.modeluuid === t.modelUUID);
return targetPath?.type === 'ArmBot';
});
const hasVehicle = conveyorPoint.connections.targets.some((t: { modelUUID: string }) => {
const targetPath = simulationStates.find(p => p.modeluuid === t.modelUUID);
return targetPath?.type === 'Vehicle';
});
if (otherPath?.type === 'Conveyor' && hasConveyor) {
isConveyorAtMaxConnections = true;
}
if (otherPath?.type === 'ArmBot' && hasArmBot) {
isConveyorAtMaxConnections = true;
}
if (otherPath?.type === 'Vehicle' && hasVehicle) {
isConveyorAtMaxConnections = true;
}
}
}
}
if ( if (
!isDuplicateConnection && !isDuplicateConnection &&
!isVehicleToVehicle && !isVehicleToVehicle &&
@@ -773,6 +911,7 @@ function PathConnector({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObje
!isArmBotToArmBot && !isArmBotToArmBot &&
!isArmBotToInvalidType && !isArmBotToInvalidType &&
!isArmBotAlreadyConnectedToStatic && !isArmBotAlreadyConnectedToStatic &&
!isConveyorAtMaxConnections && // NEW: Check conveyor limits
firstSelected.sphereUUID !== sphereUUID && firstSelected.sphereUUID !== sphereUUID &&
firstSelected.modelUUID !== modelUUID && firstSelected.modelUUID !== modelUUID &&
(firstSelected.isCorner || isConnectable) && (firstSelected.isCorner || isConnectable) &&
@@ -957,7 +1096,6 @@ function PathConnector({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObje
state.modeluuid === connection2.model state.modeluuid === connection2.model
); );
console.log("updatedPaths: ", updatedPaths);
updateBackend(updatedPaths); updateBackend(updatedPaths);
setSimulationStates(updatedStates); setSimulationStates(updatedStates);

View File

@@ -169,9 +169,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
const spawnPoint = findSpawnPoint(process); const spawnPoint = findSpawnPoint(process);
if (!spawnPoint || !spawnPoint.actions) { if (!spawnPoint || !spawnPoint.actions) {
console.log( // console.log(
`Process ${process.id} has no valid spawn point or actions` // `Process ${process.id} has no valid spawn point or actions`
); // );
return; return;
} }
@@ -255,10 +255,10 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
// Check connection status with debugging // Check connection status with debugging
const isConnected = isConnectedToActiveArmBot(process.id); const isConnected = isConnectedToActiveArmBot(process.id);
console.log( // console.log(
`Process ${process.id} animation - connected:`, // `Process ${process.id} animation - connected:`,
isConnected // isConnected
); // );
if (isConnected) { if (isConnected) {
// Stop all animations when connected to active arm bot // Stop all animations when connected to active arm bot
@@ -293,9 +293,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
currentTime - processState.processDelayStartTime >= currentTime - processState.processDelayStartTime >=
effectiveDelayTime effectiveDelayTime
) { ) {
console.log( // console.log(
`Process ${process.id} delay completed, resuming animation` // `Process ${process.id} delay completed, resuming animation`
); // );
newStates[process.id] = { newStates[process.id] = {
...processState, ...processState,
isProcessDelaying: false, isProcessDelaying: false,
@@ -331,9 +331,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
[]; [];
if (path.length < 2) { if (path.length < 2) {
console.log( // console.log(
`Process ${process.id} has insufficient path points: ${path.length}` // `Process ${process.id} has insufficient path points: ${path.length}`
); // );
return; return;
} }
@@ -348,9 +348,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current; const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
if (!currentRef) { if (!currentRef) {
console.log( // console.log(
`No reference for object ${objectId}, skipping animation` // `No reference for object ${objectId}, skipping animation`
); // );
return; return;
} }
@@ -378,9 +378,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
stateRef.currentDelayDuration / speedRef.current; stateRef.currentDelayDuration / speedRef.current;
if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) { if (currentTime - stateRef.delayStartTime >= effectiveDelayTime) {
console.log( // console.log(
`Delay complete for object ${objectId}, resuming animation` // `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;
@@ -408,9 +408,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
// Skip non-animating objects // Skip non-animating objects
if (!stateRef.isAnimating) { if (!stateRef.isAnimating) {
console.log( // console.log(
`Object ${objectId} not animating, skipping animation updates` // `Object ${objectId} not animating, skipping animation updates`
); // );
return; return;
} }
@@ -454,9 +454,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
if (shouldHide) { if (shouldHide) {
if (isAgvPicking) { if (isAgvPicking) {
console.log( // console.log(
`AGV picking at last point for object ${objectId}, hiding object` // `AGV picking at last point for object ${objectId}, hiding object`
); // );
updatedObjects[objectId] = { updatedObjects[objectId] = {
...obj, ...obj,
visible: false, visible: false,
@@ -517,14 +517,14 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
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( // console.log(
`Boosting progress for object ${objectId} after delay` // `Boosting progress for object ${objectId} after delay`
); // );
} else { } else {
stateRef.progress += movement / distance; stateRef.progress += movement / distance;
console.log( // console.log(
`Object ${objectId} progress: ${stateRef.progress.toFixed(3)}` // `Object ${objectId} progress: ${stateRef.progress.toFixed(3)}`
); // );
} }
// Handle point transition // Handle point transition
@@ -564,9 +564,9 @@ const ProcessAnimator: React.FC<ProcessContainerProps> = ({
// Log if no animation is occurring when it should // Log if no animation is occurring when it should
if (!animationOccurring && !isConnected) { if (!animationOccurring && !isConnected) {
console.log( // console.log(
`Warning: No animation occurring for process ${process.id} despite not being connected` // `Warning: No animation occurring for process ${process.id} despite not being connected`
); // );
} }
newStates[process.id] = { newStates[process.id] = {

View File

@@ -15,7 +15,7 @@ import {
usePlayButtonStore, usePlayButtonStore,
useResetButtonStore, useResetButtonStore,
} from "../../../store/usePlayButtonStore"; } from "../../../store/usePlayButtonStore";
import { usePlayAgv } from "../../../store/store"; import { usePlayAgv, useSimulationStates } from "../../../store/store";
interface ArmBotProcess { interface ArmBotProcess {
triggerId: string; triggerId: string;
@@ -82,6 +82,7 @@ export const useProcessAnimation = (
const [animationStates, setAnimationStates] = useState<Record<string, EnhancedProcessAnimationState>>({}); const [animationStates, setAnimationStates] = useState<Record<string, EnhancedProcessAnimationState>>({});
const speedRef = useRef<number>(speed); const speedRef = useRef<number>(speed);
const { PlayAgv, setPlayAgv } = usePlayAgv(); const { PlayAgv, setPlayAgv } = usePlayAgv();
const { simulationStates } = useSimulationStates();
// Effect hooks // Effect hooks
useEffect(() => { useEffect(() => {
@@ -514,8 +515,6 @@ export const useProcessAnimation = (
newTriggerCounts[triggerKey] = (newTriggerCounts[triggerKey] || 0) + 1; newTriggerCounts[triggerKey] = (newTriggerCounts[triggerKey] || 0) + 1;
shouldLog = true;
newTriggerLogs.push({ timestamp: currentTime, pointId: point.uuid, objectId, triggerId: trigger.uuid, }); newTriggerLogs.push({ timestamp: currentTime, pointId: point.uuid, objectId, triggerId: trigger.uuid, });
const connections = point.connections?.targets || []; const connections = point.connections?.targets || [];
@@ -523,13 +522,19 @@ export const useProcessAnimation = (
connections.forEach((connection) => { connections.forEach((connection) => {
const connectedModelUUID = connection.modelUUID; const connectedModelUUID = connection.modelUUID;
const matchingArmPath = armBotPaths.find((path) => path.modeluuid === connectedModelUUID); const isConveyor = simulationStates.find((state) => state.modeluuid === connectedModelUUID && state.type === "Conveyor");
if (matchingArmPath) { if (!isConveyor) {
deferredArmBotUpdates.current.push({ const matchingArmPath = armBotPaths.find((path) => path.modeluuid === connectedModelUUID);
uuid: connectedModelUUID,
triggerId: trigger.uuid, if (matchingArmPath) {
}); deferredArmBotUpdates.current.push({
uuid: connectedModelUUID,
triggerId: trigger.uuid,
});
} else {
shouldLog = true;
}
} }
}); });
}); });
@@ -592,6 +597,7 @@ export const useProcessAnimation = (
}, []); }, []);
useEffect(() => { useEffect(() => {
// console.log('deferredArmBotUpdates: ', deferredArmBotUpdates);
if (deferredArmBotUpdates.current.length > 0) { if (deferredArmBotUpdates.current.length > 0) {
const updates = [...deferredArmBotUpdates.current]; const updates = [...deferredArmBotUpdates.current];
deferredArmBotUpdates.current = []; deferredArmBotUpdates.current = [];

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);
} }
} }

View File

@@ -161,3 +161,132 @@ export type EventData = {
}; };
}; };
}; };
interface AssetEventSchema {
modelUuid: string;
modelName: string;
position: [number, number, number];
rotation: [number, number, number];
state: "idle" | "running" | "stopped" | "disabled" | "error";
}
interface TriggerSchema {
triggerUuid: string;
triggerName: string;
triggerType: "onComplete" | "onStart" | "onStop" | "delay" | "onError";
delay: number;
triggeredAsset: {
triggeredModel: { modelName: string, modelUuid: string };
triggeredAction: { actionName: string, actionUuid: string };
} | null;
}
interface TransferPointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: {
actionUuid: string;
actionName: string;
actionType: "default" | "spawn" | "swap" | "despawn";
material: string | "inherit";
delay: number | "inherit";
spawnInterval: number | "inherit";
spawnCount: number | "inherit";
triggers: TriggerSchema[] | [];
}[];
}
interface VehiclePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: {
actionUuid: string;
actionName: string;
actionType: "travel";
material: string;
unLoadDuration: number;
loadCapacity: number;
pickUpPoint: { x: number; y: number, z: number } | {};
unLoadPoint: { x: number; y: number, z: number } | {};
triggers: TriggerSchema[] | [];
}[];
}
interface RoboticArmPointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: {
actionUuid: string;
actionName: string;
actionType: "pickAndPlace";
process: { startPoint: string; endPoint: string };
triggers: TriggerSchema[] | [];
}[];
}
interface MachinePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: {
actionUuid: string;
actionName: string;
actionType: "process";
processTime: number;
swapMaterial: string;
triggers: TriggerSchema[] | [];
}[];
}
interface StoragePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: {
actionUuid: string;
actionName: string;
actionType: "storage";
materials: { materialName: string; materialId: string; quantity: number }[];
storageCapacity: number;
}[];
}
interface TransferEventSchema extends AssetEventSchema {
type: "transfer";
speed: number;
points: TransferPointSchema[];
}
interface VehicleSchemaEvent extends AssetEventSchema {
type: "vehicle";
speed: number;
point: VehiclePointSchema;
}
interface RoboticArmSchemaEvent extends AssetEventSchema {
type: "roboticArm";
speed: number;
point: RoboticArmPointSchema;
}
interface MachineSchemaEvent extends AssetEventSchema {
type: "machine";
point: MachinePointSchema;
}
interface StorageSchemaEvent extends AssetEventSchema {
type: "storageUnit";
point: StoragePointSchema;
}
type EventsSchema = TransferEventSchema | VehicleSchemaEvent | RoboticArmSchemaEvent | MachineSchemaEvent | StorageSchemaEvent | [];
type productsSchema = {
productName: string;
productId: string;
eventsData: EventsSchema[];
}[] | []