<fix> 로봇 Grab하는 동안 생긴 버그 해결, 놓으면 바로 멈추도록 변경, 프로그램 재생 시에도 사운드 재생하도록 변경

This commit is contained in:
SOOBEEN HAN
2025-11-25 15:15:54 +09:00
parent d599d066be
commit 810432db96
12 changed files with 347 additions and 126 deletions

View File

@@ -134,7 +134,7 @@ Transform:
m_GameObject: {fileID: 9153036347640126610}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0.8996404, y: 1.4682918, z: 1.5212109}
m_LocalPosition: {x: -6.382849, y: 7.971563, z: 6.985526}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:

View File

@@ -43836,6 +43836,7 @@ MonoBehaviour:
robotInfoView: {fileID: 873227714}
programInfoView: {fileID: 1807992934}
envView: {fileID: 971773399}
gripperCollide: {fileID: 2809842780563999912}
robotController: {fileID: 806304512143720359}
pointManagerView: {fileID: 1299890574}
pathLineView: {fileID: 1299890572}
@@ -75135,9 +75136,10 @@ GameObject:
- component: {fileID: 7750953148055529459}
- component: {fileID: 7899423492771922356}
- component: {fileID: 7899423492771922357}
- component: {fileID: 7899423492771922358}
m_Layer: 0
m_Name: "\uB85C\uBD07\uD314_06"
m_TagString: Untagged
m_TagString: Robot
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
@@ -75396,6 +75398,9 @@ GameObject:
- component: {fileID: 2809842780563999911}
- component: {fileID: 1878676147561002156}
- component: {fileID: 2580099376798576862}
- component: {fileID: 2809842780563999914}
- component: {fileID: 2809842780563999913}
- component: {fileID: 2809842780563999912}
m_Layer: 0
m_Name: "\uADF8\uB9AC\uD37C_1"
m_TagString: Untagged
@@ -75505,6 +75510,68 @@ Transform:
m_Children: []
m_Father: {fileID: 6476108356885335244}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2809842780563999912
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2254038508360166054}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c1c9958a493b1e042bf5480ca2d9f3af, type: 3}
m_Name:
m_EditorClassIdentifier:
robotBodyTag: Robot
isTriggerRobot: 0
--- !u!65 &2809842780563999913
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2254038508360166054}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 0.0030580568, y: 0.0030454502, z: 0.001771739}
m_Center: {x: -0.00000038708095, y: 0.000021483833, z: -0.000944237}
--- !u!54 &2809842780563999914
Rigidbody:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2254038508360166054}
serializedVersion: 4
m_Mass: 1
m_Drag: 0
m_AngularDrag: 0.05
m_CenterOfMass: {x: 0, y: 0, z: 0}
m_InertiaTensor: {x: 1, y: 1, z: 1}
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_ImplicitCom: 1
m_ImplicitTensor: 1
m_UseGravity: 0
m_IsKinematic: 1
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
--- !u!1 &3269629316409468015
GameObject:
m_ObjectHideFlags: 0
@@ -75516,7 +75583,6 @@ GameObject:
- component: {fileID: 1034940619344592870}
- component: {fileID: 8348283317296094972}
- component: {fileID: 8218048649755986071}
- component: {fileID: 8348283317296094973}
m_Layer: 6
m_Name: "\uADF8\uB9AC\uD37C_2"
m_TagString: Gripper
@@ -75545,9 +75611,10 @@ GameObject:
- component: {fileID: 7875942327570384369}
- component: {fileID: 8980508415438906781}
- component: {fileID: 8980508415438906782}
- component: {fileID: 8980508415438906783}
m_Layer: 0
m_Name: "\uB85C\uBD07\uD314_05"
m_TagString: Untagged
m_TagString: Robot
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
@@ -75947,6 +76014,27 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: -2328343405082307496, guid: f8e1d25363bc3264ead6a2d4dba3d589, type: 3}
--- !u!65 &7899423492771922358
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1019155422019270077}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 0.0031899994, y: 0.003175993, z: 0.003566943}
m_Center: {x: 0.00024499983, y: 0.00034892443, z: 0.0014505462}
--- !u!33 &7939903229847774538
MeshFilter:
m_ObjectHideFlags: 0
@@ -76106,27 +76194,6 @@ MeshFilter:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3269629316409468015}
m_Mesh: {fileID: 3108956238139520217, guid: f8e1d25363bc3264ead6a2d4dba3d589, type: 3}
--- !u!65 &8348283317296094973
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3269629316409468015}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 0.0029685243, y: 0.002955866, z: 0.0010668265}
m_Center: {x: -0.0000137486595, y: 0.0000029048076, z: -0.00037045297}
--- !u!23 &8380366081719268712
MeshRenderer:
m_ObjectHideFlags: 0
@@ -76285,6 +76352,27 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 1644276158865073351, guid: f8e1d25363bc3264ead6a2d4dba3d589, type: 3}
--- !u!65 &8980508415438906783
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3543066072088338770}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 0.0022507885, y: 0.0021192858, z: 0.008971175}
m_Center: {x: -0.000027214437, y: 0.00081971334, z: 0.0041109337}
--- !u!4 &9208936240245234726
Transform:
m_ObjectHideFlags: 0

