<fix> 로봇좌표 TCP전달 수정

This commit is contained in:
SOOBEEN HAN
2025-11-06 15:28:16 +09:00
parent 23e3a34837
commit 033c6aeb8e
12 changed files with 681 additions and 272 deletions

View File

@@ -1,26 +1,14 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactables;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
// Presenter가 InteractionView를 제어하기 위한 인터페이스
public interface IInteractionView
{
event Action<Vector3, Quaternion> OnRobotGrabbed;
// VR 컨트롤러가 로봇을 잡았다 놨을 때 발생
event Action<RobotData> OnRobotReleased;
// VR 컨트롤러가 특정 포인트를 클릭했을 때 발생
event Action<int> OnPointClicked;
// VR 컨트롤러가 포인트를 잡고 드래그 시작/중/끝 했을 때 발생
event Action<int> OnPointDragStart;
event Action<int, Vector3> OnPointDragUpdate; // (포인트 인덱스, 새 월드 좌표)
event Action<int> OnPointDragEnd;
// Presenter가 호출할 함수들
void ShowGhostRobot(RobotData pose);
void HideGhostRobot();
void ShowDragArrow(Vector3 position);
void HideDragArrow();
}
public class InteractionView : MonoBehaviour, IInteractionView
@@ -29,10 +17,12 @@ public class InteractionView : MonoBehaviour, IInteractionView
public event Action<RobotData> OnRobotReleased;
public event Action<int> OnPointClicked;
public event Action<int> OnPointDragStart;
public event Action<int, Vector3> OnPointDragUpdate;
public event Action<int, Vector3, Quaternion> OnPointDragUpdate;
public event Action<int> OnPointDragEnd;
private XRBaseInteractor interactor;
[SerializeField]private NearFarInteractor nearFarInteractor;
private XRBaseInteractor baseInteractor;
private IXRRayProvider rayProvider;
private bool isInitialized = false;
private bool isGrabbingPoint = false;
@@ -42,41 +32,57 @@ public class InteractionView : MonoBehaviour, IInteractionView
void Start()
{
// Ray Interactor와 Direct Interactor 모두 처리할 수 있도록 XRBaseInteractor로 받음
interactor = GetComponent<NearFarInteractor>();
if (interactor == null)
nearFarInteractor = GetComponent<NearFarInteractor>();
if (nearFarInteractor == null)
{
Debug.LogError("InteractionView requires an XRBaseInteractor (Ray or Direct) on the same GameObject.", this);
Debug.LogError("InteractionView: 'nearFarInteractor'가 할당되지 않았습니다", this);
return;
}
baseInteractor = nearFarInteractor as XRBaseInteractor;
rayProvider = nearFarInteractor as IXRRayProvider;
if (baseInteractor == null)
{
Debug.LogError("NearFarInteractor를 XRBaseInteractor로 변환할 수 없습니다.", this);
return;
}
InitializeInteraction();
}
// --- 실시간 스트리밍을 위한 Update ---
void Update()
{
if (isInitialized)
if (!isInitialized || rayProvider == null)
{
if (isGrabbingPoint)
if (nearFarInteractor == null)
{
Vector3 currentControllerPosition = interactor.attachTransform.position;
OnPointDragUpdate?.Invoke(currentGrabbedPointIndex, currentControllerPosition);
nearFarInteractor = GetComponentInChildren<NearFarInteractor>(true);
baseInteractor = nearFarInteractor as XRBaseInteractor;
rayProvider = nearFarInteractor as IXRRayProvider;
}
else if(isGrabbingRobot)
if (nearFarInteractor != null && nearFarInteractor.gameObject.activeInHierarchy)
{
Vector3 currentHandleTransform = interactor.attachTransform.position;
Quaternion currentControllerRotation = interactor.attachTransform.rotation;
OnRobotGrabbed?.Invoke(currentHandleTransform, currentControllerRotation);
InitializeInteraction();
}
}
else
{
if (interactor == null)
Vector3 currentTargetPosition = Vector3.zero;
Quaternion currentTargetRotation = Quaternion.identity;
currentTargetPosition = rayProvider.rayEndPoint;
currentTargetRotation = baseInteractor.attachTransform.rotation;
if (isGrabbingPoint)
{
interactor = GetComponentInChildren<NearFarInteractor>(true);
OnPointDragUpdate?.Invoke(currentGrabbedPointIndex, currentTargetPosition, currentTargetRotation);
}
if (interactor != null && interactor.gameObject.activeInHierarchy)
else if (isGrabbingRobot)
{
InitializeInteraction();
OnRobotGrabbed?.Invoke(currentTargetPosition, currentTargetRotation);
}
}
}
@@ -84,22 +90,23 @@ public class InteractionView : MonoBehaviour, IInteractionView
private void InitializeInteraction()
{
// XRI 이벤트 구독
interactor.selectEntered.AddListener(HandleGrabStart);
interactor.selectExited.AddListener(HandleGrabEnd);
// '클릭'을 감지하려면 interactor.activated 이벤트를 구독해야 함
baseInteractor.selectEntered.AddListener(HandleGrabStart);
baseInteractor.selectExited.AddListener(HandleGrabEnd);
//interactor.activated.AddListener(OnActivated);
//interactor.deactivated.AddListener(OnDeactivated);
isInitialized = true;
AppManager.Instance.RegisterView(this);
Debug.Log("InteractionView 초기화 완료. Interactor 이벤트 구독 시작.");
}
private void OnDestroy()
{
if (interactor != null)
if (baseInteractor != null)
{
interactor.selectEntered.RemoveListener(HandleGrabStart);
interactor.selectExited.RemoveListener(HandleGrabEnd);
baseInteractor.selectEntered.RemoveListener(HandleGrabStart);
baseInteractor.selectExited.RemoveListener(HandleGrabEnd);
//interactor.activated.RemoveListener(OnActivated);
//interactor.deactivated.RemoveListener(OnDeactivated);
}
}
@@ -108,48 +115,157 @@ public class InteractionView : MonoBehaviour, IInteractionView
// "잡기" 버튼을 눌렀을 때
private void HandleGrabStart(SelectEnterEventArgs args)
{
// 잡은 대상 오브젝트
GameObject grabbedGO = args.interactableObject.transform.gameObject;
//// 잡은 것이 "포인트"인지 확인 (RobotPoint 스크립트 탐색)
// 잡은 것이 "포인트"인지 확인
//RobotPoint point = grabbedGO.GetComponent<RobotPoint>();
//if (point != null)
//{
// isGrabbingPoint = true;
// isGrabbingRobot = false;
// currentGrabbedPointIndex = point.pointIndex;
// // Presenter에게 "드래그 시작" 이벤트 전송
// // (Presenter의 HandlePointDragStart 호출)
// OnPointDragStart?.Invoke(currentGrabbedPointIndex);
//}
// 잡은 것이 "로봇"인지 확인
if (grabbedGO.CompareTag("RobotArm"))
// 2. 잡은 것이 "로봇 핸들"인지 확인
if (grabbedGO.CompareTag("RobotArm"))
{
isGrabbingPoint = false;
isGrabbingRobot = true;
// (로봇 자체를 잡는 것은 -1 인덱스로 Presenter에게 알림)
currentGrabbedPointIndex = -1;
// (로봇 잡기도 "PointDragStart" 이벤트로 통합하여 Presenter에 알림)
OnPointDragStart?.Invoke(currentGrabbedPointIndex);
}
//if (!enableSelectToDrag) return;
//BeginDrag(args.interactorObject);
}
// "잡기" 버튼을 뗐을 때
private void HandleGrabEnd(SelectExitEventArgs args)
{
// "포인트"를 놓고 있는 중이었다면
if (isGrabbingPoint)
{
// Presenter에게 "드래그 끝" 이벤트 전송 (팝업 트리거용)
OnPointDragEnd?.Invoke(currentGrabbedPointIndex);
}
// "로봇"을 놓고 있는 중이었다면
else if (isGrabbingRobot)
{
// Presenter에게 "로봇 놓기" 이벤트 전송 (팝업 트리거용)
RobotData currentPose = GetCurrentRobotPoseFromController();
OnRobotReleased?.Invoke(currentPose);
}
//if (!enableSelectToDrag) return;
//EndDrag();
//// "포인트"를 놓고 있는 중이었다면
//if (isGrabbingPoint)
//{
// // Presenter에게 "드래그 끝" 이벤트 전송 (팝업 트리거용)
// OnPointDragEnd?.Invoke(currentGrabbedPointIndex);
//}
//// "로봇"을 놓고 있는 중이었다면
//else if (isGrabbingRobot)
//{
// // Presenter에게 "로봇 놓기" 이벤트 전송 (팝업 트리거용)
// RobotData currentPose = GetCurrentRobotPoseFromController();
// OnRobotReleased?.Invoke(currentPose);
//}
// 상태 초기화
isGrabbingPoint = false;
isGrabbingRobot = false;
currentGrabbedPointIndex = -1;
}
// ---- Drag lifecycle ----
//private void BeginDrag(IXRInteractor interactor)
//{
// currentInteractor = interactor;
// isDragging = true;
// startPosition = transform.position;
// //pathLine.enabled = true;
// uiController?.UpdateStatus("드래그 중...");
// if (communicationScript != null) communicationScript.isMoving = false;
// // ← 드래그 루프 시작
// StartDragMoveLoop();
//}
//private void EndDrag()
//{
// if (!isDragging) return;
// isDragging = false;
// uiController?.UpdateStatus("완료!");
// communicationScript.isMoving = false;
// // ← 루프 중지
// StopDragMoveLoop();
// currentInteractor = null;
//}
//private void StopDragMoveLoop()
//{
// if (dragLoopCts != null)
// {
// dragLoopCts.Cancel();
// dragLoopCts.Dispose();
// dragLoopCts = null;
// }
//}
//private void StartDragMoveLoop()
//{
// StopDragMoveLoop(); // 중복 방지
// dragLoopCts = new CancellationTokenSource();
// _ = DragMoveLoopAsync(dragLoopCts.Token);
//}
//private async Task DragMoveLoopAsync(CancellationToken token)
//{
// // 드래그 동안 100ms 주기로 전송
// while (!token.IsCancellationRequested && isDragging)
// {
// try
// {
// // 로봇이 아직 이동 중이 아니라면(대기 상태) 최신 dragPosition으로 지령 전송
// if (communicationScript != null && !communicationScript.isMoving)
// {
// await MovingTask(); // 내부에서 StratMovement(dragPosition) 호출 및 isMoving 갱신 :contentReference[oaicite:4]{index=4}
// }
// await Task.Delay(10, token); // 100ms 주기
// }
// catch (TaskCanceledException) { /* 정상 취소 */ }
// }
//}
//// ---- Per-frame ----
//void Update()
//{
// if (isDragging && currentInteractor != null)
// {
// communicationScript.isMoving = false;
// // 1) Ray Interactor: 현재 3D Raycast 히트 포인트가 있으면 그대로 사용
// if (currentInteractor is XRRayInteractor ray &&
// ray.TryGetCurrent3DRaycastHit(out RaycastHit hit))
// {
// dragPosition = hit.point;
// }
// else
// {
// // 2) Direct Interactor 또는 Ray 히트가 없을 때: 컨트롤러(또는 attachTransform) 위치 사용
// var t = currentInteractor.transform;
// var attach = currentInteractor.GetAttachTransform(null);
// dragPosition = attach != null ? attach.position : t.position;
// }
// uiController?.UpdateCoordinates(dragPosition); // 기존 UI 갱신 유지 :contentReference[oaicite:2]{index=2}
// }
//}
//private async Task MovingTask()
//{
// if (communicationScript == null) return;
// bool ok = await communicationScript.StratMovement(dragPosition); // 기존 호출 그대로 사용 :contentReference[oaicite:3]{index=3}
// if (ok) communicationScript.isMoving = true;
//}
private RobotData GetCurrentRobotPoseFromController()
{
// 이 View는 Presenter의 변환 로직(ConvertVrToRobotPose)을 알 수 없으므로,
@@ -159,8 +275,23 @@ public class InteractionView : MonoBehaviour, IInteractionView
}
// --- Presenter가 호출할 함수들 ---
public void ShowGhostRobot(RobotData pose) { /* 반투명 로봇2 활성화 및 위치 설정 */ }
public void HideGhostRobot() { /* 반투명 로봇2 비활성화 */ }
public void ShowDragArrow(Vector3 position) { /* 드래그용 화살표 UI 활성화 및 위치 설정 */ }
public void HideDragArrow() { /* 드래그용 화살표 UI 비활성화 */ }
public void ShowGhostRobot(RobotData pose)
{
/* 반투명 로봇2 활성화 및 위치 설정 */
}
public void HideGhostRobot()
{
/* 반투명 로봇2 비활성화 */
}
public void ShowDragArrow(Vector3 position)
{
/* 드래그용 화살표 UI 활성화 및 위치 설정 */
}
public void HideDragArrow()
{
/* 드래그용 화살표 UI 비활성화 */
}
}

