Files
HDRobotics/Assets/Scripts/Presenter/ProgramPresenter.cs
2025-11-07 16:26:43 +09:00

475 lines
15 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
public enum PopupState
{
None,
ConfirmAddPoint,
ConfirmModifyPoint,
MoveOrDelete,
ConfirmDelete
}
public class ProgramPresenter
{
private ProgramModel model;
private ProgramView view;
private TCPView tcpView;
private RobotController controlledRobot;
private string _programId;
private bool lastKnownMotorState = false;
private InteractionView interactionView;
private PointManagerView pointManagerView;
private PathLineView pathLineView;
private PopupView popupView;
private Transform popPos;
private PopupState currentPopupState = PopupState.None;
private RobotData pendingPointData; // 팝업창에 대한 응답을 기다리는 임시 데이터
private int activePointIndex = -1; // 현재 수정/삭제 중인 포인트의 인덱스
private CancellationTokenSource dragLoopCts;
private RobotData latestTargetPose; // 뷰가 갱신할 최신 목표 지점
private bool IsDragging = false;
private RobotData originalDragPose; // 취소 시 돌아갈 원본 위치
public ProgramPresenter(ProgramModel model, ProgramView view, TCPView tcpView,
InteractionView interactionView, PointManagerView pmView, PopupView popView, PathLineView pathLineView)
{
this.model = model;
this.view = view;
this.tcpView = tcpView;
this.interactionView = interactionView;
this.pointManagerView = pmView;
this.popupView = popView;
this.pathLineView = pathLineView;
this.view.OnCreateProgramClicked += async (id) => await HandleCreateProgram(id);
this.view.OnLoadProgramListRequested += HandleLoadProgramList;
this.view.OnProgramSelectedToLoad += HandleProgramSelected;
this.view.OnOpenProgramClicked += async () => await HandleOpenProgram();
this.view.OnSaveClicked += HandleSaveProgram;
this.view.OnAddPointClicked += HandleAddPoint;
this.tcpView.OnTCPupdateRequested += HandleTCPViewUpdate;
this.interactionView.OnRobotGrabbed += HandleRobotGrabbed;
this.interactionView.OnRobotReleased += HandleRobotReleased;
this.interactionView.OnPointClicked += HandlePointClicked;
this.interactionView.OnPointDragStart += HandlePointDragStart;
this.interactionView.OnPointDragUpdate += HandlePointDragUpdate;
this.interactionView.OnPointDragEnd += HandlePointDragEnd;
this.popupView.OnPopupResponse += HandlePopupResponse;
}
public void RegisterControlledRobot(RobotController robot)
{
this.controlledRobot = robot;
this.controlledRobot.OnPoseUpdateRequest += HandleGETPose;
popPos = controlledRobot.GetEndPoint();
if (controlledRobot != null)
controlledRobot.StartCoroutine(UpdateRobotMovingStateCoroutine());
}
public async Task UpdateMotorStateAsync()
{
try
{
bool currentState = await model.GetRobotMotorStateAsync();
if (currentState != lastKnownMotorState)
{
controlledRobot.SetMotorState(currentState);
lastKnownMotorState = currentState;
}
}
catch (Exception e)
{
Debug.LogWarning($"모터 상태 업데이트 실패: {e.Message}");
}
}
private IEnumerator UpdateRobotMovingStateCoroutine()
{
var waitForOneSecond = new WaitForSeconds(1.0f);
while (true)
{
Task<string> getTask = model.GetMovingState();
yield return new WaitUntil(() => getTask.IsCompleted);
try
{
if (getTask.IsFaulted)
{
throw getTask.Exception.InnerException;
}
string currentState = getTask.Result;
if (pointManagerView.movingAlert != null)
{
pointManagerView.movingAlert.SetActive(currentState == "1");
}
}
catch (Exception e)
{
Debug.LogWarning($"로봇 moving 상태 업데이트 실패: {e.Message}");
}
yield return waitForOneSecond;
}
}
public void OnApplicationStart()
{
if (controlledRobot != null)
{
Debug.Log("제어할 로봇이 등록되었습니다.");
}
else
{
Debug.LogError("제어할 로봇이 등록되지 않았습니다");
}
}
private async Task HandleCreateProgram(string programId)
{
if (await model.CreateNewProgram(programId))
{
view.DisplayProgram(programId);
view.HideProgramSelectPanel();
OnApplicationStart();
}
else
{
view.ShowMessage($"Error: Program ID '{programId}.job' already exists or is invalid.");
}
}
private void HandleLoadProgramList()
{
var programIds = model.GetAllProgramIds();
view.ShowProgramList(programIds);
}
private void HandleProgramSelected(string programId)
{
_programId = programId;
}
private async Task HandleOpenProgram()
{
if(_programId != null && await model.LoadProgram(_programId))
{
view.DisplayProgram(_programId);
view.HideProgramSelectPanel();
view.HideProgramList();
OnApplicationStart();
RedrawSceneFromModel(); // 뷰 갱신
_programId = null;
}
}
private void HandleSaveProgram()
{
//model.SaveCurrentProgram();
}
private void HandleAddPoint()
{
//Vector3 newPoint = new Vector3(Random.Range(-1f, 1f), Random.Range(0f, 1f), Random.Range(-1f, 1f));
//model.AddPointToCurrentProgram(newPoint);
//view.DisplayProgram(model.CurrentProgram);
}
private void HandleTCPViewUpdate()
{
if (model.IsNewDataAvailable())
{
RobotData data = model.GetLatestRobotData();
tcpView.SetCoordinates(data);
}
}
// --- 실시간 동기화(제어기->3d) ---
private void HandleGETPose()
{
RobotData data = model.GetLatestRobotData();
if (data != null)
{
controlledRobot.DisableIK();
controlledRobot.SetRobotPosition(data); // 3D 가상 로봇 모델 위치 업데이트
}
}
// --- 실시간 동기화(3d->제어기) ---
private RobotData ConvertPoseToRobotData(Vector3 newWorldPos, Quaternion newWorldRot)
{
RobotData targetPose = new RobotData();
targetPose.x = Convert.ToSingle(Math.Round(-1 * newWorldPos.x * 1000, 2));
targetPose.y = Convert.ToSingle(Math.Round(-1 * newWorldPos.z * 1000, 2));
targetPose.z = Convert.ToSingle(Math.Round(newWorldPos.y * 1000, 2));
Vector3 eulerAngles = newWorldRot.eulerAngles;
targetPose.rx = Convert.ToSingle(Math.Round(eulerAngles.x, 2));
targetPose.ry = Convert.ToSingle(Math.Round(eulerAngles.y, 2));
targetPose.rz = Convert.ToSingle(Math.Round(eulerAngles.z, 2));
return targetPose;
}
private Vector3 ConvertRobotDataToVector3(RobotData pose)
{
float x = Convert.ToSingle(pose.x / -1000.0);
float y = Convert.ToSingle(pose.z / 1000.0);
float z = Convert.ToSingle(pose.y / -1000.0);
return new Vector3(x, y, z);
}
private async void HandleRobotGrabbed(Vector3 newWorldPos, Quaternion newWorldRot)
{
try
{
if (!IsDragging)
{
Debug.LogWarning("HandleRobotGrabbed: 'IsDragging'이 false라서 중단됨.");
return;
}
this.latestTargetPose = ConvertPoseToRobotData(newWorldPos, newWorldRot);
await model.MoveToPoseTcpAsync(newWorldPos);
}
catch (Exception e)
{
Debug.LogError($"HandleRobotGrabbed 내부 오류: {e.Message}\n{e.StackTrace}");
}
}
// --- 새 포인트 추가 ---
private void HandleRobotReleased(RobotData pose)
{
IsDragging = false;
pendingPointData = this.latestTargetPose; // 임시 저장
currentPopupState = PopupState.ConfirmAddPoint; // 상태 설정
_ = WaitForRobotToStopAndShowPopup(currentPopupState);
}
private void StopDragMoveLoop()
{
dragLoopCts?.Cancel();
dragLoopCts?.Dispose();
dragLoopCts = null;
}
// --- 포인트 클릭 ---
private void HandlePointClicked(int index, Vector3 newWorldPos)
{
activePointIndex = index; // 인덱스 저장
currentPopupState = PopupState.MoveOrDelete; // 상태 설정
popupView.ShowOptionPopup(newWorldPos); // 팝업 요청
}
// --- 포인트 드래그 ---
private void HandlePointDragStart(int index)
{
IsDragging = true;
activePointIndex = index;
if (index > -1) // 포인트를 잡았을 때
{
Vector3 startPos = interactionView.GetCurrentRayPosition();
interactionView.ShowDragArrow(startPos);
interactionView.ShowGhostRobot();
// 반투명 로봇의 위치를 마우스 위치와 같도록 설정
pointManagerView.kinematicsNode.targetTransform.localPosition = startPos;
}
}
private void HandlePointDragUpdate(int index, Vector3 newWorldPos, Quaternion newWorldRot)
{
if (!IsDragging) return;
this.latestTargetPose = ConvertPoseToRobotData(newWorldPos, newWorldRot);
interactionView.ShowDragArrow(newWorldPos); // 마우스 이미지 변경
// 고스트 로봇, 포인트, 경로 실시간 이동
pointManagerView.kinematicsNode.targetTransform.localPosition = newWorldPos;
pointManagerView.UpdatePointPosition(index, this.latestTargetPose);
RedrawTemporaryPath(index, latestTargetPose);
}
private async void HandlePointDragEnd(int index)
{
IsDragging = false;
interactionView.HideDragArrow();
interactionView.HideGhostRobot();
pendingPointData = this.latestTargetPose;
Vector3 popupWorldPos = ConvertRobotDataToVector3(pendingPointData);
await model.MoveToPoseTcpAsync(popupWorldPos);
currentPopupState = PopupState.ConfirmModifyPoint; // 상태 설정
popupView.ShowModifyPopup(popPos);
}
// --- 팝업 응답 통합 핸들러 ---
private async void HandlePopupResponse(PopupResponse response)
{
popupView.HidePopup();
switch (currentPopupState)
{
// 기록 확정/취소
case PopupState.ConfirmAddPoint:
if (response == PopupResponse.InsConfirm)
{
await model.SavePointToProgramAsync(pendingPointData, -1);
RedrawSceneFromModel(); // 뷰 갱신
}
break;
// 수정 확정/취소
case PopupState.ConfirmModifyPoint:
if (response == PopupResponse.ModConfirm)
{
// 최종 위치를 서버에 저장
await model.SavePointToProgramAsync(pendingPointData, activePointIndex);
RedrawSceneFromModel();
}
else if (response == PopupResponse.Cancel)
{
pointManagerView.UpdatePointPosition(activePointIndex, originalDragPose); // 원위치
RedrawTemporaryPath(activePointIndex, originalDragPose); // 경로 원위치
}
break;
// 여기로 이동/삭제
case PopupState.MoveOrDelete:
RobotData targetPose = model.CurrentProgram.GetStepPose(activePointIndex);
Vector3 pos = ConvertRobotDataToVector3(targetPose);
if (response == PopupResponse.Move)
{
await model.MoveToPoseTcpAsync(pos);
}
else if (response == PopupResponse.Delete)
{
popupView.ShowDeletePopup(pos);
currentPopupState = PopupState.ConfirmDelete;
return;
}
break;
// 최종 삭제
case PopupState.ConfirmDelete:
if (response == PopupResponse.DelConfirm)
{
await model.DeletePointFromProgramAsync(activePointIndex);
RedrawSceneFromModel();
}
break;
}
// 모든 작업 후 상태 초기화
currentPopupState = PopupState.None;
activePointIndex = -1;
}
private async Task WaitForRobotToStopAndShowPopup(PopupState nextState)
{
while (await model.GetMovingState() == "1")
{
Debug.Log("로봇이 멈추기를 기다리는 중...");
await Task.Delay(100);
}
Debug.Log("로봇 정지 완료. 팝업 표시.");
currentPopupState = nextState;
Vector3 popupWorldPos = Vector3.zero;
if (nextState == PopupState.ConfirmAddPoint)
{
popupWorldPos = ConvertRobotDataToVector3(pendingPointData);
popupView.ShowConfirmPopupFromPoint(popupWorldPos); // (새 포인트 위치)
}
}
private async Task WaitForRobotToStop()
{
Debug.Log("로봇 이동 완료 대기 중...");
await Task.Delay(200); // (명령이 전달될 최소 시간)
while (await model.GetMovingState() == "1")
{
await Task.Delay(100);
}
Debug.Log("로봇 정지 완료.");
}
// Model의 현재 상태를 읽어 모든 View를 새로 고침
private async void RedrawSceneFromModel()
{
if (model.CurrentProgram == null) return;
await model.LoadProgram(model.CurrentProgram.ProgramId);
// RobotProgram.Steps (List<RobotMoveStep>)를 List<RobotData>로 변환
List<RobotData> poses = model.CurrentProgram.GetAllStepPoses();
pointManagerView.RedrawPoints(poses); // 포인트 다시 그림
pathLineView.DrawPath(poses); // 경로 다시 그림
}
// 실시간 경로 그리기
private void RedrawTemporaryPath(int modifiedIndex, RobotData tempPose)
{
if (model.CurrentProgram == null) return;
List<RobotData> poses = model.CurrentProgram.GetAllStepPoses();
if (poses == null || modifiedIndex < 0 || modifiedIndex >= poses.Count)
{
return;
}
pathLineView.DrawPath(poses, modifiedIndex, tempPose);
}
private void Destroy()
{
this.view.OnCreateProgramClicked -= async (id) => await HandleCreateProgram(id);
this.view.OnLoadProgramListRequested -= HandleLoadProgramList;
this.view.OnProgramSelectedToLoad -= HandleProgramSelected;
this.view.OnOpenProgramClicked -= async () => await HandleOpenProgram();
this.view.OnSaveClicked -= HandleSaveProgram;
this.view.OnAddPointClicked -= HandleAddPoint;
this.tcpView.OnTCPupdateRequested -= HandleTCPViewUpdate;
this.controlledRobot.OnPoseUpdateRequest -= HandleGETPose;
this.interactionView.OnRobotGrabbed -= HandleRobotGrabbed;
this.interactionView.OnRobotReleased -= HandleRobotReleased;
this.interactionView.OnPointDragStart -= HandlePointDragStart;
this.interactionView.OnPointDragUpdate -= HandlePointDragUpdate;
this.interactionView.OnPointDragEnd -= HandlePointDragEnd;
}
}