View File

@@ -15,6 +15,7 @@ public class AppManager : MonoBehaviour
[SerializeField] private RobotInfoView robotInfoView;
[SerializeField] private ProgramInfoView programInfoView;
[SerializeField] private EnvView envView;
[SerializeField] private GripperCollide gripperCollide;
[SerializeField] private RobotController robotController;
private InteractionView leftInteractionView;
@@ -109,6 +110,7 @@ public class AppManager : MonoBehaviour
robotInfoView,
programInfoView,
envView,
gripperCollide,
leftInteractionView, rightInteractionView,
pointManagerView,
popupView,

View File

@@ -0,0 +1,25 @@
using UnityEngine;
public class GripperCollide : MonoBehaviour
{
[Tooltip("·Îº¿ ¸öÅë¿¡ »ç¿ëµÉ ű×")]
[SerializeField] private string robotBodyTag = "Robot";
public bool isTriggerRobot = false;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag(robotBodyTag))
{
isTriggerRobot = true;
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag(robotBodyTag))
{
isTriggerRobot = false;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c1c9958a493b1e042bf5480ca2d9f3af

View File

@@ -470,6 +470,7 @@ public class ProgramModel
{
Debug.Log("TCP POST (Move) 명령 전송 성공");
IsMoving = true;
Debug.DrawLine(Camera.main.transform.position, position, Color.red, 0.1f);
return true;
}
else
@@ -497,9 +498,9 @@ public class ProgramModel
// TCP POST 이동 명령 체크
public async Task StartMovementCheckLoopAsync(CancellationToken token)
{
try
while (!token.IsCancellationRequested)
{
while (!token.IsCancellationRequested)
try
{
if (IsMoving)
{
@@ -525,14 +526,14 @@ public class ProgramModel
await Task.Delay(500, token);
}
}
}
catch (TaskCanceledException)
{
Debug.Log("MovementCheckLoopAsync Canceled.");
}
catch (Exception e)
{
Debug.LogError($"MovementCheckLoopAsync Error: {e.Message}\n{e.StackTrace}");
catch (TaskCanceledException)
{
Debug.Log("MovementCheckLoopAsync Canceled.");
}
catch (Exception e)
{
Debug.LogError($"MovementCheckLoopAsync Error: {e.Message}\n{e.StackTrace}");
}
}
}
@@ -556,7 +557,7 @@ public class ProgramModel
{
this.isError = newErrorState;
OnRobotErrorStateChanged?.Invoke(this.isError);
Debug.Log($"[이동범위] 이동 가능 상태: {newErrorState}");
//Debug.Log($"[이동범위] 이동 가능 상태: {newErrorState}");
}
await Task.Delay(100);
@@ -573,7 +574,7 @@ public class ProgramModel
{
this.isError = false;
OnRobotErrorStateChanged?.Invoke(this.isError);
Debug.Log("로컬 에러 상태 강제 해제 (재시도 준비)");
//Debug.Log("로컬 에러 상태 강제 해제 (재시도 준비)");
}
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
public enum PopupState
{
@@ -22,6 +23,7 @@ public class ProgramPresenter
private RobotInfoView robotInfoView;
private ProgramInfoView programInfoView;
private EnvView envView;
private GripperCollide gripperCollide;
private RobotController controlledRobot;
private string _programId;
private bool lastKnownMotorState = false;
@@ -41,12 +43,12 @@ public class ProgramPresenter
private RobotData pendingPointData; // 팝업창에 대한 응답을 기다리는 임시 데이터
private int activePointIndex = -1; // 현재 수정/삭제 중인 포인트의 인덱스
private CancellationTokenSource appCts;
private CancellationTokenSource dragLoopCts;
private RobotData latestTargetPose; // 뷰가 갱신할 최신 목표 지점
private bool IsDragging = false;
private bool IsStopped = false; // 에러 나서 멈췄는지 여부
private bool errMatChanged = false; // 에러 시 로봇 mat을 빨간색으로 한 번만 바뀌도록
private bool isHovering = false;
private bool isProgramStarted = false;
private RobotData originalDragPose; // 취소 시 돌아갈 원본 위치
private float lastMoveSendTime = 0f;
private const float MOVE_SEND_INTERVAL = 0.1f; // 0.1초(100ms)마다 한 번만 전송 (초당 10회)
@@ -57,7 +59,7 @@ public class ProgramPresenter
private AudioSource robotMovingAudioSource;
private bool isFeedbackLoopRunning = false;
public ProgramPresenter(ProgramModel model, ProgramView view, TCPView tcpView, RobotInfoView robotInfoView, ProgramInfoView programInfoView, EnvView envView,
public ProgramPresenter(ProgramModel model, ProgramView view, TCPView tcpView, RobotInfoView robotInfoView, ProgramInfoView programInfoView, EnvView envView, GripperCollide gripperCollide,
InteractionView leftView, InteractionView rightView, PointManagerView pmView, PopupView popView, PathLineView pathLineView, AudioSource robotAudio, CancellationTokenSource cts)
{
this.model = model;
@@ -68,6 +70,7 @@ public class ProgramPresenter
this.robotInfoView = robotInfoView;
this.programInfoView = programInfoView;
this.envView = envView;
this.gripperCollide = gripperCollide;
this.pointManagerView = pmView;
this.popupView = popView;
this.pathLineView = pathLineView;
@@ -90,7 +93,7 @@ public class ProgramPresenter
this.envView.OnLoadProgramListRequested += HandleLoadProgramList;
this.leftInteractionView.OnRobotHoverStateChanged += (state) => HandleRobotHover(state);
this.leftInteractionView.OnRobotGrabStart += () => HandleRobotGrabStart(leftView);
this.leftInteractionView.OnRobotGrabStart += (pos) => HandleRobotGrabStart(leftView, pos);
this.leftInteractionView.OnRobotGrabbed += (pos, rot) => HandleRobotGrabbed(leftView, pos, rot);
this.leftInteractionView.OnRobotReleased += (data) => HandleRobotReleased(leftView, data);
this.leftInteractionView.OnPointClicked += (index, pos) => HandlePointClicked(leftView, index, pos);
@@ -99,7 +102,7 @@ public class ProgramPresenter
this.leftInteractionView.OnPointDragEnd += (index) => HandlePointDragEnd(leftView, index);
this.rightInteractionView.OnRobotHoverStateChanged += (state) => HandleRobotHover(state);
this.rightInteractionView.OnRobotGrabStart += () => HandleRobotGrabStart(rightView);
this.rightInteractionView.OnRobotGrabStart += (pos) => HandleRobotGrabStart(rightView, pos);
this.rightInteractionView.OnRobotGrabbed += (pos, rot) => HandleRobotGrabbed(rightView, pos, rot);
this.rightInteractionView.OnRobotReleased += (data) => HandleRobotReleased(rightView, data);
this.rightInteractionView.OnPointClicked += (index, pos) => HandlePointClicked(rightView, index, pos);
@@ -117,7 +120,7 @@ public class ProgramPresenter
this.robotMovingAudioSource.playOnAwake = false;
}
this.appCts = cts;
this.dragLoopCts = cts;
}
public void RegisterControlledRobot(RobotController robot)
@@ -163,7 +166,10 @@ public class ProgramPresenter
private IEnumerator UpdateRobotMovingStateCoroutine()
{
var waitForOneSecond = new WaitForSeconds(1.0f);
var waitForInterval = new WaitForSeconds(0.2f);
float lastMovingTime = 0f;
float offDelay = 0.5f; // 0.5초 동안은 '0'이 나와도 넘어감 (유예 시간)
while (true)
{
Task<string> getTask = model.GetMovingState();
@@ -172,23 +178,39 @@ public class ProgramPresenter
try
{
if (getTask.IsFaulted)
if (!getTask.IsFaulted)
{
string currentState = getTask.Result;
if (currentState == "1")
{
if (pointManagerView.movingAlert != null)
pointManagerView.movingAlert.SetActive(true);
lastMovingTime = Time.time;
if(!isProgramStarted) isProgramStarted = true; // 프로그램 실행 도중 GetMovingState가 0이 되는 찰나 isProgramStarted가 false가 되어서 추가한 코드
}
else
{
// 마지막으로 움직인 지 0.5초가 지났는지 확인 (유예 시간)
if (Time.time - lastMovingTime > offDelay)
{
if (pointManagerView.movingAlert != null)
pointManagerView.movingAlert.SetActive(false);
if(isProgramStarted) isProgramStarted = false; // 프로그램 실행 후 정지했을 경우
}
}
}
else
{
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;
yield return waitForInterval;
}
}
@@ -205,7 +227,7 @@ public class ProgramPresenter
if (!isFeedbackLoopRunning)
{
_ = CheckRobotMovementFeedbackLoop(appCts.Token);
_ = CheckRobotMovementFeedbackLoop(dragLoopCts.Token);
}
}
@@ -222,7 +244,7 @@ public class ProgramPresenter
{
if (model.IsMoving)
{
// 로봇이 움직이는데 소리가 안 나면
// 로봇이 움직이는데 소리가 안 나면
if (!robotMovingAudioSource.isPlaying)
{
robotMovingAudioSource.Play();
@@ -237,8 +259,13 @@ public class ProgramPresenter
}
else
{
// 프로그램 시작 시에만 켬
if (isProgramStarted && !robotMovingAudioSource.isPlaying)
{
robotMovingAudioSource.Play();
}
// 로봇이 멈췄는데 소리가 나면 끔
if (robotMovingAudioSource.isPlaying)
else if (!isProgramStarted && robotMovingAudioSource.isPlaying)
{
robotMovingAudioSource.Stop();
}
@@ -265,7 +292,7 @@ public class ProgramPresenter
{
if (isError)
{
_ = model.stopProgram(); // 프로그램 실행 중인 경우면 정지
//_ = model.stopProgram(); // 프로그램 실행 중인 경우면 정지
popupView.ShowErrorAlert(true, 2.0f);
Debug.LogError("로봇 에러 발생 (범위 초과 등)");
@@ -355,11 +382,13 @@ public class ProgramPresenter
private async Task HandleProgramStart()
{
isProgramStarted = true;
await model.startProgram();
}
private async Task HandleProgramStop()
{
robotMovingAudioSource.Stop();
await model.stopProgram();
}
@@ -403,7 +432,7 @@ public class ProgramPresenter
}
// --- 실시간 동기화(3d->제어기) ---
private RobotData ConvertPoseToRobotData(Vector3 newWorldPos, Quaternion newWorldRot)
private RobotData ConvertPoseToRobotData(Vector3 newWorldPos)
{
RobotData targetPose = new RobotData();
@@ -411,11 +440,11 @@ public class ProgramPresenter
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;
//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));
//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;
}
@@ -445,7 +474,7 @@ public class ProgramPresenter
}
}
private void HandleRobotGrabStart(InteractionView view)
private void HandleRobotGrabStart(InteractionView view, Vector3 newWorldPos)
{
activeInteractionView = view;
@@ -456,8 +485,12 @@ public class ProgramPresenter
Debug.Log("로봇 잡음: 에러 상태 해제");
}
model.ResetLocalErrorState();
IsStopped = false;
dragLoopCts = new CancellationTokenSource();
IsDragging = true;
errMatChanged = false;
gripperCollide.isTriggerRobot = false;
if (model != null) model.IsMoving = false;
}
private async void HandleRobotGrabbed(InteractionView view, Vector3 newWorldPos, Quaternion newWorldRot)
@@ -469,17 +502,20 @@ public class ProgramPresenter
if (model.isError)
{
IsStopped = true;
if (!errMatChanged) // 제어기에서 에러가 false인 경우는 알려주지 않아서 계속 model.isError가 true로 실행되어 넣은 조건문
{
controlledRobot.toolEndpoint.GetComponent<MeshRenderer>().material = controlledRobot.toolEndpointErrorHighlight;
errMatChanged = true;
return;
}
else
{
controlledRobot.toolEndpoint.GetComponent<MeshRenderer>().material = controlledRobot.toolEndpointHighlight;
}
controlledRobot.toolEndpoint.GetComponent<MeshRenderer>().material = controlledRobot.toolEndpointErrorHighlight;
errMatChanged = true;
model.IsMoving = false;
StopDragMoveLoop();
//await Task.Delay(100);
Debug.Log("에러로 잠시 멈추고 빨간 mat설정");
}
controlledRobot.toolEndpoint.GetComponent<MeshRenderer>().material = controlledRobot.toolEndpointHighlight;
if (gripperCollide.isTriggerRobot)
{
model.IsMoving = false;
StopDragMoveLoop();
return;
}
if (!IsDragging)
@@ -488,26 +524,17 @@ public class ProgramPresenter
return;
}
this.latestTargetPose = ConvertPoseToRobotData(newWorldPos, newWorldRot);
// 동기화 속도를 늘리기 위해 명령 횟수 줄임
if (!model.isError)
this.latestTargetPose = ConvertPoseToRobotData(newWorldPos);
try
{
// 시간 체크: 마지막 전송 후 0.1초가 지났는가?
bool isTimeToSend = (Time.time - lastMoveSendTime) >= MOVE_SEND_INTERVAL;
// 거리 체크: 이전 전송 위치보다 유의미하게(1cm 이상) 움직였는가?
bool isDistToSend = Vector3.Distance(lastSentPosition, newWorldPos) >= MIN_MOVE_DISTANCE;
if (isTimeToSend && isDistToSend)
{
// 전송
_ = await model.MoveToPoseTcpAsync(newWorldPos);
// 시간 및 위치 갱신
lastMoveSendTime = Time.time;
lastSentPosition = newWorldPos;
}
// 전송
_ = await model.MoveToPoseTcpAsync(newWorldPos);
Debug.Log($"로봇 Grabbed 상태, TCP업데이트 : {newWorldPos}");
}
catch (Exception e)
{
Debug.LogError(e.Message);
}
}
catch (Exception e)
@@ -521,20 +548,63 @@ public class ProgramPresenter
{
if (view != activeInteractionView) return;
IsDragging = false;
model.IsMoving = false;
StopDragMoveLoop();
//pendingPointData = this.latestTargetPose; // 임시 저장
pendingPointData = pose;
controlledRobot.toolEndpoint.GetComponent<MeshRenderer>().material = controlledRobot.toolEndpointOriginal;
//if (!IsStopped)
//{
currentPopupState = PopupState.ConfirmAddPoint; // 상태 설정
_ = WaitForRobotToStopAndShowPopup(currentPopupState);
//}
currentPopupState = PopupState.ConfirmAddPoint; // 상태 설정
_ = WaitForRobotToStopAndShowPopup(currentPopupState);
activeInteractionView = null;
}
private void StopDragMoveLoop()
{
if (dragLoopCts != null)
{
dragLoopCts.Cancel();
dragLoopCts.Dispose();
dragLoopCts = null;
Debug.Log("dragLoop 토큰 cancel");
}
}
private void StartDragMoveLoop(Vector3 dragPosition)
{
StopDragMoveLoop(); // 중복 방지
dragLoopCts = new CancellationTokenSource();
_ = DragMoveLoopAsync(dragLoopCts.Token, dragPosition);
}
private async Task DragMoveLoopAsync(CancellationToken token, Vector3 dragPosition)
{
// 드래그 동안 100ms 주기로 전송
while (!token.IsCancellationRequested && IsDragging)
{
try
{
// 로봇이 아직 이동 중이 아니라면(대기 상태) 최신 dragPosition으로 지령 전송
if (model != null && !model.IsMoving)
{
await MovingTask(dragPosition); // 내부에서 StratMovement(dragPosition) 호출 및 isMoving 갱신 :contentReference[oaicite:4]{index=4}
}
await Task.Delay(10, token); // 100ms 주기
}
catch (TaskCanceledException) { /* 정상 취소 */ }
}
}
private async Task MovingTask(Vector3 dragPosition)
{
if (model == null) return;
bool ok = await model.MoveToPoseTcpAsync(dragPosition); // 기존 호출 그대로 사용
if (ok) model.IsMoving = true;
}
// --- 포인트 클릭 ---
private void HandlePointClicked(InteractionView view, int index, Vector3 newWorldPos)
{
@@ -558,8 +628,6 @@ public class ProgramPresenter
// --- 포인트 드래그 ---
private void HandlePointDragStart(InteractionView view, int index)
{
IsDragging = true;
activePointIndex = index;
activeInteractionView = view;
@@ -573,6 +641,11 @@ public class ProgramPresenter
// 반투명 로봇의 위치를 마우스 위치와 같도록 설정
pointManagerView.kinematicsNode.targetTransform.position = startPos;
originalDragPose = model.CurrentProgram.GetStepPose(index);
model.ResetLocalErrorState();
IsDragging = true;
dragLoopCts = new CancellationTokenSource();
gripperCollide.isTriggerRobot = false;
}
}
@@ -581,12 +654,12 @@ public class ProgramPresenter
if (!IsDragging) return;
if (view != activeInteractionView) return;
pointManagerView.kinematicsNode.targetTransform.position = newWorldPos;
pointManagerView.kinematicsNode.targetTransform.rotation = newWorldRot;
//pointManagerView.kinematicsNode.targetTransform.rotation = newWorldRot;
Vector3 ghostRobotPos = pointManagerView.kinematicsNode.nodes[5].jointTransform.position;
Quaternion ghostRobotRot = pointManagerView.kinematicsNode.nodes[5].jointTransform.rotation;
//Quaternion ghostRobotRot = pointManagerView.kinematicsNode.nodes[5].jointTransform.rotation;
this.latestTargetPose = ConvertPoseToRobotData(ghostRobotPos, ghostRobotRot);
this.latestTargetPose = ConvertPoseToRobotData(ghostRobotPos);
activeInteractionView.ShowDragArrow(newWorldPos); // 마우스 이미지 변경
// 고스트 로봇, 포인트, 경로 실시간 이동
@@ -607,6 +680,7 @@ public class ProgramPresenter
Vector3 popupWorldPos = ConvertRobotDataToVector3(pendingPointData);
await model.MoveToPoseTcpAsync(popupWorldPos);
//StartDragMoveLoop(popupWorldPos);
currentPopupState = PopupState.ConfirmModifyPoint; // 상태 설정
@@ -679,25 +753,31 @@ public class ProgramPresenter
private async Task WaitForRobotToStopAndShowPopup(PopupState nextState)
{
await Task.Delay(200);
while (await model.GetMovingState() == "1")
{
Debug.Log("로봇이 멈추기를 기다리는 중...");
await Task.Delay(100);
}
Debug.Log("로봇 정지 완료. 팝업 표시.");
currentPopupState = nextState;
Vector3 popupWorldPos = Vector3.zero;
currentPopupState = nextState;
if (nextState == PopupState.ConfirmAddPoint)
{
if(!IsDragging) StopDragMoveLoop();
while (await model.GetMovingState() == "1")
{
Debug.Log("로봇이 멈추기를 기다리는 중...");
await Task.Delay(100);
}
model.IsMoving = false;
StopDragMoveLoop();
Debug.Log("로봇 정지 완료. 팝업 표시.");
popupWorldPos = ConvertRobotDataToVector3(pendingPointData);
popupView.ShowConfirmPopupFromPoint(popupWorldPos); // 새 포인트 위치
}
else if(nextState == PopupState.ConfirmModifyPoint)
{
while (await model.GetMovingState() == "1")
{
Debug.Log("로봇이 멈추기를 기다리는 중...");
await Task.Delay(100);
}
Debug.Log("로봇 정지 완료. 팝업 표시.");
popupWorldPos = ConvertRobotDataToVector3(pendingPointData);
popupView.ShowModifyPopup(popupWorldPos);
}
@@ -773,6 +853,7 @@ public class ProgramPresenter
this.envView.OnLoadProgramListRequested -= HandleLoadProgramList;
this.leftInteractionView.OnRobotHoverStateChanged -= (state) => HandleRobotHover(state);
this.leftInteractionView.OnRobotGrabStart -= (pos) => HandleRobotGrabStart(leftInteractionView, pos);
this.leftInteractionView.OnRobotGrabbed -= (pos, rot) => HandleRobotGrabbed(leftInteractionView, pos, rot);
this.leftInteractionView.OnRobotReleased -= (data) => HandleRobotReleased(leftInteractionView, data);
this.leftInteractionView.OnPointClicked -= (index, pos) => HandlePointClicked(leftInteractionView, index, pos);
@@ -781,6 +862,7 @@ public class ProgramPresenter
this.leftInteractionView.OnPointDragEnd -= (index) => HandlePointDragEnd(leftInteractionView, index);
this.rightInteractionView.OnRobotHoverStateChanged -= (state) => HandleRobotHover(state);
this.rightInteractionView.OnRobotGrabStart -= (pos) => HandleRobotGrabStart(rightInteractionView, pos);
this.rightInteractionView.OnRobotGrabbed -= (pos, rot) => HandleRobotGrabbed(this.rightInteractionView, pos, rot);
this.rightInteractionView.OnRobotReleased -= (data) => HandleRobotReleased(this.rightInteractionView, data);
this.rightInteractionView.OnPointClicked -= (index, pos) => HandlePointClicked(this.rightInteractionView, index, pos);

View File

@@ -10,7 +10,7 @@ public enum HandSide { Left, Right }
public class InteractionView : MonoBehaviour
{
public event Action<bool> OnRobotHoverStateChanged;
public event Action OnRobotGrabStart;
public event Action<Vector3> OnRobotGrabStart;
public event Action<Vector3, Quaternion> OnRobotGrabbed;
public event Action<RobotData> OnRobotReleased;
public event Action<int, Vector3> OnPointClicked;
@@ -173,7 +173,7 @@ public class InteractionView : MonoBehaviour
isGrabbingRobot = true;
currentGrabbedPointIndex = -1;
OnRobotGrabStart?.Invoke();
OnRobotGrabStart?.Invoke(startGrabPosition);
OnPointDragStart?.Invoke(currentGrabbedPointIndex);
}
}

View File

@@ -3,13 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// 인터페이스
public interface IPathLineView
{
}
[RequireComponent(typeof(LineRenderer))]
public class PathLineView : MonoBehaviour, IPathLineView
public class PathLineView : MonoBehaviour
{
private LineRenderer lineRenderer;

View File

@@ -21,21 +21,43 @@ public class PointManagerView : MonoBehaviour
private Vector3 ConvertRobotDataToVector3(RobotData pose)
{
float x = Convert.ToSingle(pose.x / -1000.0); // mm -> m
float x = Convert.ToSingle(pose.x / -1000.0); // Robot X(mm) -> Unity X(m)
float y = Convert.ToSingle(pose.z / 1000.0); // Robot Z(mm) -> Unity Y(m)
float z = Convert.ToSingle(pose.y / -1000.0); // Robot Y(mm) -> Unity Z(m)
return new Vector3(x, y, z);
}
private Quaternion ConvertRobotDataToQuaternion(RobotData pose)
{
float rx = pose.rx;
float ry = pose.z;
float rz = pose.y;
return Quaternion.Euler(pose.rx, -pose.rz, pose.ry);
}
public void CreatePoint(RobotData pose, int index)
{
if(pointPrefab == null)
{
Debug.Log("마커 프리팹이 할당되지 않았습니다.");
return;
}
Vector3 localPos = ConvertRobotDataToVector3(pose);
Vector3 worldPos = localPos;
//Quaternion localRot = ConvertRobotDataToQuaternion(pose);
//Vector3 toolOffset = new Vector3(0.681004044f, -0.221942063f, -0.447616011f);
//Vector3 rotatedOffset = localRot * toolOffset;
//Vector3 finalLocalPos = localPos + rotatedOffset;
Vector3 worldPos = localPos;
//Quaternion worldRot = localRot;
// 로봇 Base가 있다면, 로컬 좌표를 월드 좌표로 변환
if (robotBaseTransform != null)
{
worldPos = robotBaseTransform.TransformPoint(localPos);
//worldRot = robotBaseTransform.rotation * localRot;
}
else
{
@@ -44,6 +66,9 @@ public class PointManagerView : MonoBehaviour
// 변환된 월드 좌표에 생성
GameObject pointObj = Instantiate(pointPrefab, worldPos, Quaternion.identity, this.transform);
Debug.Log($"마커 프리팹 생성됨, 위치: {worldPos}");
//pointObj.transform.localRotation = Quaternion.identity;
activePoints.Add(pointObj);
RobotPoint pointComponent = pointObj.GetComponent<RobotPoint>();

View File

@@ -122,7 +122,7 @@ public class ProgramInfoView : MonoBehaviour
private void ShowProgramInfoPanel(InputAction.CallbackContext obj)
{
isPressingX = !isPressingX;
if (!isPressingX)
if (isPressingX)
{
infoPanel.transform.SetParent(leftControllerTransform);
infoPanel.transform.localPosition = new Vector3(0f, 0.2f, 0f);
@@ -143,7 +143,7 @@ public class ProgramInfoView : MonoBehaviour
private void PlayProgram(InputAction.CallbackContext obj)
{
isPressingAorB = !isPressingAorB;
if (!isPressingAorB)
if (isPressingAorB)
{
OnStartClicked?.Invoke();
}

View File

@@ -9,6 +9,7 @@ TagManager:
- Point
- DropZone
- Gripper
- Robot
layers:
- Default
- TransparentFX