diff --git a/Assets/HybridIK/Scripts/Main/HybridInverseKinematicsNode.cs b/Assets/HybridIK/Scripts/Main/HybridInverseKinematicsNode.cs index abe5818..595554d 100644 --- a/Assets/HybridIK/Scripts/Main/HybridInverseKinematicsNode.cs +++ b/Assets/HybridIK/Scripts/Main/HybridInverseKinematicsNode.cs @@ -122,6 +122,40 @@ public class HybridInverseKinematicsNode : MonoBehaviour return currentRotations; } + public List GetCurrentJointAxisRotations(char axis = 'z') + { + List jointAngles = new List(); + if (nodes == null || nodes.Count == 0) return jointAngles; + + foreach (HybridIKJoint node in nodes) + { + if (node.jointTransform != null) + { + // 관절의 부모 기준 로컬 회전값을 Euler 각도(0-360)로 가져옴 + Vector3 localEulerAngles = node.jointTransform.localEulerAngles; + + // 매개변수로 받은 축에 해당하는 값을 리스트에 추가 + switch (axis) + { + case 'x': + case 'X': + jointAngles.Add(localEulerAngles.x); + break; + case 'y': + case 'Y': + jointAngles.Add(localEulerAngles.y); + break; + case 'z': + case 'Z': + default: + jointAngles.Add(localEulerAngles.z); + break; + } + } + } + return jointAngles; + } + public void SetJointTargetPositions(List newPositions) { if (nodes == null || nodes.Count != newPositions.Count) @@ -152,6 +186,46 @@ public class HybridInverseKinematicsNode : MonoBehaviour } } + public void SetCurrentJointAxisRotations(List jointAngles, char axis = 'z') + { + // 노드 리스트가 없거나, 받은 각도 리스트의 개수가 일치하지 않으면 오류를 출력하고 중단 + if (nodes == null || nodes.Count == 0 || jointAngles == null || nodes.Count != jointAngles.Count) + { + Debug.LogError($"관절 개수가 맞지 않습니다. (모델: {nodes?.Count ?? 0}개, 받은 데이터: {jointAngles?.Count ?? 0}개)"); + return; + } + + for (int i = 0; i < nodes.Count; i++) + { + if (nodes[i].jointTransform != null) + { + // 현재 로컬 오일러 각도를 Vector3 변수로 가져옴 + Vector3 currentLocalEuler = nodes[i].jointTransform.localEulerAngles; + + // 매개변수로 받은 축에 해당하는 값을 Vector3 변수에서 수정 + switch (axis) + { + case 'x': + case 'X': + currentLocalEuler.x = jointAngles[i]; + break; + case 'y': + case 'Y': + currentLocalEuler.y = jointAngles[i]; + break; + case 'z': + case 'Z': + default: + currentLocalEuler.z = jointAngles[i]; + break; + } + + // 수정된 Vector3 전체를 다시 할당 + nodes[i].jointTransform.localEulerAngles = currentLocalEuler; + } + } + } + #region DebugDraw void OnDrawGizmos() { diff --git a/Assets/HybridIK/Scripts/Main/RobotController.cs b/Assets/HybridIK/Scripts/Main/RobotController.cs index 3775b75..dd5761f 100644 --- a/Assets/HybridIK/Scripts/Main/RobotController.cs +++ b/Assets/HybridIK/Scripts/Main/RobotController.cs @@ -1,9 +1,14 @@ using UnityEngine; using System.Collections; using System; +using NUnit.Framework; +using System.Collections.Generic; public class RobotController : MonoBehaviour { + [Header("IK")] + [SerializeField] private HybridInverseKinematicsNode kinematicsNode; + [Header("Motor State")] [SerializeField] private GameObject motorStatusIndicator1; [SerializeField] private GameObject motorStatusIndicator2; @@ -11,6 +16,8 @@ public class RobotController : MonoBehaviour [SerializeField] private Material indicatorMaterial1; // ⺻(ȸ) [SerializeField] private Material indicatorMaterial2; // ʷ + public event Action OnPoseUpdateRequest; + private bool isMotorOn; void Start() @@ -25,6 +32,11 @@ public class RobotController : MonoBehaviour } } + private void Update() + { + OnPoseUpdateRequest?.Invoke();// TODO. κ ƴ ϱ + } + public void SetMotorState(bool isOn) { isMotorOn = isOn; @@ -47,4 +59,22 @@ public class RobotController : MonoBehaviour } } } + + public void SetRobotPosition(RobotData robotData) // κ ġ Ʈ + { + // x, y, z, rx, ry, rz => endpoint + // j1, ..., j6 => 6 ȸ + kinematicsNode.targetTransform.localPosition = new Vector3(robotData.x, robotData.y, robotData.z); + kinematicsNode.targetTransform.localRotation = new Quaternion(robotData.rx, robotData.ry, robotData.rz, 0); + + List list_jAngle = new List(); + list_jAngle.Add(robotData.j6); + list_jAngle.Add(robotData.j5); + list_jAngle.Add(robotData.j4); + list_jAngle.Add(robotData.j3); + list_jAngle.Add(robotData.j2); + list_jAngle.Add(robotData.j1); + + kinematicsNode.SetCurrentJointAxisRotations(list_jAngle, 'x'); + } } diff --git a/Assets/Resources/Prefabs/Point_Prefab.prefab b/Assets/Resources/Prefabs/Point_Prefab.prefab new file mode 100644 index 0000000..5015301 --- /dev/null +++ b/Assets/Resources/Prefabs/Point_Prefab.prefab @@ -0,0 +1,77 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &280273810869499429 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 23272902704394604} + - component: {fileID: 8410384716740639099} + - component: {fileID: 7782236473570627370} + m_Layer: 5 + m_Name: Point_Prefab + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &23272902704394604 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 280273810869499429} + 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: [] + m_Father: {fileID: 0} + 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: 0, y: 0} + m_SizeDelta: {x: 0.2, y: 0.3} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8410384716740639099 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 280273810869499429} + m_CullTransparentMesh: 1 +--- !u!114 &7782236473570627370 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 280273810869499429} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: a4e764ee05645514cab2cf0636654f5f, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 diff --git a/Assets/Resources/Prefabs/Point_Prefab.prefab.meta b/Assets/Resources/Prefabs/Point_Prefab.prefab.meta new file mode 100644 index 0000000..cd075d9 --- /dev/null +++ b/Assets/Resources/Prefabs/Point_Prefab.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3e14dc8ccaa006641934bf740bd1e88d +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/HDRobo_Main.unity b/Assets/Scenes/HDRobo_Main.unity index b2cc403..511896d 100644 --- a/Assets/Scenes/HDRobo_Main.unity +++ b/Assets/Scenes/HDRobo_Main.unity @@ -12581,7 +12581,7 @@ Transform: m_GameObject: {fileID: 445093996} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0.174, y: 1.394, z: -1.015} + m_LocalPosition: {x: -1.27, y: 1.406, z: -0.054} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: @@ -16515,15 +16515,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalPosition.x - value: 1.521 + value: -0.14 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalPosition.y - value: 0.00000011920929 + value: 0 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalPosition.z - value: 0.52 + value: 1.66 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalRotation.w @@ -20564,7 +20564,7 @@ Transform: m_GameObject: {fileID: 722849471} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 1.443, y: 0, z: -0.961} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -25875,7 +25875,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 1} m_AnchorMax: {x: 0.5, y: 1} - m_AnchoredPosition: {x: 23.400135, y: -65} + m_AnchoredPosition: {x: 23.400135, y: -64.999985} m_SizeDelta: {x: 400, y: 50} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &906004066 @@ -36822,6 +36822,10 @@ MonoBehaviour: view: {fileID: 1243479632} tcpView: {fileID: 1456747455} robotController: {fileID: 806304512143720359} + interactionView: {fileID: 1568384462} + pointManagerView: {fileID: 1568384461} + pathLineView: {fileID: 1568384459} + popupView: {fileID: 1313589743} motorStatePollInterval: 1 --- !u!4 &1299890571 Transform: @@ -37154,6 +37158,7 @@ GameObject: - component: {fileID: 1313589740} - component: {fileID: 1313589739} - component: {fileID: 1313589738} + - component: {fileID: 1313589743} m_Layer: 5 m_Name: Canvas m_TagString: Untagged @@ -37266,6 +37271,25 @@ RectTransform: m_AnchoredPosition: {x: -0.2, y: 1.221} m_SizeDelta: {x: 0.8, y: 0.8} m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1313589743 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1313589737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9abda78e6c8fdb34a925ac2483efc48b, type: 3} + m_Name: + m_EditorClassIdentifier: + popupPanel: {fileID: 1313589737} + titleText: {fileID: 331501807} + messageText: {fileID: 827370641} + confirmButton: {fileID: 618711572} + cancelButton: {fileID: 1352558441} + option1Button: {fileID: 1755737344} + option2Button: {fileID: 196844229} --- !u!1001 &1315555405 PrefabInstance: m_ObjectHideFlags: 0 @@ -46151,6 +46175,256 @@ Transform: m_CorrespondingSourceObject: {fileID: 2525146012768536108, guid: af5398c451de74544b8b16ae846f351a, type: 3} m_PrefabInstance: {fileID: 1567594223} m_PrefabAsset: {fileID: 0} +--- !u!1 &1568384454 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1568384458} + - component: {fileID: 1568384457} + - component: {fileID: 1568384456} + - component: {fileID: 1568384455} + - component: {fileID: 1568384460} + - component: {fileID: 1568384459} + - component: {fileID: 1568384462} + - component: {fileID: 1568384461} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1568384455 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1568384456 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 1 +--- !u!223 &1568384457 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 2 + m_Camera: {fileID: 1928531731} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1568384458 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 0, z: -0.94} + m_LocalScale: {x: 0.5, y: 0.5, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4070703782762572036} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: -0.2, y: 1.221} + m_SizeDelta: {x: 0.8, y: 0.8} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1568384459 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c98a419633709d44ca6c0652dcf63a07, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!120 &1568384460 +LineRenderer: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 0 + m_LightProbeUsage: 0 + m_ReflectionProbeUsage: 0 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Positions: + - {x: 0, y: 0, z: 0} + - {x: 0, y: 0, z: 1} + m_Parameters: + serializedVersion: 3 + widthMultiplier: 1 + widthCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + colorGradient: + serializedVersion: 2 + key0: {r: 1, g: 1, b: 1, a: 1} + key1: {r: 1, g: 1, b: 1, a: 1} + key2: {r: 0, g: 0, b: 0, a: 0} + key3: {r: 0, g: 0, b: 0, a: 0} + key4: {r: 0, g: 0, b: 0, a: 0} + key5: {r: 0, g: 0, b: 0, a: 0} + key6: {r: 0, g: 0, b: 0, a: 0} + key7: {r: 0, g: 0, b: 0, a: 0} + ctime0: 0 + ctime1: 65535 + ctime2: 0 + ctime3: 0 + ctime4: 0 + ctime5: 0 + ctime6: 0 + ctime7: 0 + atime0: 0 + atime1: 65535 + atime2: 0 + atime3: 0 + atime4: 0 + atime5: 0 + atime6: 0 + atime7: 0 + m_Mode: 0 + m_ColorSpace: -1 + m_NumColorKeys: 2 + m_NumAlphaKeys: 2 + numCornerVertices: 0 + numCapVertices: 0 + alignment: 0 + textureMode: 0 + textureScale: {x: 1, y: 1} + shadowBias: 0.5 + generateLightingData: 0 + m_MaskInteraction: 0 + m_UseWorldSpace: 1 + m_Loop: 0 + m_ApplyActiveColorSpace: 1 +--- !u!114 &1568384461 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 06c8786b6262dad4a86217e213be1b96, type: 3} + m_Name: + m_EditorClassIdentifier: + pointPrefab: {fileID: 280273810869499429, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} +--- !u!114 &1568384462 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568384454} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c1b136fe9693203418aa8d9bacb7cfcf, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &1570547337 GameObject: m_ObjectHideFlags: 0 @@ -57054,15 +57328,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 9d5ec35fa0043fd488588e61261d23de, type: 3} propertyPath: m_LocalPosition.x - value: 1.451 + value: -0.21000004 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 9d5ec35fa0043fd488588e61261d23de, type: 3} propertyPath: m_LocalPosition.y - value: 0.483 + value: 0.4829999 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 9d5ec35fa0043fd488588e61261d23de, type: 3} propertyPath: m_LocalPosition.z - value: 0.562 + value: 1.7019999 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 9d5ec35fa0043fd488588e61261d23de, type: 3} propertyPath: m_LocalRotation.w @@ -60885,15 +61159,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalPosition.x - value: 1.521 + value: -0.13999999 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalPosition.y - value: 0.00000011920929 + value: 0 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalPosition.z - value: -2.71 + value: -1.57 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: 6c52fe1416d967a409332c0065a41575, type: 3} propertyPath: m_LocalRotation.w @@ -61191,7 +61465,7 @@ RectTransform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2084353331} m_LocalRotation: {x: -0, y: -0.72839886, z: -0, w: 0.68515337} - m_LocalPosition: {x: 0, y: 0, z: -0.368} + m_LocalPosition: {x: 0, y: 0, z: 0.55} m_LocalScale: {x: 0.673158, y: 0.904753, z: 1} m_ConstrainProportionsScale: 0 m_Children: @@ -61200,7 +61474,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: -93.505, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 2.676, y: 1.548} + m_AnchoredPosition: {x: 1.67, y: 1.548} m_SizeDelta: {x: 1, y: 1} m_Pivot: {x: 0.5, y: 0.5} --- !u!1001 &2085318412 @@ -62731,7 +63005,7 @@ Transform: m_GameObject: {fileID: 34692899277808924} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 1.443, y: 0, z: -0.961} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: @@ -62750,6 +63024,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c12f4ab377ddfdc46820089b240eaf27, type: 3} m_Name: m_EditorClassIdentifier: + kinematicsNode: {fileID: 722849473} motorStatusIndicator1: {fileID: 1475297475771640280} motorStatusIndicator2: {fileID: 2476781507827223150} indicatorMaterial1: {fileID: 2100000, guid: 8429ea8a04d5dd844875dc07c5f6c06b, type: 2} @@ -63190,6 +63465,108 @@ Transform: m_Children: [] m_Father: {fileID: 6476108356885335244} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &4070703782762572035 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 1568384458} + m_Modifications: + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_AnchorMax.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_AnchorMax.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_AnchorMin.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_AnchorMin.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_SizeDelta.x + value: 0.2 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_SizeDelta.y + value: 0.3 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 280273810869499429, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + propertyPath: m_Name + value: Point_Prefab + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} +--- !u!224 &4070703782762572036 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 23272902704394604, guid: 3e14dc8ccaa006641934bf740bd1e88d, type: 3} + m_PrefabInstance: {fileID: 4070703782762572035} + m_PrefabAsset: {fileID: 0} --- !u!23 &4421136600761446311 MeshRenderer: m_ObjectHideFlags: 0 @@ -63960,6 +64337,7 @@ SceneRoots: - {fileID: 1814192178} - {fileID: 2084353335} - {fileID: 1313589742} + - {fileID: 1568384458} - {fileID: 1299890571} - {fileID: 580519499} - {fileID: 2077727516} diff --git a/Assets/Scripts/AppManager.cs b/Assets/Scripts/AppManager.cs index c711236..91a6c6e 100644 --- a/Assets/Scripts/AppManager.cs +++ b/Assets/Scripts/AppManager.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Threading; using UnityEngine; @@ -9,12 +10,19 @@ public class AppManager : MonoBehaviour [SerializeField] private ProgramView view; [SerializeField] private TCPView tcpView; [SerializeField] private RobotController robotController; + + [SerializeField] private InteractionView interactionView; + [SerializeField] private PointManagerView pointManagerView; + [SerializeField] private PathLineView pathLineView; + [SerializeField] private PopupView popupView; + [SerializeField] private float motorStatePollInterval = 1.0f; - ProgramPresenter presenter; + private ProgramPresenter presenter; private string hostip; private int tcpPort; private int udpPort; private string configFileName = "config.cfg"; + private CancellationToken cancellationToken; async void Start() { @@ -22,15 +30,13 @@ public class AppManager : MonoBehaviour ProgramModel model = new ProgramModel(hostip, tcpPort, udpPort); await model.InitializeAsync(); - _ = model.GetTCPAsync(); + _ = model.GetTCPAsync(cancellationToken); - presenter = new ProgramPresenter(model, view, tcpView); + presenter = new ProgramPresenter(model, view, tcpView, interactionView, pointManagerView, popupView, pathLineView); presenter.RegisterControlledRobot(robotController); await presenter.UpdateMotorStateAsync(); - view.DisplayProgram(null); - StartCoroutine(PollMotorStateCoroutine()); } diff --git a/Assets/Scripts/Model/ProgramModel.cs b/Assets/Scripts/Model/ProgramModel.cs index 470b2ec..eed99eb 100644 --- a/Assets/Scripts/Model/ProgramModel.cs +++ b/Assets/Scripts/Model/ProgramModel.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq; using Palmmedia.ReportGenerator.Core; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Net.Sockets; @@ -22,6 +23,7 @@ public class ProgramModel : IProgramModel private string tcpBaseUrl; private string udpBaseUrl; HttpClient httpClient = new HttpClient(); + private SingleTcpClient tcpClient; private SingleUdpClient udpClientForHttp; public UdpClientManager manager = new UdpClientManager(); @@ -32,8 +34,10 @@ public class ProgramModel : IProgramModel private readonly object lockObject = new object(); private bool hasNewData; - - public bool isUdpLoopRunning = false; + public bool IsUdpLoopRunning = false; + public bool IsMoving; + public CancellationTokenSource cancellationTokenSource; + private Vector3 startMovementPosition; public ProgramModel(string hostip, int tcpPort, int udpPort) { @@ -46,7 +50,8 @@ public class ProgramModel : IProgramModel { await LoadAllPrograms(); hasNewData = false; - isUdpLoopRunning = true; + IsUdpLoopRunning = true; + IsMoving = false; return; } @@ -63,10 +68,14 @@ public class ProgramModel : IProgramModel lock (lockObject) { hasNewData = false; // ͸ оǷ ÷׸ - return robotData; // ( 纻 ȯϴ ) + return robotData; } } + /// + /// α׷ /ҷ ý + /// + public async Task CheckProgramExists(string jobProgramName) { string requestUri = $"{tcpBaseUrl}/file_manager/file_exist?pathname=project/jobs/{jobProgramName}"; @@ -171,9 +180,9 @@ public class ProgramModel : IProgramModel } } - public async Task GetTCPAsync() + public async Task GetTCPAsync(CancellationToken token) { - while (isUdpLoopRunning) + while (IsUdpLoopRunning) { try { @@ -260,8 +269,148 @@ public class ProgramModel : IProgramModel return ids; } + /// + /// κ ġ ý + /// + public async Task SavePointToProgramAsync(RobotData pointData, int index = -1) + { + if (CurrentProgram == null) + { + Debug.LogError(" α׷ ε ʾҽϴ."); + return false; + } + + // DTO( ü) + // ( RobotData index, α׷ ID  ޴ ʿ) + var payload = new + { + programId = CurrentProgram.ProgramId, + indexToUpdate = index, // -1̸ Ʈ, 0 ̸̻ ش ε + pose = pointData + }; + string jsonPayload = JsonConvert.SerializeObject(payload); + HttpContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + string requestUri = (index == -1) + ? $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/ins_cmd_line" + : $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/pose_modify"; + + try + { + HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); + if (result.IsSuccessStatusCode) + { + // ϸ, ޸(CurrentProgram) ݿ + if (index == -1) + CurrentProgram.AddStep(pointData); // RobotProgram RobotMoveStep ߰ + else + CurrentProgram.UpdateStep(index, pointData); // RobotProgram ش + + return true; + } + return false; + } + catch (Exception e) + { + Debug.LogError($"Ʈ : {e.Message}"); + return false; + } + } + + // Ʈ û + public async Task DeletePointFromProgramAsync(int index) + { + if (CurrentProgram == null) return false; + + var payload = new { programId = CurrentProgram.ProgramId, indexToDelete = index }; + string jsonPayload = JsonConvert.SerializeObject(payload); + HttpContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + string requestUri = $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/del_cmd_line"; + + try + { + HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); + if (result.IsSuccessStatusCode) + { + // , ޸(CurrentProgram) + CurrentProgram.DeleteStep(index); + return true; + } + return false; + } + catch (Exception e) + { + Debug.LogError($"Ʈ : {e.Message}"); + return false; + } + } + + // ǽð κ TCP ̵ + public async Task StreamPoseToRobotUdpAsync(RobotData pose) + { + try + { + byte[] udpPacket = ConvertPoseToPacket(pose); + + await udpClientForHttp.SendBytesAsync(udpPacket); + } + catch (Exception e) + { + Debug.LogWarning($"UDP Ʈ : {e.Message}"); + } + } + + private byte[] ConvertPoseToPacket(RobotData pose) + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(pose.x); + writer.Write(pose.y); + writer.Write(pose.z); + writer.Write(pose.rx); + writer.Write(pose.ry); + writer.Write(pose.rz); + } + return stream.ToArray(); + } + } + + // tcp 巡 ̵ ϴ + public async Task StartMovement(Vector3 position) + { + startMovementPosition.x = Convert.ToSingle(Math.Round(-1 * position.x * 1000, 2)); + startMovementPosition.y = Convert.ToSingle(Math.Round(-1 * position.z * 1000, 2)); + startMovementPosition.z = Convert.ToSingle(Math.Round(position.y * 1000, 2)); + var jsonResponse = await tcpClient.SendPostRequestAsync("/project/robot/move_to_pose_manual", $"{{\"pose_tg\":{{\"crd\":\"robot\",\"_type\":\"Pose\",\"mechinfo\":1,\"x\":{startMovementPosition.x},\"y\":{startMovementPosition.y},\"z\":{startMovementPosition.z}, \"rx\":{robotData.rx}, \"ry\":{robotData.ry}, \"rz\":{robotData.rz}}}}}"); + return jsonResponse.Contains("200"); + } + + //Ÿ ޱ ̵ + private async Task MovementLoopAsync() + { + while (!cancellationTokenSource.Token.IsCancellationRequested) + { + if (IsMoving) + { + await udpClientForHttp.SendFilledBytesAsync(new Dictionary { { 2, 0x20 } }); + await Task.Delay(100); + bool isApproximatelyX = Mathf.Approximately(startMovementPosition.x, Convert.ToSingle(Math.Round(robotData.x, 2))); + bool isApproximatelyY = Mathf.Approximately(startMovementPosition.y, Convert.ToSingle(Math.Round(robotData.y, 2))); + bool isApproximatelyZ = Mathf.Approximately(startMovementPosition.z, Convert.ToSingle(Math.Round(robotData.z, 2))); + if (isApproximatelyX && isApproximatelyY && isApproximatelyZ) + { + IsMoving = false; + } + } + + } + } + void OnDestroy() { - isUdpLoopRunning = false; + IsUdpLoopRunning = false; } } \ No newline at end of file diff --git a/Assets/Scripts/Model/RobotProgram.cs b/Assets/Scripts/Model/RobotProgram.cs index 0a9356d..0964a64 100644 --- a/Assets/Scripts/Model/RobotProgram.cs +++ b/Assets/Scripts/Model/RobotProgram.cs @@ -17,10 +17,10 @@ public class RobotProgram private void ParseJobContent(string rawText) { - // 1. Ľ (: "Robot Job File; { version: 2.0, ... }") + // Ľ (: "Robot Job File; { version: 2.0, ... }") // Խ̳ Substring version, mech_type - // 2. (S1, S2...) Ľ + // (S1, S2...) Ľ string[] lines = rawText.Split('\n'); foreach (string line in lines) { @@ -45,4 +45,30 @@ public class RobotProgram } return string.Empty; } + + public void AddStep(RobotData data) + { + + } + + public void UpdateStep(int index, RobotData data) + { + + } + + public void DeleteStep(int index) + { + + } + + public RobotData GetStepPose(int index) + { + RobotData data = null; + return data; + } + + public void GetAllStepPoses() + { + + } } \ No newline at end of file diff --git a/Assets/Scripts/Presenter/ProgramPresenter.cs b/Assets/Scripts/Presenter/ProgramPresenter.cs index adfd39b..dd72aaa 100644 --- a/Assets/Scripts/Presenter/ProgramPresenter.cs +++ b/Assets/Scripts/Presenter/ProgramPresenter.cs @@ -1,8 +1,18 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using UnityEngine.XR.ARSubsystems; +public enum PopupState +{ + None, + ConfirmAddPoint, + ConfirmModifyPoint, + MoveOrDelete, + ConfirmDelete +} + public class ProgramPresenter { private ProgramModel model; @@ -12,7 +22,19 @@ public class ProgramPresenter private string _programId; private bool lastKnownMotorState = false; - public ProgramPresenter(ProgramModel model, IProgramView view, TCPView tcpView) + private IInteractionView interactionView; + private IPointManagerView pointManagerView; + private IPathLineView pathLineView; + private IPopupView popupView; + + private PopupState currentPopupState = PopupState.None; + private RobotData pendingPointData; // ˾â ٸ ӽ + private int activePointIndex = -1; // / Ʈ ε + + private bool IsDragging = false; + + public ProgramPresenter(ProgramModel model, IProgramView view, TCPView tcpView, + IInteractionView interactionView, IPointManagerView pmView, IPopupView popView, IPathLineView pathLineView) { this.model = model; this.view = view; @@ -25,11 +47,20 @@ public class ProgramPresenter this.view.OnSaveClicked += HandleSaveProgram; this.view.OnAddPointClicked += HandleAddPoint; this.tcpView.OnTCPupdateRequested += HandleTCPViewUpdate; + + //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 += HandlePoseViewUpdate; } public async Task UpdateMotorStateAsync() @@ -62,7 +93,6 @@ public class ProgramPresenter } } - private async Task HandleCreateProgram(string programId) { if (await model.CreateNewProgram(programId)) @@ -126,6 +156,143 @@ public class ProgramPresenter tcpView.SetCoordinates(data); } } + + // --- ǽð ȭ --- + private void HandlePoseViewUpdate() + { + RobotData data = model.GetLatestRobotData(); + controlledRobot.SetRobotPosition(data); // 3D κ ġ Ʈ + } + + // --- Ʈ ߰ --- + private void HandleRobotReleased(RobotData pose) + { + pendingPointData = pose; // 1. ӽ + currentPopupState = PopupState.ConfirmAddPoint; // 2. + popupView.ShowConfirmPopup("ġ Ȯ", " ġ Ʈ Ͻðڽϱ?"); // 3. ˾ û + } + + // --- Ʈ Ŭ --- + private void HandlePointClicked(int index) + { + activePointIndex = index; // ε + currentPopupState = PopupState.MoveOrDelete; // + popupView.ShowOptionPopup("Ʈ ۾", " Ͻðڽϱ?", " ̵", ""); // ˾ û + } + + // --- Ʈ 巡 --- + private RobotData originalDragPose; // ư ġ + + private void HandlePointDragStart(int index) + { + IsDragging = true; + activePointIndex = index; + originalDragPose = model.CurrentProgram.GetStepPose(index); + + //interactionView.ShowDragArrow(GetPositionFromPose(originalDragPose)); + interactionView.ShowGhostRobot(originalDragPose); + } + + private async void HandlePointDragUpdate(int index, Vector3 newWorldPos) + { + if (!IsDragging) return; + //RobotData newPose = ConvertVectorToRobotData(newWorldPos); + + // Ʈ κ, Ʈ, ǽð ̵ + //interactionView.ShowGhostRobot(newPose); + //pointManagerView.UpdatePointPosition(index, newPose); + //pathLineView.DrawPath(GetFullPathOfProgramWithTempChange(index, newPose)); // ӽ ׸ + + //await model.StreamPoseToRobotUdpAsync(newPose); + } + + private void HandlePointDragEnd(int index) + { + //IsDragging = false; + //interactionView.HideDragArrow(); + //interactionView.HideGhostRobot(); + + //// ( κ ġ ̵ - 5ܰ) + //// robotController.SetRobotPosition(newPose); + + //pendingPointData = ConvertVectorToRobotData(GetLastDragPosition()); // ӽ + //currentPopupState = PopupState.ConfirmModifyPoint; // + //popupView.ShowConfirmPopup("ġ ", " ġ Ʈ Ͻðڽϱ?"); // ˾ û + } + + + // --- ˾ ڵ鷯 --- + private async void HandlePopupResponse(PopupResponse response) + { + popupView.HidePopup(); + + switch (currentPopupState) + { + case PopupState.ConfirmAddPoint: + if (response == PopupResponse.Confirm) // Ȯ + { + await model.SavePointToProgramAsync(pendingPointData); + RedrawSceneFromModel(); // + } + break; + + // 巡 Ȯ/ + case PopupState.ConfirmModifyPoint: + if (response == PopupResponse.Confirm) // Ȯ + { + // ġ + // 巡װ ġ α׷ + await model.SavePointToProgramAsync(pendingPointData, activePointIndex); + RedrawSceneFromModel(); + } + else // + { + pointManagerView.UpdatePointPosition(activePointIndex, originalDragPose); // ġ + RedrawSceneFromModel(); // ġ + } + break; + + // ̵/ + case PopupState.MoveOrDelete: + if (response == PopupResponse.Option1) // ̵ + { + RobotData targetPose = model.CurrentProgram.GetStepPose(activePointIndex); + //await model.StartMovement(targetPose); + } + else if (response == PopupResponse.Option2) // + { + currentPopupState = PopupState.ConfirmDelete; + popupView.ShowConfirmPopup(" Ȯ", " Ʈ Ͻðڽϱ?"); + } + break; + + // + case PopupState.ConfirmDelete: + if (response == PopupResponse.Confirm) + { + await model.DeletePointFromProgramAsync(activePointIndex); + RedrawSceneFromModel(); + } + break; + } + + // ۾ ʱȭ + currentPopupState = PopupState.None; + activePointIndex = -1; + } + + // Model ¸ о View(Ʈ, ) ħ + private void RedrawSceneFromModel() + { + //if (model.CurrentProgram == null) return; + + //// (RobotProgram.Steps (List) List ȯϴ ) + //List poses = model.CurrentProgram.GetAllStepPoses(); + + //pointManagerView.RedrawPoints(poses); // Ʈ ٽ ׸ + //pathLineView.DrawPath(poses); // ٽ ׸ + } + private void Destroy() { this.view.OnCreateProgramClicked -= async (id) => await HandleCreateProgram(id); diff --git a/Assets/Scripts/View/InteractionView.cs b/Assets/Scripts/View/InteractionView.cs new file mode 100644 index 0000000..e8a086d --- /dev/null +++ b/Assets/Scripts/View/InteractionView.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine; + +// Presenter InteractionView ϱ ̽ +public interface IInteractionView +{ + // VR Ʈѷ κ Ҵ ߻ + event Action OnRobotReleased; + // VR Ʈѷ Ư Ʈ Ŭ ߻ + event Action OnPointClicked; + // VR Ʈѷ Ʈ 巡 // ߻ + event Action OnPointDragStart; + event Action OnPointDragUpdate; // (Ʈ ε, ǥ) + event Action OnPointDragEnd; + + // Presenter ȣ Լ + void ShowGhostRobot(RobotData pose); + void HideGhostRobot(); + void ShowDragArrow(Vector3 position); + void HideDragArrow(); +} + +// ( ũƮ VR Ʈѷ ִ پ մϴ) +public class InteractionView : MonoBehaviour, IInteractionView +{ + public event Action OnRobotReleased; + public event Action OnPointClicked; + public event Action OnPointDragStart; + public event Action OnPointDragUpdate; + public event Action OnPointDragEnd; + + void Update() + { + // Ʈѷ κ ִ + // if (IsGrabbingRobot()) { ... } + + // Ʈѷ ׷ ư + // if (OnGrabRelease()) + // { + // RobotData currentPose = GetCurrentRobotPose(); + // OnRobotReleased?.Invoke(currentPose); // 2. "ġ Ȯ?" ˾ û + // } + + // Ʈѷ Ʈ Ŭ + // if (OnPointClick(out int clickedPointIndex)) + // { + // OnPointClicked?.Invoke(clickedPointIndex); // 8. "̵/?" ˾ û + // } + + // Ʈѷ Ʈ (巡 ) + // if (OnPointHold(out int draggedPointIndex)) + // { + // OnPointDragStart?.Invoke(draggedPointIndex); // 5. 巡 + // } + } + + // --- Presenter ȣ Լ --- + public void ShowGhostRobot(RobotData pose) { /* κ2 Ȱȭ ġ */ } + public void HideGhostRobot() { /* κ2 Ȱȭ */ } + public void ShowDragArrow(Vector3 position) { /* 巡׿ ȭǥ UI Ȱȭ ġ */ } + public void HideDragArrow() { /* 巡׿ ȭǥ UI Ȱȭ */ } +} diff --git a/Assets/Scripts/View/InteractionView.cs.meta b/Assets/Scripts/View/InteractionView.cs.meta new file mode 100644 index 0000000..7d3c618 --- /dev/null +++ b/Assets/Scripts/View/InteractionView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c1b136fe9693203418aa8d9bacb7cfcf \ No newline at end of file diff --git a/Assets/Scripts/View/PathLineView.cs b/Assets/Scripts/View/PathLineView.cs new file mode 100644 index 0000000..545a05e --- /dev/null +++ b/Assets/Scripts/View/PathLineView.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using UnityEngine; + +// ̽ +public interface IPathLineView +{ + void DrawPath(List poses); // (RobotProgram.Steps ȯ) +} + +[RequireComponent(typeof(LineRenderer))] +public class PathLineView : MonoBehaviour, IPathLineView +{ + private LineRenderer lineRenderer; + + void Awake() + { + lineRenderer = GetComponent(); + lineRenderer.positionCount = 0; + } + + public void DrawPath(List poses) + { + if (poses == null || poses.Count < 2) + { + lineRenderer.positionCount = 0; + return; + } + + lineRenderer.positionCount = poses.Count; + for (int i = 0; i < poses.Count; i++) + { + lineRenderer.SetPosition(i, new Vector3(poses[i].x, poses[i].y, poses[i].z)); + } + } +} diff --git a/Assets/Scripts/View/PathLineView.cs.meta b/Assets/Scripts/View/PathLineView.cs.meta new file mode 100644 index 0000000..b64b2ed --- /dev/null +++ b/Assets/Scripts/View/PathLineView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c98a419633709d44ca6c0652dcf63a07 \ No newline at end of file diff --git a/Assets/Scripts/View/PointManagerView.cs b/Assets/Scripts/View/PointManagerView.cs new file mode 100644 index 0000000..140af26 --- /dev/null +++ b/Assets/Scripts/View/PointManagerView.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using UnityEngine; + +// ̽ +public interface IPointManagerView +{ + void CreatePoint(RobotData pose); + void UpdatePointPosition(int index, RobotData pose); + void DeletePoint(int index); + void RedrawPoints(List poses); // (RobotProgram.Steps ȯ) +} + +public class PointManagerView : MonoBehaviour, IPointManagerView +{ + [SerializeField] private GameObject pointPrefab; // νͿ Ʈ + private List activePoints = new List(); + + public void CreatePoint(RobotData pose) + { + Vector3 position = new Vector3(pose.x, pose.y, pose.z); + GameObject pointObj = Instantiate(pointPrefab, position, Quaternion.identity, this.transform); + activePoints.Add(pointObj); + // (: pointObj 'InteractionView' ִ ݶ̴ ũƮ ־ ) + } + + public void UpdatePointPosition(int index, RobotData pose) + { + if (index < 0 || index >= activePoints.Count) return; + activePoints[index].transform.position = new Vector3(pose.x, pose.y, pose.z); + } + + public void DeletePoint(int index) + { + if (index < 0 || index >= activePoints.Count) return; + Destroy(activePoints[index]); + activePoints.RemoveAt(index); + } + + public void RedrawPoints(List poses) + { + // Ʈ + foreach (var point in activePoints) Destroy(point); + activePoints.Clear(); + // Ʈ + foreach (var pose in poses) CreatePoint(pose); + } +} diff --git a/Assets/Scripts/View/PointManagerView.cs.meta b/Assets/Scripts/View/PointManagerView.cs.meta new file mode 100644 index 0000000..c98cc5c --- /dev/null +++ b/Assets/Scripts/View/PointManagerView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 06c8786b6262dad4a86217e213be1b96 \ No newline at end of file diff --git a/Assets/Scripts/View/PopupView.cs b/Assets/Scripts/View/PopupView.cs new file mode 100644 index 0000000..01743ec --- /dev/null +++ b/Assets/Scripts/View/PopupView.cs @@ -0,0 +1,76 @@ +using System; +using UnityEngine; +using UnityEngine.UI; +using TMPro; + +// ˾ Ÿ +public enum PopupResponse +{ + Confirm, // Ȯ + Cancel, // + Option1, // (: ̵) + Option2 // (: ) +} + +public interface IPopupView +{ + event Action OnPopupResponse; + void ShowConfirmPopup(string title, string message); // 2, 5ܰ (Ȯ/) + void ShowOptionPopup(string title, string message, string opt1Text, string opt2Text); // 8ܰ (̵/) + void HidePopup(); +} + +public class PopupView : MonoBehaviour, IPopupView +{ + public event Action OnPopupResponse; + + [SerializeField] private GameObject popupPanel; + [SerializeField] private TextMeshProUGUI titleText; + [SerializeField] private TextMeshProUGUI messageText; + + [SerializeField] private Button confirmButton; // 'Ȯ' ư + [SerializeField] private Button cancelButton; // '' ư + [SerializeField] private Button option1Button; // 'ɼ1(̵)' ư + [SerializeField] private Button option2Button; // 'ɼ2()' ư + + void Start() + { + // ư ŬǸ Presenter ̺Ʈ + confirmButton.onClick.AddListener(() => OnPopupResponse?.Invoke(PopupResponse.Confirm)); + cancelButton.onClick.AddListener(() => OnPopupResponse?.Invoke(PopupResponse.Cancel)); + option1Button.onClick.AddListener(() => OnPopupResponse?.Invoke(PopupResponse.Option1)); + option2Button.onClick.AddListener(() => OnPopupResponse?.Invoke(PopupResponse.Option2)); + + popupPanel.SetActive(false); + } + + public void ShowConfirmPopup(string title, string message) + { + titleText.text = title; + messageText.text = message; + confirmButton.gameObject.SetActive(true); + cancelButton.gameObject.SetActive(true); + option1Button.gameObject.SetActive(false); + option2Button.gameObject.SetActive(false); + popupPanel.SetActive(true); + } + + public void ShowOptionPopup(string title, string message, string opt1Text, string opt2Text) + { + titleText.text = title; + messageText.text = message; + option1Button.GetComponentInChildren().text = opt1Text; + option2Button.GetComponentInChildren().text = opt2Text; + + confirmButton.gameObject.SetActive(false); + cancelButton.gameObject.SetActive(true); // ư + option1Button.gameObject.SetActive(true); + option2Button.gameObject.SetActive(true); + popupPanel.SetActive(true); + } + + public void HidePopup() + { + popupPanel.SetActive(false); + } +} diff --git a/Assets/Scripts/View/PopupView.cs.meta b/Assets/Scripts/View/PopupView.cs.meta new file mode 100644 index 0000000..b7d1ccd --- /dev/null +++ b/Assets/Scripts/View/PopupView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9abda78e6c8fdb34a925ac2483efc48b \ No newline at end of file