Merge remote-tracking branch 'origin/main-dev' into main-demo
This commit is contained in:
@@ -199,9 +199,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
|||||||
{["relative", "absolute"].map((position) => (
|
{["relative", "absolute"].map((position) => (
|
||||||
<div
|
<div
|
||||||
key={position}
|
key={position}
|
||||||
className={`type ${
|
className={`type ${currentBlock.positionType === position ? "active" : ""
|
||||||
currentBlock.positionType === position ? "active" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateBlockPositionType(
|
updateBlockPositionType(
|
||||||
selectedBlock,
|
selectedBlock,
|
||||||
@@ -220,8 +219,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -231,8 +230,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -242,8 +241,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -253,8 +252,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -277,7 +276,7 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
|||||||
min={0}
|
min={0}
|
||||||
value={String(
|
value={String(
|
||||||
parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) ||
|
parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) ||
|
||||||
8
|
8
|
||||||
)}
|
)}
|
||||||
placeholder={"Width"}
|
placeholder={"Width"}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
@@ -361,15 +360,9 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
|||||||
<InputRange
|
<InputRange
|
||||||
label={"Blur"}
|
label={"Blur"}
|
||||||
min={0}
|
min={0}
|
||||||
max={8}
|
max={30}
|
||||||
value={parseInt(
|
value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")}
|
||||||
getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(
|
onChange={(value: number) => handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))}
|
||||||
/\d+/
|
|
||||||
)?.[0] || "10"
|
|
||||||
)}
|
|
||||||
onChange={(value: number) =>
|
|
||||||
handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from "../../../icons/ExportCommonIcons";
|
} from "../../../icons/ExportCommonIcons";
|
||||||
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
||||||
import InputRange from "../../../ui/inputs/InputRange";
|
import InputRange from "../../../ui/inputs/InputRange";
|
||||||
import { rgbaToHex } from "../../functions/helpers/colorHandlers";
|
import { getAlphaFromRgba, hexToRgba, rgbaToHex } from "../../functions/helpers/colorHandlers";
|
||||||
import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes";
|
import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes";
|
||||||
import { getCurrentElementStyleValue } from "../../functions/helpers/getCurrentElementStyleValue";
|
import { getCurrentElementStyleValue } from "../../functions/helpers/getCurrentElementStyleValue";
|
||||||
import { ResetIcon } from "../../../icons/SimulationIcons";
|
import { ResetIcon } from "../../../icons/SimulationIcons";
|
||||||
@@ -109,9 +109,8 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
{["relative", "absolute"].map((position) => (
|
{["relative", "absolute"].map((position) => (
|
||||||
<div
|
<div
|
||||||
key={position}
|
key={position}
|
||||||
className={`type ${
|
className={`type ${currentElement.positionType === position ? "active" : ""
|
||||||
currentElement.positionType === position ? "active" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementPositionType(
|
updateElementPositionType(
|
||||||
selectedBlock,
|
selectedBlock,
|
||||||
@@ -131,8 +130,8 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -142,8 +141,8 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -153,8 +152,8 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -164,8 +163,8 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
value={
|
value={
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
? String(
|
? String(
|
||||||
getCurrentElementStyleValue(currentElement, "padding")
|
getCurrentElementStyleValue(currentElement, "padding")
|
||||||
)
|
)
|
||||||
: "120"
|
: "120"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -176,11 +175,10 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
<div className="alignments-section">
|
<div className="alignments-section">
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<div
|
<div
|
||||||
className={`icon ${
|
className={`icon ${getCurrentElementStyleValue(currentElement, "textAlign") === "right"
|
||||||
getCurrentElementStyleValue(currentElement, "textAlign") === "right"
|
|
||||||
? "active"
|
? "active"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
updateElementStyle(selectedBlock, selectedElement, {
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
@@ -190,12 +188,11 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
<AlignRightIcon />
|
<AlignRightIcon />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`icon ${
|
className={`icon ${getCurrentElementStyleValue(currentElement, "textAlign") ===
|
||||||
getCurrentElementStyleValue(currentElement, "textAlign") ===
|
"justify"
|
||||||
"justify"
|
|
||||||
? "active"
|
? "active"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
updateElementStyle(selectedBlock, selectedElement, {
|
||||||
textAlign: "justify",
|
textAlign: "justify",
|
||||||
@@ -205,11 +202,10 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
<AlignJustifyIcon />
|
<AlignJustifyIcon />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`icon ${
|
className={`icon ${getCurrentElementStyleValue(currentElement, "textAlign") === "left"
|
||||||
getCurrentElementStyleValue(currentElement, "textAlign") === "left"
|
|
||||||
? "active"
|
? "active"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
updateElementStyle(selectedBlock, selectedElement, {
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
@@ -221,11 +217,10 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<div
|
<div
|
||||||
className={`icon ${
|
className={`icon ${((currentElement.style.flexDirection as string) || "row") === "row"
|
||||||
((currentElement.style.flexDirection as string) || "row") === "row"
|
|
||||||
? "active"
|
? "active"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
updateElementStyle(selectedBlock, selectedElement, {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -235,12 +230,11 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
<FlexRowIcon />
|
<FlexRowIcon />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`icon ${
|
className={`icon ${((currentElement.style.flexDirection as string) || "row") ===
|
||||||
((currentElement.style.flexDirection as string) || "row") ===
|
"column"
|
||||||
"column"
|
|
||||||
? "active"
|
? "active"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
updateElementStyle(selectedBlock, selectedElement, {
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
@@ -250,12 +244,11 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
<FlexColumnIcon />
|
<FlexColumnIcon />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`icon ${
|
className={`icon ${((currentElement.style.flexDirection as string) || "row") ===
|
||||||
((currentElement.style.flexDirection as string) || "row") ===
|
"row-reverse"
|
||||||
"row-reverse"
|
|
||||||
? "active"
|
? "active"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
updateElementStyle(selectedBlock, selectedElement, {
|
||||||
flexDirection: "row-reverse",
|
flexDirection: "row-reverse",
|
||||||
@@ -265,12 +258,11 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
<FlexRowReverseIcon />
|
<FlexRowReverseIcon />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`icon ${
|
className={`icon ${((currentElement.style.flexDirection as string) || "row") ===
|
||||||
((currentElement.style.flexDirection as string) || "row") ===
|
"column-reverse"
|
||||||
"column-reverse"
|
|
||||||
? "active"
|
? "active"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
updateElementStyle(selectedBlock, selectedElement, {
|
||||||
flexDirection: "column-reverse",
|
flexDirection: "column-reverse",
|
||||||
@@ -282,69 +274,12 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="design-section-footer">
|
<div className="design-section-footer">
|
||||||
<div className="layer-system">
|
<InputWithDropDown
|
||||||
<InputWithDropDown
|
label="Layer"
|
||||||
label="Layer"
|
value={String(currentElement.zIndex || 1)}
|
||||||
value={String(currentElement.zIndex || 1)}
|
placeholder={"Layer"}
|
||||||
placeholder={"Layer"}
|
onChange={(newValue: string) => updateElementZIndex(selectedBlock, selectedElement, Number(newValue))}
|
||||||
onChange={(newValue: string) =>
|
|
||||||
updateElementZIndex(
|
|
||||||
selectedBlock,
|
|
||||||
selectedElement,
|
|
||||||
Number(newValue)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="increase-z"
|
|
||||||
onClick={() => {
|
|
||||||
updateElementZIndex(
|
|
||||||
selectedBlock,
|
|
||||||
selectedElement,
|
|
||||||
Number(currentElement.zIndex ? currentElement.zIndex + 1 : 1)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="decrease-z"
|
|
||||||
onClick={() => {
|
|
||||||
updateElementZIndex(
|
|
||||||
selectedBlock,
|
|
||||||
selectedElement,
|
|
||||||
Number(currentElement.zIndex ? currentElement.zIndex - 1 : 1)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="reset"
|
|
||||||
onClick={() => {
|
|
||||||
updateElementZIndex(selectedBlock, selectedElement, Number(1));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ResetIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<InputRange
|
|
||||||
label={"Blur"}
|
|
||||||
min={0}
|
|
||||||
max={40}
|
|
||||||
value={parseInt(
|
|
||||||
getCurrentElementStyleValue(currentElement, "backdropFilter")?.match(
|
|
||||||
/\d+/
|
|
||||||
)?.[0] || "0"
|
|
||||||
)}
|
|
||||||
onChange={(value: number) =>
|
|
||||||
updateElementStyle(selectedBlock, selectedElement, {
|
|
||||||
backdropFilter: `blur(${Number(value)}px)`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputRange
|
<InputRange
|
||||||
label={"Radius"}
|
label={"Radius"}
|
||||||
min={0}
|
min={0}
|
||||||
@@ -392,7 +327,7 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
label="Width"
|
label="Width"
|
||||||
value={String(
|
value={String(
|
||||||
currentElement.size?.width ??
|
currentElement.size?.width ??
|
||||||
(currentElement.type === "graph" ? 400 : 200)
|
(currentElement.type === "graph" ? 400 : 200)
|
||||||
)}
|
)}
|
||||||
placeholder={"Width"}
|
placeholder={"Width"}
|
||||||
onChange={(newValue: string) => {
|
onChange={(newValue: string) => {
|
||||||
@@ -406,7 +341,7 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
label="Height"
|
label="Height"
|
||||||
value={String(
|
value={String(
|
||||||
currentElement.size?.height ??
|
currentElement.size?.height ??
|
||||||
(currentElement.type === "graph" ? 200 : 60)
|
(currentElement.type === "graph" ? 200 : 60)
|
||||||
)}
|
)}
|
||||||
placeholder={"Height"}
|
placeholder={"Height"}
|
||||||
onChange={(newValue: string) => {
|
onChange={(newValue: string) => {
|
||||||
@@ -420,7 +355,7 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="design-section element-color">
|
<div className="design-section element-color">
|
||||||
<div className="section-header">Element Color</div>
|
<div className="section-header">Background</div>
|
||||||
<div className="data-picker">
|
<div className="data-picker">
|
||||||
<div className="label">Color</div>
|
<div className="label">Color</div>
|
||||||
<div className="left">
|
<div className="left">
|
||||||
@@ -461,6 +396,27 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<InputRange
|
||||||
|
label={"Opacity"}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
value={Math.round(getAlphaFromRgba(getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(0,0,0,1)") * 100)}
|
||||||
|
onChange={(value: number) => {
|
||||||
|
const currentBg = getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(0,0,0,1)";
|
||||||
|
const currentHex = rgbaToHex(currentBg);
|
||||||
|
const newBg = hexToRgba(currentHex, Number(value) / 100);
|
||||||
|
updateElementStyle(selectedBlock, selectedElement, { backgroundColor: newBg });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<InputRange
|
||||||
|
label={"Blur"}
|
||||||
|
min={0}
|
||||||
|
max={30}
|
||||||
|
value={parseInt(getCurrentElementStyleValue(currentElement, "backdropFilter")?.match(/\d+/)?.[0] || "0")}
|
||||||
|
onChange={(value: number) => {
|
||||||
|
updateElementStyle(selectedBlock, selectedElement, { backdropFilter: `blur(${Number(value)}px)` });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
{ id: "productivityMetrics.actionsPerHour", label: "Actions Per Hour", icon: <ParametersIcon /> },
|
{ id: "productivityMetrics.actionsPerHour", label: "Actions Per Hour", icon: <ParametersIcon /> },
|
||||||
{ id: "efficiency.laborProductivity", label: "Labor Productivity", icon: <ParametersIcon /> },
|
{ id: "efficiency.laborProductivity", label: "Labor Productivity", icon: <ParametersIcon /> },
|
||||||
{ id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
|
{ id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
|
||||||
{ id: "workloadDistribution", label: "Workload Distribution", icon: <ParametersIcon /> },
|
{ id: "workloadSummary", label: "Workload Distribution", icon: <ParametersIcon /> },
|
||||||
{ id: "costMetrics.costPerHour", label: "Cost Per Hour", icon: <ParametersIcon /> },
|
{ id: "costMetrics.costPerHour", label: "Cost Per Hour", icon: <ParametersIcon /> },
|
||||||
{ id: "productivityMetrics.distanceTraveled", label: "Distance Traveled", icon: <ParametersIcon /> },
|
{ id: "productivityMetrics.distanceTraveled", label: "Distance Traveled", icon: <ParametersIcon /> },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -60,14 +60,16 @@ export function ModelAnimator({ asset, gltfScene }: ModelAnimatorProps) {
|
|||||||
if (isPlaying && currentAction && !isPaused) {
|
if (isPlaying && currentAction && !isPaused) {
|
||||||
blendFactor.current = 0;
|
blendFactor.current = 0;
|
||||||
|
|
||||||
|
Object.values(actions.current).forEach((action) => {
|
||||||
|
if (action !== currentAction) {
|
||||||
|
action.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
currentAction.reset();
|
currentAction.reset();
|
||||||
currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
|
currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
|
||||||
currentAction.clampWhenFinished = true;
|
currentAction.clampWhenFinished = true;
|
||||||
|
|
||||||
if (previousAction && previousAction !== currentAction) {
|
|
||||||
previousAction.crossFadeTo(currentAction, blendDuration, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentAction.play();
|
currentAction.play();
|
||||||
mixerRef.current.addEventListener("finished", handleAnimationComplete);
|
mixerRef.current.addEventListener("finished", handleAnimationComplete);
|
||||||
setPreviousAnimation(current);
|
setPreviousAnimation(current);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useEffect, useCallback, useRef } from "react";
|
import { useEffect, useCallback, useRef } from "react";
|
||||||
import { useSceneContext } from "../../scene/sceneContext";
|
import { useSceneContext } from "../../scene/sceneContext";
|
||||||
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
||||||
|
|
||||||
function Analyzer() {
|
function Analyzer() {
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const { conveyorStore, machineStore, armBotStore, humanStore, vehicleStore, craneStore, storageUnitStore, materialStore, analysisStore } = useSceneContext();
|
const { conveyorStore, machineStore, armBotStore, humanStore, vehicleStore, craneStore, storageUnitStore, materialStore, analysisStore, humanEventManagerRef } = useSceneContext();
|
||||||
|
|
||||||
const { conveyors } = conveyorStore();
|
const { conveyors } = conveyorStore();
|
||||||
const { machines } = machineStore();
|
const { machines } = machineStore();
|
||||||
@@ -14,6 +14,7 @@ function Analyzer() {
|
|||||||
const { cranes } = craneStore();
|
const { cranes } = craneStore();
|
||||||
const { storageUnits } = storageUnitStore();
|
const { storageUnits } = storageUnitStore();
|
||||||
const { materials, getMaterialsByModel } = materialStore();
|
const { materials, getMaterialsByModel } = materialStore();
|
||||||
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
|
||||||
const { setAnalysis, setAnalyzing, analysis } = analysisStore();
|
const { setAnalysis, setAnalyzing, analysis } = analysisStore();
|
||||||
|
|
||||||
@@ -183,6 +184,15 @@ function Analyzer() {
|
|||||||
>
|
>
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
|
// Track previous actions for ArmBots to detect cycle completion
|
||||||
|
const previousArmBotActionsRef = useRef<Record<string, string | undefined>>({});
|
||||||
|
|
||||||
|
// Track previous action counts for Humans to detect completion from EventManager
|
||||||
|
const previousHumanCountsRef = useRef<Record<string, Record<string, number>>>({});
|
||||||
|
|
||||||
|
// Track previous vehicle phases to detect trip completion
|
||||||
|
const previousVehiclePhasesRef = useRef<Record<string, string>>({});
|
||||||
|
|
||||||
// Material lifecycle tracking
|
// Material lifecycle tracking
|
||||||
const materialLifecycleRef = useRef<
|
const materialLifecycleRef = useRef<
|
||||||
Record<
|
Record<
|
||||||
@@ -214,6 +224,9 @@ function Analyzer() {
|
|||||||
performanceSnapshotsRef.current = {};
|
performanceSnapshotsRef.current = {};
|
||||||
bottleneckEventsRef.current = {};
|
bottleneckEventsRef.current = {};
|
||||||
previousAssetStatesRef.current = {};
|
previousAssetStatesRef.current = {};
|
||||||
|
previousArmBotActionsRef.current = {};
|
||||||
|
previousHumanCountsRef.current = {};
|
||||||
|
previousVehiclePhasesRef.current = {};
|
||||||
materialLifecycleRef.current = {};
|
materialLifecycleRef.current = {};
|
||||||
setAnalysis(null);
|
setAnalysis(null);
|
||||||
setAnalyzing(false);
|
setAnalyzing(false);
|
||||||
@@ -222,6 +235,9 @@ function Analyzer() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
resetAllRefs();
|
resetAllRefs();
|
||||||
|
} else {
|
||||||
|
// Reset start time when simulation starts
|
||||||
|
startTimeRef.current = new Date().toISOString();
|
||||||
}
|
}
|
||||||
}, [isPlaying]);
|
}, [isPlaying]);
|
||||||
|
|
||||||
@@ -288,32 +304,35 @@ function Analyzer() {
|
|||||||
|
|
||||||
const calculateMaterialFlowMetricsForAsset = (assetId: string) => {
|
const calculateMaterialFlowMetricsForAsset = (assetId: string) => {
|
||||||
const materialsOnAsset = getMaterialsByModel(assetId).length;
|
const materialsOnAsset = getMaterialsByModel(assetId).length;
|
||||||
const materialHistory = materialHistoryRef.current.filter((m) => m.material.current?.modelUuid === assetId || m.material.previous?.modelUuid === assetId);
|
|
||||||
|
// Use removals as the history of processed items
|
||||||
|
// This fixes the issue where items per hour was 0 because materialHistoryRef was empty
|
||||||
|
const removals = materialRemovalsRef.current[assetId] || [];
|
||||||
|
|
||||||
// Calculate flow metrics
|
// Calculate flow metrics
|
||||||
const wip = materialsOnAsset;
|
const wip = materialsOnAsset;
|
||||||
const throughput = (materialHistory.length / (Date.now() - new Date(startTimeRef.current).getTime())) * 1000;
|
|
||||||
|
|
||||||
// Calculate lead times
|
// Calculate throughput (items per second)
|
||||||
const leadTimes = materialHistory
|
// Ensure we don't divide by zero
|
||||||
.map((m) => {
|
const durationMs = Date.now() - new Date(startTimeRef.current).getTime();
|
||||||
if (m.removedAt && m.material.startTime) {
|
|
||||||
return new Date(m.removedAt).getTime() - m.material.startTime;
|
// Normalize by simulation speed to get "simulation time" throughput
|
||||||
}
|
const currentSpeed = Math.max(1, speed);
|
||||||
return 0;
|
const throughput = durationMs > 1000 ? ((removals.length / durationMs) * 1000) / currentSpeed : 0;
|
||||||
})
|
|
||||||
.filter((t) => t > 0);
|
// Calculate lead times (processing times on this asset)
|
||||||
|
const leadTimes = removals.map((m) => m.processingTime || 0).filter((t) => t > 0);
|
||||||
|
|
||||||
const avgLeadTime = leadTimes.length > 0 ? leadTimes.reduce((sum, t) => sum + t, 0) / leadTimes.length : 0;
|
const avgLeadTime = leadTimes.length > 0 ? leadTimes.reduce((sum, t) => sum + t, 0) / leadTimes.length : 0;
|
||||||
|
|
||||||
// Calculate velocity
|
// Calculate velocity (turnover)
|
||||||
const velocity = materialHistory.length / Math.max(1, wip);
|
const velocity = removals.length / Math.max(1, wip);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wip,
|
wip,
|
||||||
throughput,
|
throughput,
|
||||||
avgLeadTime,
|
avgLeadTime,
|
||||||
totalMaterialsProcessed: materialHistory.length,
|
totalMaterialsProcessed: removals.length,
|
||||||
currentMaterials: materialsOnAsset,
|
currentMaterials: materialsOnAsset,
|
||||||
avgCycleTime: avgLeadTime / 1000,
|
avgCycleTime: avgLeadTime / 1000,
|
||||||
materialVelocity: velocity,
|
materialVelocity: velocity,
|
||||||
@@ -544,9 +563,29 @@ function Analyzer() {
|
|||||||
/**
|
/**
|
||||||
* Track asset state change
|
* Track asset state change
|
||||||
*/
|
*/
|
||||||
const trackStateChange = useCallback((assetId: string, fromState: string, toState: string) => {
|
const trackStateChange = useCallback((assetId: string, fromState: string, toState: string, context?: { actionName?: string }) => {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
// Increment error count if entering error state
|
||||||
|
if (toState === "error") {
|
||||||
|
if (!errorCountsRef.current[assetId]) {
|
||||||
|
errorCountsRef.current[assetId] = 0;
|
||||||
|
}
|
||||||
|
errorCountsRef.current[assetId]++;
|
||||||
|
|
||||||
|
// Granular error tracking based on action type
|
||||||
|
if (context?.actionName) {
|
||||||
|
const actionName = context.actionName.toLowerCase();
|
||||||
|
if (actionName.includes("pick")) {
|
||||||
|
if (!errorCountsRef.current[`${assetId}_pick`]) errorCountsRef.current[`${assetId}_pick`] = 0;
|
||||||
|
errorCountsRef.current[`${assetId}_pick`]++;
|
||||||
|
} else if (actionName.includes("place")) {
|
||||||
|
if (!errorCountsRef.current[`${assetId}_place`]) errorCountsRef.current[`${assetId}_place`] = 0;
|
||||||
|
errorCountsRef.current[`${assetId}_place`]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!assetStateChangesRef.current[assetId]) {
|
if (!assetStateChangesRef.current[assetId]) {
|
||||||
assetStateChangesRef.current[assetId] = [];
|
assetStateChangesRef.current[assetId] = [];
|
||||||
}
|
}
|
||||||
@@ -670,32 +709,37 @@ function Analyzer() {
|
|||||||
/**
|
/**
|
||||||
* Update throughput snapshot for an asset
|
* Update throughput snapshot for an asset
|
||||||
*/
|
*/
|
||||||
const updateThroughputSnapshot = useCallback((assetId: string) => {
|
const updateThroughputSnapshot = useCallback(
|
||||||
const timestamp = Date.now();
|
(assetId: string) => {
|
||||||
const timeWindow = 60; // 60 seconds
|
const timestamp = Date.now();
|
||||||
|
const timeWindow = 60; // 60 seconds
|
||||||
|
|
||||||
if (!throughputSnapshotsRef.current[assetId]) {
|
if (!throughputSnapshotsRef.current[assetId]) {
|
||||||
throughputSnapshotsRef.current[assetId] = [];
|
throughputSnapshotsRef.current[assetId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count items processed in the last time window
|
// Count items processed in the last time window
|
||||||
const removals = materialRemovalsRef.current[assetId] || [];
|
const removals = materialRemovalsRef.current[assetId] || [];
|
||||||
const recentRemovals = removals.filter((r) => timestamp - r.timestamp <= timeWindow * 1000);
|
const recentRemovals = removals.filter((r) => timestamp - r.timestamp <= timeWindow * 1000);
|
||||||
const itemsProcessed = recentRemovals.length;
|
const itemsProcessed = recentRemovals.length;
|
||||||
const rate = (itemsProcessed / timeWindow) * 3600; // items per hour
|
// Normalize by speed
|
||||||
|
const currentSpeed = Math.max(1, speed);
|
||||||
|
const rate = ((itemsProcessed / timeWindow) * 3600) / currentSpeed; // items per hour
|
||||||
|
|
||||||
throughputSnapshotsRef.current[assetId].push({
|
throughputSnapshotsRef.current[assetId].push({
|
||||||
timestamp,
|
timestamp,
|
||||||
itemsProcessed,
|
itemsProcessed,
|
||||||
timeWindow,
|
timeWindow,
|
||||||
rate,
|
rate,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep only last 100 snapshots
|
// Keep only last 100 snapshots
|
||||||
if (throughputSnapshotsRef.current[assetId].length > 100) {
|
if (throughputSnapshotsRef.current[assetId].length > 100) {
|
||||||
throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100);
|
throughputSnapshotsRef.current[assetId] = throughputSnapshotsRef.current[assetId].slice(-100);
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[speed]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update performance snapshot for an asset
|
* Update performance snapshot for an asset
|
||||||
@@ -948,7 +992,7 @@ function Analyzer() {
|
|||||||
historicalData: historicalDataRef.current[conveyor.modelUuid] || [],
|
historicalData: historicalDataRef.current[conveyor.modelUuid] || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[materials, analysis]
|
[materials, analysis, speed]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -963,7 +1007,7 @@ function Analyzer() {
|
|||||||
const materialFlow = calculateMaterialFlowMetricsForAsset(vehicle.modelUuid);
|
const materialFlow = calculateMaterialFlowMetricsForAsset(vehicle.modelUuid);
|
||||||
const tripsCompleted = completedActionsRef.current[vehicle.modelUuid] || 0;
|
const tripsCompleted = completedActionsRef.current[vehicle.modelUuid] || 0;
|
||||||
const totalLoadsDelivered = completedActionsRef.current[`${vehicle.modelUuid}_loads`] || 0;
|
const totalLoadsDelivered = completedActionsRef.current[`${vehicle.modelUuid}_loads`] || 0;
|
||||||
const defects = errorCountsRef.current[`${vehicle.modelUuid}_defects`] || 0;
|
const defects = errorCount; // Use main error count directly
|
||||||
const qualityMetrics = calculateQualityMetrics(vehicle.modelUuid, totalLoadsDelivered, defects);
|
const qualityMetrics = calculateQualityMetrics(vehicle.modelUuid, totalLoadsDelivered, defects);
|
||||||
|
|
||||||
// Performance calculations
|
// Performance calculations
|
||||||
@@ -1079,7 +1123,7 @@ function Analyzer() {
|
|||||||
maintenanceCost: costMetrics.maintenanceCost,
|
maintenanceCost: costMetrics.maintenanceCost,
|
||||||
energyCost: energyMetrics.energyCost,
|
energyCost: energyMetrics.energyCost,
|
||||||
totalCost: costMetrics.totalCost,
|
totalCost: costMetrics.totalCost,
|
||||||
costPerMile: actualDistance > 0 ? costMetrics.totalCost / actualDistance : 0,
|
costPerMile: actualDistance > 0 ? costMetrics.totalCost / (actualDistance / 1609.34) : 0, // Convert meters to miles
|
||||||
costPerTrip: tripsCompleted > 0 ? costMetrics.totalCost / tripsCompleted : 0,
|
costPerTrip: tripsCompleted > 0 ? costMetrics.totalCost / tripsCompleted : 0,
|
||||||
costPerLoad: totalLoadsDelivered > 0 ? costMetrics.totalCost / totalLoadsDelivered : 0,
|
costPerLoad: totalLoadsDelivered > 0 ? costMetrics.totalCost / totalLoadsDelivered : 0,
|
||||||
roi: costMetrics.roi,
|
roi: costMetrics.roi,
|
||||||
@@ -1099,7 +1143,7 @@ function Analyzer() {
|
|||||||
historicalData: historicalDataRef.current[vehicle.modelUuid] || [],
|
historicalData: historicalDataRef.current[vehicle.modelUuid] || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[materials, analysis]
|
[materials, analysis, speed]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1114,7 +1158,7 @@ function Analyzer() {
|
|||||||
const materialFlow = calculateMaterialFlowMetricsForAsset(armBot.modelUuid);
|
const materialFlow = calculateMaterialFlowMetricsForAsset(armBot.modelUuid);
|
||||||
const cyclesCompleted = completedActionsRef.current[armBot.modelUuid] || 0;
|
const cyclesCompleted = completedActionsRef.current[armBot.modelUuid] || 0;
|
||||||
const pickAndPlaceCount = completedActionsRef.current[`${armBot.modelUuid}_pickplace`] || cyclesCompleted;
|
const pickAndPlaceCount = completedActionsRef.current[`${armBot.modelUuid}_pickplace`] || cyclesCompleted;
|
||||||
const defects = errorCountsRef.current[`${armBot.modelUuid}_defects`] || 0;
|
const defects = errorCount; // Use main error count directly
|
||||||
|
|
||||||
const qualityMetrics = calculateQualityMetrics(armBot.modelUuid, pickAndPlaceCount, defects);
|
const qualityMetrics = calculateQualityMetrics(armBot.modelUuid, pickAndPlaceCount, defects);
|
||||||
|
|
||||||
@@ -1131,9 +1175,23 @@ function Analyzer() {
|
|||||||
const energyMetrics = calculateEnergyMetrics("roboticArm", armBot.activeTime || 0);
|
const energyMetrics = calculateEnergyMetrics("roboticArm", armBot.activeTime || 0);
|
||||||
|
|
||||||
// Calculate success rates
|
// Calculate success rates
|
||||||
const pickAttempts = pickAndPlaceCount + (errorCountsRef.current[armBot.modelUuid] || 0);
|
// Calculate success rates
|
||||||
const pickSuccessRate = pickAttempts > 0 ? (pickAndPlaceCount / pickAttempts) * 100 : 100;
|
const pickSuccessCount = completedActionsRef.current[`${armBot.modelUuid}_pick`] || pickAndPlaceCount / 2;
|
||||||
const placeAccuracy = pickSuccessRate * 0.98; // Assuming 98% of successful picks are placed correctly
|
const placeSuccessCount = completedActionsRef.current[`${armBot.modelUuid}_place`] || pickAndPlaceCount / 2;
|
||||||
|
|
||||||
|
const pickErrors = errorCountsRef.current[`${armBot.modelUuid}_pick`] || 0;
|
||||||
|
const placeErrors = errorCountsRef.current[`${armBot.modelUuid}_place`] || 0;
|
||||||
|
|
||||||
|
// If granular errors are 0 but main error count > 0, distribute them (fallback)
|
||||||
|
const remainingErrors = Math.max(0, errorCount - pickErrors - placeErrors);
|
||||||
|
const effectivePickErrors = pickErrors + (remainingErrors > 0 ? Math.ceil(remainingErrors / 2) : 0);
|
||||||
|
const effectivePlaceErrors = placeErrors + (remainingErrors > 0 ? Math.floor(remainingErrors / 2) : 0);
|
||||||
|
|
||||||
|
const pickAttempts = pickSuccessCount + effectivePickErrors;
|
||||||
|
const placeAttempts = placeSuccessCount + effectivePlaceErrors;
|
||||||
|
|
||||||
|
const pickSuccessRate = pickAttempts > 0 ? (pickSuccessCount / pickAttempts) * 100 : 100;
|
||||||
|
const placeAccuracy = placeAttempts > 0 ? (placeSuccessCount / placeAttempts) * 100 : 100;
|
||||||
|
|
||||||
// Update historical data
|
// Update historical data
|
||||||
|
|
||||||
@@ -1224,7 +1282,7 @@ function Analyzer() {
|
|||||||
historicalData: historicalDataRef.current[armBot.modelUuid] || [],
|
historicalData: historicalDataRef.current[armBot.modelUuid] || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[materials, analysis]
|
[materials, analysis, speed]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1349,7 +1407,7 @@ function Analyzer() {
|
|||||||
historicalData: historicalDataRef.current[machine.modelUuid] || [],
|
historicalData: historicalDataRef.current[machine.modelUuid] || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[materials, analysis]
|
[materials, analysis, speed]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1486,7 +1544,7 @@ function Analyzer() {
|
|||||||
historicalData: historicalDataRef.current[storage.modelUuid] || [],
|
historicalData: historicalDataRef.current[storage.modelUuid] || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[analysis]
|
[analysis, speed]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1517,13 +1575,15 @@ function Analyzer() {
|
|||||||
|
|
||||||
const totalActionTime = workerTime + manufacturerTime + operatorTime + assemblerTime;
|
const totalActionTime = workerTime + manufacturerTime + operatorTime + assemblerTime;
|
||||||
|
|
||||||
const workloadDistribution = [
|
const workloadDistributionData = [
|
||||||
{ actionType: "worker", count: workerActions, totalTime: workerTime, percentage: totalActionTime > 0 ? (workerTime / totalActionTime) * 100 : 0 },
|
{ actionType: "Worker", count: workerActions, totalTime: workerTime, percentage: totalActionTime > 0 ? (workerTime / totalActionTime) * 100 : 0 },
|
||||||
{ actionType: "manufacturer", count: manufacturerActions, totalTime: manufacturerTime, percentage: totalActionTime > 0 ? (manufacturerTime / totalActionTime) * 100 : 0 },
|
{ actionType: "Manufacturer", count: manufacturerActions, totalTime: manufacturerTime, percentage: totalActionTime > 0 ? (manufacturerTime / totalActionTime) * 100 : 0 },
|
||||||
{ actionType: "operator", count: operatorActions, totalTime: operatorTime, percentage: totalActionTime > 0 ? (operatorTime / totalActionTime) * 100 : 0 },
|
{ actionType: "Operator", count: operatorActions, totalTime: operatorTime, percentage: totalActionTime > 0 ? (operatorTime / totalActionTime) * 100 : 0 },
|
||||||
{ actionType: "assembler", count: assemblerActions, totalTime: assemblerTime, percentage: totalActionTime > 0 ? (assemblerTime / totalActionTime) * 100 : 0 },
|
{ actionType: "Assembler", count: assemblerActions, totalTime: assemblerTime, percentage: totalActionTime > 0 ? (assemblerTime / totalActionTime) * 100 : 0 },
|
||||||
].filter((w) => w.count > 0);
|
].filter((w) => w.count > 0);
|
||||||
|
|
||||||
|
const workloadDistribution = workloadDistributionData.map((d) => `${Math.round(d.percentage)}%`).join(" | ");
|
||||||
|
|
||||||
// Performance calculations
|
// Performance calculations
|
||||||
const idealActionsPerHour = 60; // 60 actions per hour ideal
|
const idealActionsPerHour = 60; // 60 actions per hour ideal
|
||||||
const actualActionsPerHour = timeMetrics.totalTime > 0 ? (actionsCompleted / timeMetrics.totalTime) * 3600 : 0;
|
const actualActionsPerHour = timeMetrics.totalTime > 0 ? (actionsCompleted / timeMetrics.totalTime) * 3600 : 0;
|
||||||
@@ -1586,7 +1646,8 @@ function Analyzer() {
|
|||||||
loadEfficiency: loadUtilization,
|
loadEfficiency: loadUtilization,
|
||||||
},
|
},
|
||||||
|
|
||||||
workloadDistribution,
|
workloadDistribution: workloadDistributionData,
|
||||||
|
workloadSummary: workloadDistribution === "" ? "0%" : workloadDistribution,
|
||||||
|
|
||||||
efficiency: {
|
efficiency: {
|
||||||
overallEffectiveness: calculateOEE(timeMetrics.uptime, performanceRate, qualityMetrics.firstPassYield),
|
overallEffectiveness: calculateOEE(timeMetrics.uptime, performanceRate, qualityMetrics.firstPassYield),
|
||||||
@@ -1624,7 +1685,7 @@ function Analyzer() {
|
|||||||
historicalData: historicalDataRef.current[human.modelUuid] || [],
|
historicalData: historicalDataRef.current[human.modelUuid] || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[analysis]
|
[analysis, speed]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1774,7 +1835,7 @@ function Analyzer() {
|
|||||||
historicalData: historicalDataRef.current[crane.modelUuid] || [],
|
historicalData: historicalDataRef.current[crane.modelUuid] || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[analysis]
|
[analysis, speed]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -2255,7 +2316,7 @@ function Analyzer() {
|
|||||||
const allAssets = [
|
const allAssets = [
|
||||||
...conveyors.map((c) => ({ id: c.modelUuid, state: c.state, isActive: !c.isPaused, type: "conveyor" as const })),
|
...conveyors.map((c) => ({ id: c.modelUuid, state: c.state, isActive: !c.isPaused, type: "conveyor" as const })),
|
||||||
...machines.map((m) => ({ id: m.modelUuid, state: m.state, isActive: m.isActive, type: "machine" as const })),
|
...machines.map((m) => ({ id: m.modelUuid, state: m.state, isActive: m.isActive, type: "machine" as const })),
|
||||||
...armBots.map((a) => ({ id: a.modelUuid, state: a.state, isActive: a.isActive, type: "roboticArm" as const })),
|
...armBots.map((a) => ({ id: a.modelUuid, state: a.state, isActive: a.isActive, type: "roboticArm" as const, currentAction: a.currentAction })),
|
||||||
...vehicles.map((v) => ({ id: v.modelUuid, state: v.state, isActive: v.isActive, type: "vehicle" as const })),
|
...vehicles.map((v) => ({ id: v.modelUuid, state: v.state, isActive: v.isActive, type: "vehicle" as const })),
|
||||||
...humans.map((h) => ({ id: h.modelUuid, state: h.state, isActive: h.isActive, type: "human" as const })),
|
...humans.map((h) => ({ id: h.modelUuid, state: h.state, isActive: h.isActive, type: "human" as const })),
|
||||||
...cranes.map((c) => ({ id: c.modelUuid, state: c.state, isActive: c.isActive, type: "crane" as const })),
|
...cranes.map((c) => ({ id: c.modelUuid, state: c.state, isActive: c.isActive, type: "crane" as const })),
|
||||||
@@ -2269,7 +2330,7 @@ function Analyzer() {
|
|||||||
if (previousState) {
|
if (previousState) {
|
||||||
// Check for state change
|
// Check for state change
|
||||||
if (previousState.state !== asset.state) {
|
if (previousState.state !== asset.state) {
|
||||||
trackStateChange(asset.id, previousState.state, asset.state);
|
trackStateChange(asset.id, previousState.state, asset.state, asset.type === "roboticArm" ? { actionName: (asset as any).currentAction?.actionName } : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for material count change (potential bottleneck)
|
// Check for material count change (potential bottleneck)
|
||||||
@@ -2285,6 +2346,98 @@ function Analyzer() {
|
|||||||
});
|
});
|
||||||
}, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, getMaterialsByModel, trackStateChange, trackBottleneckEvent, updatePreviousAssetState]);
|
}, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, getMaterialsByModel, trackStateChange, trackBottleneckEvent, updatePreviousAssetState]);
|
||||||
|
|
||||||
|
// Monitor ArmBot action changes to track cycles
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying) return;
|
||||||
|
|
||||||
|
armBots.forEach((armBot) => {
|
||||||
|
const previousActionUuid = previousArmBotActionsRef.current[armBot.modelUuid];
|
||||||
|
const currentActionUuid = armBot.currentAction?.actionUuid;
|
||||||
|
|
||||||
|
// Check if action completed (transition from an action to no action or different action)
|
||||||
|
if (previousActionUuid && previousActionUuid !== currentActionUuid) {
|
||||||
|
// Action completed
|
||||||
|
if (!completedActionsRef.current[armBot.modelUuid]) {
|
||||||
|
completedActionsRef.current[armBot.modelUuid] = 0;
|
||||||
|
}
|
||||||
|
completedActionsRef.current[armBot.modelUuid]++;
|
||||||
|
|
||||||
|
// Also update pick and place count which is used in analysis
|
||||||
|
if (!completedActionsRef.current[`${armBot.modelUuid}_pickplace`]) {
|
||||||
|
completedActionsRef.current[`${armBot.modelUuid}_pickplace`] = 0;
|
||||||
|
}
|
||||||
|
completedActionsRef.current[`${armBot.modelUuid}_pickplace`]++;
|
||||||
|
|
||||||
|
// Granular pick/place tracking
|
||||||
|
if (previousActionUuid) {
|
||||||
|
// We need to look up what action this UUID corresponded to
|
||||||
|
// Since we don't store the action map history, we check the current config
|
||||||
|
// This assumes configuration hasn't changed, which is true for runtime
|
||||||
|
const action = armBot.point.actions.find((a) => a.actionUuid === previousActionUuid);
|
||||||
|
if (action) {
|
||||||
|
const actionName = action.actionName.toLowerCase();
|
||||||
|
if (actionName.includes("pick")) {
|
||||||
|
if (!completedActionsRef.current[`${armBot.modelUuid}_pick`]) completedActionsRef.current[`${armBot.modelUuid}_pick`] = 0;
|
||||||
|
completedActionsRef.current[`${armBot.modelUuid}_pick`]++;
|
||||||
|
} else if (actionName.includes("place")) {
|
||||||
|
if (!completedActionsRef.current[`${armBot.modelUuid}_place`]) completedActionsRef.current[`${armBot.modelUuid}_place`] = 0;
|
||||||
|
completedActionsRef.current[`${armBot.modelUuid}_place`]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousArmBotActionsRef.current[armBot.modelUuid] = currentActionUuid;
|
||||||
|
});
|
||||||
|
}, [armBots, isPlaying]);
|
||||||
|
|
||||||
|
// Monitor Human action changes from EventManager
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying || !humanEventManagerRef.current) return;
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (!humanEventManagerRef.current) return;
|
||||||
|
|
||||||
|
humanEventManagerRef.current.humanStates.forEach((humanState) => {
|
||||||
|
const humanId = humanState.humanId;
|
||||||
|
|
||||||
|
// Initialize tracking for this human if needed
|
||||||
|
if (!previousHumanCountsRef.current[humanId]) {
|
||||||
|
previousHumanCountsRef.current[humanId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
humanState.actionQueue.forEach((action) => {
|
||||||
|
let lastCount = previousHumanCountsRef.current[humanId][action.actionUuid] || 0;
|
||||||
|
const currentCount = action.count || 0;
|
||||||
|
|
||||||
|
// Handle reset case (new action instance with same UUID)
|
||||||
|
if (currentCount < lastCount) {
|
||||||
|
lastCount = 0;
|
||||||
|
previousHumanCountsRef.current[humanId][action.actionUuid] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = currentCount - lastCount;
|
||||||
|
|
||||||
|
if (delta > 0) {
|
||||||
|
// Update total completions for this human
|
||||||
|
if (!completedActionsRef.current[humanId]) completedActionsRef.current[humanId] = 0;
|
||||||
|
completedActionsRef.current[humanId] += delta;
|
||||||
|
|
||||||
|
// Update granular action type completions (e.g., worker, manufacturer)
|
||||||
|
const typeKey = `${humanId}_${action.actionType}`;
|
||||||
|
if (!completedActionsRef.current[typeKey]) completedActionsRef.current[typeKey] = 0;
|
||||||
|
completedActionsRef.current[typeKey] += delta;
|
||||||
|
|
||||||
|
// Update the last known count
|
||||||
|
previousHumanCountsRef.current[humanId][action.actionUuid] = currentCount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [isPlaying, humanEventManagerRef]);
|
||||||
|
|
||||||
// Periodic WIP and throughput snapshots
|
// Periodic WIP and throughput snapshots
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPlaying) return;
|
if (!isPlaying) return;
|
||||||
@@ -2308,6 +2461,32 @@ function Analyzer() {
|
|||||||
return () => clearInterval(snapshotInterval);
|
return () => clearInterval(snapshotInterval);
|
||||||
}, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, updateWIPSnapshot]);
|
}, [conveyors, machines, armBots, vehicles, humans, cranes, storageUnits, isPlaying, updateWIPSnapshot]);
|
||||||
|
|
||||||
|
// Monitor Vehicle phase changes to track completed trips
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying) return;
|
||||||
|
|
||||||
|
vehicles.forEach((vehicle) => {
|
||||||
|
const previousPhase = previousVehiclePhasesRef.current[vehicle.modelUuid];
|
||||||
|
const currentPhase = vehicle.currentPhase;
|
||||||
|
|
||||||
|
// Check for transition from 'drop-pickup' to 'picking' (Trip completed)
|
||||||
|
if (previousPhase === "drop-pickup" && currentPhase === "picking") {
|
||||||
|
if (!completedActionsRef.current[vehicle.modelUuid]) {
|
||||||
|
completedActionsRef.current[vehicle.modelUuid] = 0;
|
||||||
|
}
|
||||||
|
completedActionsRef.current[vehicle.modelUuid]++;
|
||||||
|
|
||||||
|
// Track loads delivered (assuming 1 load per trip for now, or use vehicle.currentLoad if available/reliable at this point)
|
||||||
|
if (!completedActionsRef.current[`${vehicle.modelUuid}_loads`]) {
|
||||||
|
completedActionsRef.current[`${vehicle.modelUuid}_loads`] = 0;
|
||||||
|
}
|
||||||
|
completedActionsRef.current[`${vehicle.modelUuid}_loads`] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousVehiclePhasesRef.current[vehicle.modelUuid] = currentPhase;
|
||||||
|
});
|
||||||
|
}, [vehicles, isPlaying]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface ManufacturerAnimatorProps {
|
|||||||
function ManufacturerAnimator({ path, handleCallBack, human, reset }: Readonly<ManufacturerAnimatorProps>) {
|
function ManufacturerAnimator({ path, handleCallBack, human, reset }: Readonly<ManufacturerAnimatorProps>) {
|
||||||
const { humanStore, assetStore, productStore } = useSceneContext();
|
const { humanStore, assetStore, productStore } = useSceneContext();
|
||||||
const { getActionByUuid, selectedProduct } = productStore();
|
const { getActionByUuid, selectedProduct } = productStore();
|
||||||
const { getHumanById } = humanStore();
|
const { getHumanById, incrementDistanceTraveled } = humanStore();
|
||||||
const { setCurrentAnimation } = assetStore();
|
const { setCurrentAnimation } = assetStore();
|
||||||
const { isPaused } = usePauseButtonStore();
|
const { isPaused } = usePauseButtonStore();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
@@ -109,6 +109,7 @@ function ManufacturerAnimator({ path, handleCallBack, human, reset }: Readonly<M
|
|||||||
|
|
||||||
if (isAligned) {
|
if (isAligned) {
|
||||||
progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance);
|
progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance);
|
||||||
|
incrementDistanceTraveled(human.modelUuid, delta * (speed * human.speed));
|
||||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||||
const position = start.clone().lerp(end, t);
|
const position = start.clone().lerp(end, t);
|
||||||
object.position.copy(position);
|
object.position.copy(position);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface WorkerAnimatorProps {
|
|||||||
function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly<WorkerAnimatorProps>) {
|
function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly<WorkerAnimatorProps>) {
|
||||||
const { humanStore, assetStore, productStore } = useSceneContext();
|
const { humanStore, assetStore, productStore } = useSceneContext();
|
||||||
const { getActionByUuid, selectedProduct } = productStore();
|
const { getActionByUuid, selectedProduct } = productStore();
|
||||||
const { getHumanById } = humanStore();
|
const { getHumanById, incrementDistanceTraveled } = humanStore();
|
||||||
const { setCurrentAnimation } = assetStore();
|
const { setCurrentAnimation } = assetStore();
|
||||||
const { isPaused } = usePauseButtonStore();
|
const { isPaused } = usePauseButtonStore();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
@@ -117,6 +117,7 @@ function OperatorAnimator({ path, handleCallBack, human, reset }: Readonly<Worke
|
|||||||
|
|
||||||
if (isAligned) {
|
if (isAligned) {
|
||||||
progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance);
|
progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance);
|
||||||
|
incrementDistanceTraveled(human.modelUuid, delta * (speed * human.speed));
|
||||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||||
const position = start.clone().lerp(end, t);
|
const position = start.clone().lerp(end, t);
|
||||||
object.position.copy(position);
|
object.position.copy(position);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface WorkerAnimatorProps {
|
|||||||
function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly<WorkerAnimatorProps>) {
|
function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly<WorkerAnimatorProps>) {
|
||||||
const { humanStore, assetStore, productStore } = useSceneContext();
|
const { humanStore, assetStore, productStore } = useSceneContext();
|
||||||
const { getActionByUuid, selectedProduct } = productStore();
|
const { getActionByUuid, selectedProduct } = productStore();
|
||||||
const { getHumanById } = humanStore();
|
const { getHumanById, incrementDistanceTraveled } = humanStore();
|
||||||
const { setCurrentAnimation } = assetStore();
|
const { setCurrentAnimation } = assetStore();
|
||||||
const { isPaused } = usePauseButtonStore();
|
const { isPaused } = usePauseButtonStore();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
@@ -119,6 +119,7 @@ function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProc
|
|||||||
|
|
||||||
if (isAligned) {
|
if (isAligned) {
|
||||||
progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance);
|
progressRef.current = Math.min(progressRef.current + delta * (speed * human.speed), totalDistance);
|
||||||
|
incrementDistanceTraveled(human.modelUuid, delta * (speed * human.speed));
|
||||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||||
const position = start.clone().lerp(end, t);
|
const position = start.clone().lerp(end, t);
|
||||||
object.position.copy(position);
|
object.position.copy(position);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface VehicleAnimatorProps {
|
|||||||
|
|
||||||
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset, startUnloadingProcess }: Readonly<VehicleAnimatorProps>) {
|
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset, startUnloadingProcess }: Readonly<VehicleAnimatorProps>) {
|
||||||
const { vehicleStore } = useSceneContext();
|
const { vehicleStore } = useSceneContext();
|
||||||
const { getVehicleById } = vehicleStore();
|
const { getVehicleById, incrementDistanceTraveled } = vehicleStore();
|
||||||
const { isPaused } = usePauseButtonStore();
|
const { isPaused } = usePauseButtonStore();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const { speed } = useAnimationPlaySpeed();
|
const { speed } = useAnimationPlaySpeed();
|
||||||
@@ -126,6 +126,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
|||||||
|
|
||||||
if (isAligned) {
|
if (isAligned) {
|
||||||
progressRef.current += delta * (speed * agvDetail.speed);
|
progressRef.current += delta * (speed * agvDetail.speed);
|
||||||
|
incrementDistanceTraveled(agvUuid, delta * (speed * agvDetail.speed));
|
||||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||||
const position = start.clone().lerp(end, t);
|
const position = start.clone().lerp(end, t);
|
||||||
object.position.copy(position);
|
object.position.copy(position);
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ interface VehiclesStore {
|
|||||||
|
|
||||||
addVehicle: (productUuid: string, event: VehicleEventSchema) => void;
|
addVehicle: (productUuid: string, event: VehicleEventSchema) => void;
|
||||||
removeVehicle: (modelUuid: string) => void;
|
removeVehicle: (modelUuid: string) => void;
|
||||||
updateVehicle: (
|
updateVehicle: (modelUuid: string, updates: Partial<Omit<VehicleStatus, "modelUuid" | "productUuid">>) => void;
|
||||||
modelUuid: string,
|
|
||||||
updates: Partial<Omit<VehicleStatus, "modelUuid" | "productUuid">>
|
|
||||||
) => void;
|
|
||||||
addPathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], point: VehicleAction["paths"]["initPickup"][number]) => VehicleAction["paths"];
|
addPathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], point: VehicleAction["paths"]["initPickup"][number]) => VehicleAction["paths"];
|
||||||
updatePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string, updates: Partial<VehicleAction["paths"]["initPickup"][number]>) => VehicleAction["paths"];
|
updatePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string, updates: Partial<VehicleAction["paths"]["initPickup"][number]>) => VehicleAction["paths"];
|
||||||
deletePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string) => VehicleAction["paths"];
|
deletePathPoint: (modelUuid: string, pathKey: keyof VehicleAction["paths"], pointId: string) => VehicleAction["paths"];
|
||||||
@@ -22,17 +19,15 @@ interface VehiclesStore {
|
|||||||
incrementVehicleLoad: (modelUuid: string, incrementBy: number) => void;
|
incrementVehicleLoad: (modelUuid: string, incrementBy: number) => void;
|
||||||
decrementVehicleLoad: (modelUuid: string, decrementBy: number) => void;
|
decrementVehicleLoad: (modelUuid: string, decrementBy: number) => void;
|
||||||
setVehicleLoad: (modelUuid: string, load: number) => void;
|
setVehicleLoad: (modelUuid: string, load: number) => void;
|
||||||
setVehicleState: (
|
setVehicleState: (modelUuid: string, newState: VehicleStatus["state"]) => void;
|
||||||
modelUuid: string,
|
|
||||||
newState: VehicleStatus["state"]
|
|
||||||
) => void;
|
|
||||||
addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void;
|
addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void;
|
||||||
setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string; }[]) => void;
|
setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void;
|
||||||
removeLastMaterial: (modelUuid: string) => { materialId: string; materialType: string; } | undefined;
|
removeLastMaterial: (modelUuid: string) => { materialId: string; materialType: string } | undefined;
|
||||||
getLastMaterial: (modelUuid: string) => { materialId: string; materialType: string; } | undefined;
|
getLastMaterial: (modelUuid: string) => { materialId: string; materialType: string } | undefined;
|
||||||
clearCurrentMaterials: (modelUuid: string) => void;
|
clearCurrentMaterials: (modelUuid: string) => void;
|
||||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||||
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
||||||
|
incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void;
|
||||||
resetTime: (modelUuid: string) => void;
|
resetTime: (modelUuid: string) => void;
|
||||||
|
|
||||||
getVehicleById: (modelUuid: string) => VehicleStatus | undefined;
|
getVehicleById: (modelUuid: string) => VehicleStatus | undefined;
|
||||||
@@ -53,7 +48,7 @@ export const createVehicleStore = () => {
|
|||||||
state.vehicles.push({
|
state.vehicles.push({
|
||||||
...event,
|
...event,
|
||||||
productUuid,
|
productUuid,
|
||||||
currentPhase: 'stationed',
|
currentPhase: "stationed",
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isPicking: false,
|
isPicking: false,
|
||||||
idleTime: 0,
|
idleTime: 0,
|
||||||
@@ -61,7 +56,7 @@ export const createVehicleStore = () => {
|
|||||||
currentLoad: 0,
|
currentLoad: 0,
|
||||||
currentMaterials: [],
|
currentMaterials: [],
|
||||||
distanceTraveled: 0,
|
distanceTraveled: 0,
|
||||||
state: 'idle'
|
state: "idle",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -69,9 +64,7 @@ export const createVehicleStore = () => {
|
|||||||
|
|
||||||
removeVehicle: (modelUuid) => {
|
removeVehicle: (modelUuid) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.vehicles = state.vehicles.filter(
|
state.vehicles = state.vehicles.filter((v) => v.modelUuid !== modelUuid);
|
||||||
(v) => v.modelUuid !== modelUuid
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -87,7 +80,7 @@ export const createVehicleStore = () => {
|
|||||||
addPathPoint: (modelUuid, pathKey, point) => {
|
addPathPoint: (modelUuid, pathKey, point) => {
|
||||||
let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] };
|
let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] };
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
if (vehicle) {
|
if (vehicle) {
|
||||||
const path = vehicle.point.action.paths[pathKey];
|
const path = vehicle.point.action.paths[pathKey];
|
||||||
path.push(point);
|
path.push(point);
|
||||||
@@ -100,10 +93,10 @@ export const createVehicleStore = () => {
|
|||||||
updatePathPoint: (modelUuid, pathKey, pointId, updates) => {
|
updatePathPoint: (modelUuid, pathKey, pointId, updates) => {
|
||||||
let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] };
|
let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] };
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
if (vehicle) {
|
if (vehicle) {
|
||||||
const path = vehicle.point.action.paths[pathKey];
|
const path = vehicle.point.action.paths[pathKey];
|
||||||
const index = path.findIndex(p => p.pointId === pointId);
|
const index = path.findIndex((p) => p.pointId === pointId);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
Object.assign(path[index], updates);
|
Object.assign(path[index], updates);
|
||||||
updatedPaths = vehicle.point.action.paths;
|
updatedPaths = vehicle.point.action.paths;
|
||||||
@@ -116,10 +109,10 @@ export const createVehicleStore = () => {
|
|||||||
deletePathPoint: (modelUuid, pathKey, pointId) => {
|
deletePathPoint: (modelUuid, pathKey, pointId) => {
|
||||||
let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] };
|
let updatedPaths: VehicleAction["paths"] = { initPickup: [], pickupDrop: [], dropPickup: [] };
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
if (vehicle) {
|
if (vehicle) {
|
||||||
const path = vehicle.point.action.paths[pathKey];
|
const path = vehicle.point.action.paths[pathKey];
|
||||||
vehicle.point.action.paths[pathKey] = path.filter(p => p.pointId !== pointId);
|
vehicle.point.action.paths[pathKey] = path.filter((p) => p.pointId !== pointId);
|
||||||
updatedPaths = vehicle.point.action.paths;
|
updatedPaths = vehicle.point.action.paths;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -134,7 +127,7 @@ export const createVehicleStore = () => {
|
|||||||
|
|
||||||
setCurrentPhase: (modelUuid, phase) => {
|
setCurrentPhase: (modelUuid, phase) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
if (vehicle) {
|
if (vehicle) {
|
||||||
vehicle.currentPhase = phase;
|
vehicle.currentPhase = phase;
|
||||||
}
|
}
|
||||||
@@ -223,7 +216,7 @@ export const createVehicleStore = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
removeLastMaterial: (modelUuid) => {
|
removeLastMaterial: (modelUuid) => {
|
||||||
let removedMaterial: { materialId: string; materialType: string; } | undefined;
|
let removedMaterial: { materialId: string; materialType: string } | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
if (vehicle) {
|
if (vehicle) {
|
||||||
@@ -239,14 +232,14 @@ export const createVehicleStore = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getLastMaterial: (modelUuid) => {
|
getLastMaterial: (modelUuid) => {
|
||||||
let removedMaterial: { materialId: string; materialType: string; } | undefined;
|
let removedMaterial: { materialId: string; materialType: string } | undefined;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
if (vehicle) {
|
if (vehicle) {
|
||||||
if (vehicle.currentMaterials.length > 0) {
|
if (vehicle.currentMaterials.length > 0) {
|
||||||
removedMaterial = {
|
removedMaterial = {
|
||||||
materialId: vehicle.currentMaterials[vehicle.currentMaterials.length - 1].materialId,
|
materialId: vehicle.currentMaterials[vehicle.currentMaterials.length - 1].materialId,
|
||||||
materialType: vehicle.currentMaterials[vehicle.currentMaterials.length - 1].materialType
|
materialType: vehicle.currentMaterials[vehicle.currentMaterials.length - 1].materialType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,6 +274,15 @@ export const createVehicleStore = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
incrementDistanceTraveled: (modelUuid, incrementBy) => {
|
||||||
|
set((state) => {
|
||||||
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
|
if (vehicle) {
|
||||||
|
vehicle.distanceTraveled += incrementBy;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
resetTime: (modelUuid) => {
|
resetTime: (modelUuid) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid);
|
||||||
@@ -307,7 +309,7 @@ export const createVehicleStore = () => {
|
|||||||
return get().vehicles.filter((v) => v.isActive);
|
return get().vehicles.filter((v) => v.isActive);
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export type VehicleStoreType = ReturnType<typeof createVehicleStore>;
|
export type VehicleStoreType = ReturnType<typeof createVehicleStore>;
|
||||||
|
|||||||
1
app/src/types/simulationTypes.d.ts
vendored
1
app/src/types/simulationTypes.d.ts
vendored
@@ -930,6 +930,7 @@ interface HumanAnalysis {
|
|||||||
totalTime: number;
|
totalTime: number;
|
||||||
percentage: number;
|
percentage: number;
|
||||||
}[];
|
}[];
|
||||||
|
workloadSummary: string;
|
||||||
|
|
||||||
// Efficiency
|
// Efficiency
|
||||||
efficiency: EfficiencyMetrics & {
|
efficiency: EfficiencyMetrics & {
|
||||||
|
|||||||
Reference in New Issue
Block a user