diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 49f3959d..913841d9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,8 @@ "Bash(powershell -Command \"\\(Get-Content ''Assets\\\\DownloadAssets\\\\XRLib\\\\Scripts\\\\UVC\\\\Entity\\\\Processors\\\\TwinAgentAutoProcessor.cs'' -Raw\\) -replace ''Completions\\\\[Completions\\\\.Length - 1\\\\]'',''Completions[^1]'' | Set-Content ''Assets\\\\DownloadAssets\\\\XRLib\\\\Scripts\\\\UVC\\\\Entity\\\\Processors\\\\TwinAgentAutoProcessor.cs'' -NoNewline\")", "Bash(powershell -Command \"\\(Get-Content ''Assets\\\\DownloadAssets\\\\XRLib\\\\Scripts\\\\UVC\\\\Entity\\\\Processors\\\\TwinAgentAutoProcessor.cs'' -Raw\\) -replace ''public struct StepProgressInfo'',''public readonly struct StepProgressInfo'' | Set-Content ''Assets\\\\DownloadAssets\\\\XRLib\\\\Scripts\\\\UVC\\\\Entity\\\\Processors\\\\TwinAgentAutoProcessor.cs'' -NoNewline\")", "Bash(git add:*)", - "Bash(git commit -m \"$\\(cat <<''EOF''\nTwinAgentAutoProcessor 가중치 완료조건 및 Entity별 터미널 로그 분리\n\n- CompletionResult enum 추가 \\(Complete/Warning/Error\\)로 완료 상태 분류\n- WeightedCompletion으로 SubStep별 가중치 기반 랜덤 완료 조건 구현\n - Server: OctopusHub 75% / NotFound 25%\\(Error, 중지\\)\n - Protocol: API 75% / MQTT 25%\n - ServerStatusCheck: Done 85% / Busy 10%\\(Warning, 중지\\) / Error 5%\\(중지\\)\n - Speed: 5~120ms 80% / \"300ms~\" 20%\n- IsFullPipelineCompleted 속성으로 전체 파이프라인 완료 여부 판별\n- MessageHistory 버퍼로 Entity별 터미널 로그 이력 관리\n- Entity 전환 시 ClearAllProcessorScopedBindings\\(\\)로 이전 UI 바인딩 해제\n- EntityPropertyAdapter에서 PropertyItem 복원과 터미널 복원 순서 분리\n\nCo-Authored-By: Claude Opus 4.6 \nEOF\n\\)\")" + "Bash(git commit -m \"$\\(cat <<''EOF''\nTwinAgentAutoProcessor 가중치 완료조건 및 Entity별 터미널 로그 분리\n\n- CompletionResult enum 추가 \\(Complete/Warning/Error\\)로 완료 상태 분류\n- WeightedCompletion으로 SubStep별 가중치 기반 랜덤 완료 조건 구현\n - Server: OctopusHub 75% / NotFound 25%\\(Error, 중지\\)\n - Protocol: API 75% / MQTT 25%\n - ServerStatusCheck: Done 85% / Busy 10%\\(Warning, 중지\\) / Error 5%\\(중지\\)\n - Speed: 5~120ms 80% / \"300ms~\" 20%\n- IsFullPipelineCompleted 속성으로 전체 파이프라인 완료 여부 판별\n- MessageHistory 버퍼로 Entity별 터미널 로그 이력 관리\n- Entity 전환 시 ClearAllProcessorScopedBindings\\(\\)로 이전 UI 바인딩 해제\n- EntityPropertyAdapter에서 PropertyItem 복원과 터미널 복원 순서 분리\n\nCo-Authored-By: Claude Opus 4.6 \nEOF\n\\)\")", + "Bash(dir:*)" ] } } diff --git a/Assets/DownloadAssets/XRLib/Resources/Prefabs/UI/Window/PropertyWindow.prefab b/Assets/DownloadAssets/XRLib/Resources/Prefabs/UI/Window/PropertyWindow.prefab index 82db5b39..a88a18d0 100644 --- a/Assets/DownloadAssets/XRLib/Resources/Prefabs/UI/Window/PropertyWindow.prefab +++ b/Assets/DownloadAssets/XRLib/Resources/Prefabs/UI/Window/PropertyWindow.prefab @@ -710,8 +710,8 @@ MonoBehaviour: m_TargetGraphic: {fileID: 8654063950112981296} m_HandleRect: {fileID: 0} m_Direction: 0 - m_Value: 0 - m_Size: 0.99995804 + m_Value: 1 + m_Size: 0.9997558 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -1349,8 +1349,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 0.0001373291} - m_SizeDelta: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0.000015258789} + m_SizeDelta: {x: 0, y: -0.17746803} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &4790256706883938101 MonoBehaviour: @@ -1385,7 +1385,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6982330689999531319} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} m_Name: diff --git a/Assets/DownloadAssets/XRLib/Scripts/UVC/Entity/Processors/TwinAgentAutoProcessor.cs b/Assets/DownloadAssets/XRLib/Scripts/UVC/Entity/Processors/TwinAgentAutoProcessor.cs index bf738973..bee79fa2 100644 --- a/Assets/DownloadAssets/XRLib/Scripts/UVC/Entity/Processors/TwinAgentAutoProcessor.cs +++ b/Assets/DownloadAssets/XRLib/Scripts/UVC/Entity/Processors/TwinAgentAutoProcessor.cs @@ -273,6 +273,9 @@ namespace UVC.Entity.Processors /// 완료된 단계 상태 (PropertyId -> Value). PropertyItem 복원용. private readonly Dictionary _stepStates = new(); + /// 완료된 단계의 결과 분류 (PropertyId -> CompletionResult). UI 색상 매핑용. + private readonly Dictionary _completionResults = new(); + /// 전체 메시지 이력 (Entity별 터미널 로그 복원용) private readonly List<(string PropertyId, string Value)> _messageHistory = new(); @@ -287,6 +290,10 @@ namespace UVC.Entity.Processors /// 전체 파이프라인이 중지 없이 완료되었는지 여부 (Speed까지 완료해야 true) public bool IsFullPipelineCompleted { get; private set; } + /// 특정 단계의 완료 결과를 반환합니다. 아직 완료되지 않은 단계는 null. + public CompletionResult? GetCompletionResult(string propertyId) + => _completionResults.TryGetValue(propertyId, out var result) ? result : null; + /// SubStep 진행 시 콜백 (진행도 정보 포함) public event Action onSubStepChanged; @@ -440,6 +447,7 @@ namespace UVC.Entity.Processors { _stepStates.Clear(); _messageHistory.Clear(); + _completionResults.Clear(); _currentPropertyId = null; IsFullPipelineCompleted = false; Debug.Log("[TwinAgentAutoProcessor] 초기화 완료"); @@ -484,6 +492,7 @@ namespace UVC.Entity.Processors // 완료 상태 선택 (가중치 기반) 및 전송 var completion = def.SelectCompletion(); var completionStatus = completion.Status; + _completionResults[def.PropertyId] = completion.Result; SendMessage(def.PropertyId, completionStatus); _messageHistory.Add((def.PropertyId, completionStatus)); _stepStates[def.PropertyId] = completionStatus; diff --git a/Assets/DownloadAssets/XRLib/Scripts/UVC/UI/Window/PropertyWindow/EntityPropertyAdapter.cs b/Assets/DownloadAssets/XRLib/Scripts/UVC/UI/Window/PropertyWindow/EntityPropertyAdapter.cs index 2879c44e..86b3a409 100644 --- a/Assets/DownloadAssets/XRLib/Scripts/UVC/UI/Window/PropertyWindow/EntityPropertyAdapter.cs +++ b/Assets/DownloadAssets/XRLib/Scripts/UVC/UI/Window/PropertyWindow/EntityPropertyAdapter.cs @@ -254,12 +254,7 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st // TextColor를 SetValue보다 먼저 설정해야 UI의 UpdateValue에서 올바른 색상을 읽음 if (item is LabelProperty label) { - label.TextColor = value switch - { - "Done" => Color.green, - "Canceled" => new Color(1f, 0.647f, 0f), - _ => Color.white - }; + label.TextColor = GetStepColor(value, processor.GetCompletionResult(propertyId)); } item.SetValue(value); @@ -329,18 +324,16 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st if (propId == processor.ProcessorId) return; string stepName = stepNames.TryGetValue(propId, out var name) ? name : propId; - - if (value == "Done") - tv.AddLog($"> [{stepName}] Done", Color.green); - else if (value == "Canceled") - tv.AddLog($"> [{stepName}] Canceled", new Color(1f, 0.647f, 0f)); - else - tv.AddLog($"> [{stepName}] {value}"); + var color = GetStepColor(value, processor.GetCompletionResult(propId)); + tv.AddLog($"> [{stepName}] {value}", color); }; completeTerminal = () => { - tv.AddLog("> Process completed successfully", Color.green); + if (processor.IsFullPipelineCompleted) + tv.AddLog("> Process completed successfully", Color.green); + else + tv.AddLog("> Process stopped", new Color(1f, 0.647f, 0f)); }; cancelTerminal = () => @@ -369,18 +362,18 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st { if (propId == processor.ProcessorId) continue; string stepName = stepNames.TryGetValue(propId, out var sn) ? sn : propId; - - if (value == "Done") - tv.AddLog($"> [{stepName}] Done", Color.green); - else if (value == "Canceled") - tv.AddLog($"> [{stepName}] Canceled", new Color(1f, 0.647f, 0f)); - else - tv.AddLog($"> [{stepName}] {value}"); + var color = GetStepColor(value, processor.GetCompletionResult(propId)); + tv.AddLog($"> [{stepName}] {value}", color); } // 완료/취소 상태에 따라 터미널 종료 메시지 추가 if (processor.IsCompleted) - tv.AddLog("> Process completed successfully", Color.green); + { + if (processor.IsFullPipelineCompleted) + tv.AddLog("> Process completed successfully", Color.green); + else + tv.AddLog("> Process stopped", new Color(1f, 0.647f, 0f)); + } else if (!processor.IsRunning) tv.AddLog("> Process canceled", new Color(1f, 0.647f, 0f)); } @@ -502,15 +495,16 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st { if (propId == processor.ProcessorId) continue; string sn = stepNames.TryGetValue(propId, out var n) ? n : propId; - if (value == "Done") - terminalView?.AddLog($"> [{sn}] Done", Color.green); - else if (value == "Canceled") - terminalView?.AddLog($"> [{sn}] Canceled", new Color(1f, 0.647f, 0f)); - else - terminalView?.AddLog($"> [{sn}] {value}"); + var color = GetStepColor(value, processor.GetCompletionResult(propId)); + terminalView?.AddLog($"> [{sn}] {value}", color); } if (processor.IsCompleted) - terminalView?.AddLog("> Process completed successfully", Color.green); + { + if (processor.IsFullPipelineCompleted) + terminalView?.AddLog("> Process completed successfully", Color.green); + else + terminalView?.AddLog("> Process stopped", new Color(1f, 0.647f, 0f)); + } else if (!processor.IsRunning) terminalView?.AddLog("> Process canceled", new Color(1f, 0.647f, 0f)); @@ -555,6 +549,21 @@ private static EntityTabData? CreateNetworkTab(StageObjectManager.StageObject st } + /// + /// CompletionResult 기반으로 단계 텍스트 색상을 결정합니다. + /// + private static Color GetStepColor(string value, CompletionResult? result) + { + if (value == "Canceled") return new Color(1f, 0.647f, 0f); + return result switch + { + CompletionResult.Complete => Color.green, + CompletionResult.Warning => new Color(1f, 0.647f, 0f), + CompletionResult.Error => Color.red, + _ => Color.white + }; + } + private static void CreateTwinAgentProgressItems( List properties, Dictionary propertyDict, diff --git a/Assets/Resources/UI/Prefabs/ChunilENG/MachineInfoItem.prefab b/Assets/Resources/UI/Prefabs/ChunilENG/MachineInfoItem.prefab index 9951303a..c62ac13b 100644 --- a/Assets/Resources/UI/Prefabs/ChunilENG/MachineInfoItem.prefab +++ b/Assets/Resources/UI/Prefabs/ChunilENG/MachineInfoItem.prefab @@ -750,69 +750,6 @@ RectTransform: m_AnchoredPosition: {x: 150, y: -85} m_SizeDelta: {x: 100, y: 50} m_Pivot: {x: 0.5, y: 0.5} ---- !u!1 &2237869627694328014 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 6554659810962538785} - - component: {fileID: 5302855139812990020} - m_Layer: 0 - m_Name: Name - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &6554659810962538785 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2237869627694328014} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: - - {fileID: 5167543184334540414} - m_Father: {fileID: 9208340457178526904} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0.5, y: 0.5} - m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 54.3, y: -8.6} - m_SizeDelta: {x: 100, y: 30} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!114 &5302855139812990020 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2237869627694328014} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} - m_Name: - m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.HorizontalLayoutGroup - m_Padding: - m_Left: 0 - m_Right: 0 - m_Top: 0 - m_Bottom: 0 - m_ChildAlignment: 5 - m_Spacing: -10 - m_ChildForceExpandWidth: 0 - m_ChildForceExpandHeight: 0 - m_ChildControlWidth: 0 - m_ChildControlHeight: 0 - m_ChildScaleWidth: 0 - m_ChildScaleHeight: 0 - m_ReverseArrangement: 0 --- !u!1 &2289400806680334342 GameObject: m_ObjectHideFlags: 0 @@ -1020,7 +957,7 @@ RectTransform: - {fileID: 6745655140557672707} - {fileID: 7725313684788843977} - {fileID: 3190177910311329453} - - {fileID: 6554659810962538785} + - {fileID: 5167543184334540414} - {fileID: 8742493579327078235} m_Father: {fileID: 4042325045007633289} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -2605,7 +2542,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 84.5, y: 0} + m_AnchoredPosition: {x: 84.5, y: -8} m_SizeDelta: {x: 26, y: 26} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &618883874101042488 @@ -3358,7 +3295,7 @@ GameObject: - component: {fileID: 7560852003513131910} - component: {fileID: 9051470887640582819} m_Layer: 0 - m_Name: +55% + m_Name: Rate m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -3376,11 +3313,11 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 6554659810962538785} + m_Father: {fileID: 9208340457178526904} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 0, y: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 170.93, y: -38.099976} m_SizeDelta: {x: 85.74, y: 30} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &7560852003513131910 @@ -3411,7 +3348,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: +55% + m_text: +99% m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 155cf7b89710f7e40aa64ac01771e416, type: 2} m_sharedMaterial: {fileID: 2250789165734441016, guid: 155cf7b89710f7e40aa64ac01771e416, type: 2} diff --git a/Assets/Scenes/SystemScene.unity b/Assets/Scenes/SystemScene.unity index 45096a35..ef044869 100644 --- a/Assets/Scenes/SystemScene.unity +++ b/Assets/Scenes/SystemScene.unity @@ -630,6 +630,14 @@ PrefabInstance: propertyPath: m_SizeDelta.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 1205322734457541541, guid: 4b98d7ee8b805ff42be384e91f3bf8a4, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0.00012207031 + objectReference: {fileID: 0} + - target: {fileID: 1554032197748324192, guid: 4b98d7ee8b805ff42be384e91f3bf8a4, type: 3} + propertyPath: m_Enabled + value: 1 + objectReference: {fileID: 0} - target: {fileID: 1602519165350808158, guid: 4b98d7ee8b805ff42be384e91f3bf8a4, type: 3} propertyPath: m_Name value: PropertyWindow @@ -4752,9 +4760,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 55e9e2efe7596a746b0c8f0332bab2f9, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::OCTOPUS_TWIN.CameraRoute - gizmoResolution: 20 - gizmoColor: {r: 0, g: 1, b: 1, a: 1} - movePoints: [] --- !u!4 &1716751011 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/ChunilENG/Managements/MachineInfoItemManager.cs b/Assets/Scripts/ChunilENG/Managements/MachineInfoItemManager.cs index 8f1cd144..9a15e4ce 100644 --- a/Assets/Scripts/ChunilENG/Managements/MachineInfoItemManager.cs +++ b/Assets/Scripts/ChunilENG/Managements/MachineInfoItemManager.cs @@ -219,27 +219,28 @@ namespace ChunilENG.Management { if (stageObject.TryGetProcessor(out var processor)) { - if (processor.IsCompleted) + if (processor.IsCompleted && processor.IsFullPipelineCompleted) { RegistStageObject(stageObject); return; } - processor.onComplete += () => OnProcessorCompleted(stageObject); + processor.onComplete += () => OnProcessorCompleted(stageObject, processor); return; } // 프로세서가 아직 없으면 등록 시점에 구독 stageObject.OnProcessorRegistered += registeredProcessor => { - if (registeredProcessor is TwinAgentAutoProcessor) + if (registeredProcessor is TwinAgentAutoProcessor autoProcessor) { - registeredProcessor.onComplete += () => OnProcessorCompleted(stageObject); + registeredProcessor.onComplete += () => OnProcessorCompleted(stageObject, autoProcessor); } }; } - private void OnProcessorCompleted(StageObjectManager.StageObject stageObject) + private void OnProcessorCompleted(StageObjectManager.StageObject stageObject, TwinAgentAutoProcessor processor) { + if (!processor.IsFullPipelineCompleted) return; if (itemToStageObjects.ContainsValue(stageObject)) return; RegistStageObject(stageObject); } diff --git a/Assets/Scripts/ChunilENG/UI/MachineInfoItem/MachineInfoItem.cs b/Assets/Scripts/ChunilENG/UI/MachineInfoItem/MachineInfoItem.cs index e61ae90e..84088f3a 100644 --- a/Assets/Scripts/ChunilENG/UI/MachineInfoItem/MachineInfoItem.cs +++ b/Assets/Scripts/ChunilENG/UI/MachineInfoItem/MachineInfoItem.cs @@ -60,13 +60,16 @@ namespace ChunilENG.UI transform.TryGetComponentInChildren(nameof(goodqtyrate), out goodqtyrate); transform.TryGetComponentInChildren(nameof(workdt), out workdt); transform.TryGetComponentInChildren(nameof(wordno), out wordno); + transform.TryGetComponentInChildren("Rate", out TextMeshProUGUI rate); + var r = UnityEngine.Random.Range(90, 99); + rate.SetText($"+{r}%"); } public void SetData(CompleteInfo data, string machineName) { this.data = data; eorate.SetText(DecimalPointCalculate(data.eorate).ToString() + "%"); - daynight.SetText(SetTextData((data.daynight == "1")? "ְ" : "߰")); + daynight.SetText(SetTextData((data.daynight == "1")? "�ְ�" : "�߰�")); workcd.SetText(SetTextData(data.workcd)); goodqtyrate.SetText(DecimalPointCalculate(data.goodqtyrate).ToString() + "%"); porate.SetText(DecimalPointCalculate(data.porate).ToString() + "%"); @@ -81,23 +84,8 @@ namespace ChunilENG.UI } private Color SetStatusColor(string value) { - var color = Color.white; + var color = Color.green; - switch (value) - { - case "-": - color = Color.black; - break; - case "": - color = Color.green; - break; - case "񰡵": - color = Color.red; - break; - default: - color = Color.yellow; - break; - } return color; } private bool CheckDataExists(string value)