475 lines
15 KiB
C#
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;
|
|
}
|
|
} |