View File

@@ -23,7 +23,7 @@ public interface IProgramView
public class ProgramView : MonoBehaviour, IProgramView
{
// --- UI 요소 참조 ---
[SerializeField] private Button loadIconButton;
//[SerializeField] private Button loadIconButton;
[SerializeField] private Button loadProgramButton;
//[SerializeField] private Button saveProgramButton;
//[SerializeField] private Button addPointButton; // UI상 위치를 찍는 버튼
@@ -68,13 +68,12 @@ public class ProgramView : MonoBehaviour, IProgramView
backspaceButton.onClick.AddListener(HandleBackspace);
createProgramButton.onClick.AddListener(HandleCreateClick);
loadIconButton.onClick.AddListener(HandleLoadIconClick);
//loadIconButton.onClick.AddListener(HandleLoadIconClick);
closeProgramListButton.onClick.AddListener(HideProgramList);
programSelectPanel.SetActive(true);
programNewPanel.SetActive(true);
programListPanel.SetActive(false);
loadProgramButton.gameObject.SetActive(false);
programIdText.text = string.Empty;
}
@@ -97,11 +96,6 @@ public class ProgramView : MonoBehaviour, IProgramView
OnCreateProgramClicked?.Invoke(inputId);
}
private void HandleLoadIconClick()
{
loadProgramButton.gameObject.SetActive(true);
}
// Presenter가 호출할 오류 메시지 표시 함수
public void ShowMessage(string message)
{
@@ -112,21 +106,10 @@ public class ProgramView : MonoBehaviour, IProgramView
{
if (programId == null)
{
//currentProgramIdText.text = "No Program Loaded";
//endpointListText.text = "";
Debug.Log("No Program Loaded");
return;
}
Debug.Log($"연결된 프로그램: {programId}.job");
//currentProgramIdText.text = "Current: " + program.programId;
//System.Text.StringBuilder sb = new System.Text.StringBuilder();
//for (int i = 0; i < program.endpointPositions.Count; i++)
//{
// sb.AppendLine($"P{i + 1}: {program.endpointPositions[i].ToString("F2")}");
//}
//endpointListText.text = sb.ToString();
}
public void ShowProgramList(List<string> programIds)