mirror of
https://github.com/BotChain-Robots/ui.git
synced 2026-07-08 15:07:22 +02:00
Implemented new discover flow and implemented unit test for it
Made-with: Cursor
This commit is contained in:
@@ -5,14 +5,43 @@ public class DiscoverOverlayController : MonoBehaviour
|
|||||||
{
|
{
|
||||||
public GameObject overlayPanel;
|
public GameObject overlayPanel;
|
||||||
public RectTransform overlayContent;
|
public RectTransform overlayContent;
|
||||||
|
public TopologyBuilder topologyBuilder;
|
||||||
|
|
||||||
public void ShowOverlay()
|
public void ShowOverlay()
|
||||||
{
|
{
|
||||||
if (overlayPanel != null)
|
if (overlayPanel == null) return;
|
||||||
{
|
|
||||||
EnsureScrollRect();
|
EnsureScrollRect();
|
||||||
overlayPanel.SetActive(true);
|
ClearContent();
|
||||||
|
|
||||||
|
if (topologyBuilder == null)
|
||||||
|
topologyBuilder = FindObjectOfType<TopologyBuilder>();
|
||||||
|
|
||||||
|
bool useMock = topologyBuilder != null && topologyBuilder.mockControlLibrary;
|
||||||
|
|
||||||
|
if (useMock)
|
||||||
|
{
|
||||||
|
PopulateMockLeaders();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int[] leaders = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
leaders = ControlLibrary.getRobotLeaders();
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[Discover] getRobotLeaders failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leaders != null && leaders.Length > 0)
|
||||||
|
PopulateLeaders(leaders);
|
||||||
|
else
|
||||||
|
CreateLabel("No leaders found. Is the hardware connected?");
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayPanel.SetActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HideOverlay()
|
public void HideOverlay()
|
||||||
@@ -22,6 +51,195 @@ public class DiscoverOverlayController : MonoBehaviour
|
|||||||
overlayPanel.SetActive(false);
|
overlayPanel.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PopulateLeaders(int[] leaderIds)
|
||||||
|
{
|
||||||
|
float buttonHeight = 60f;
|
||||||
|
float spacing = 10f;
|
||||||
|
Font font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||||||
|
|
||||||
|
for (int i = 0; i < leaderIds.Length; i++)
|
||||||
|
{
|
||||||
|
int leaderId = leaderIds[i];
|
||||||
|
|
||||||
|
GameObject btnGo = new GameObject($"LeaderBtn_{leaderId}", typeof(RectTransform));
|
||||||
|
btnGo.transform.SetParent(overlayContent, false);
|
||||||
|
|
||||||
|
RectTransform rt = btnGo.GetComponent<RectTransform>();
|
||||||
|
rt.anchorMin = new Vector2(0.05f, 1f);
|
||||||
|
rt.anchorMax = new Vector2(0.95f, 1f);
|
||||||
|
rt.pivot = new Vector2(0.5f, 1f);
|
||||||
|
rt.anchoredPosition = new Vector2(0f, -(spacing + i * (buttonHeight + spacing)));
|
||||||
|
rt.sizeDelta = new Vector2(0f, buttonHeight);
|
||||||
|
|
||||||
|
Image bg = btnGo.AddComponent<Image>();
|
||||||
|
bg.color = new Color(0.2f, 0.2f, 0.2f, 1f);
|
||||||
|
|
||||||
|
Button btn = btnGo.AddComponent<Button>();
|
||||||
|
var colors = btn.colors;
|
||||||
|
colors.highlightedColor = new Color(0.35f, 0.35f, 0.35f, 1f);
|
||||||
|
colors.pressedColor = new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||||
|
btn.colors = colors;
|
||||||
|
|
||||||
|
GameObject txtGo = new GameObject("Text", typeof(RectTransform));
|
||||||
|
txtGo.transform.SetParent(btnGo.transform, false);
|
||||||
|
RectTransform txtRt = txtGo.GetComponent<RectTransform>();
|
||||||
|
txtRt.anchorMin = Vector2.zero;
|
||||||
|
txtRt.anchorMax = Vector2.one;
|
||||||
|
txtRt.offsetMin = Vector2.zero;
|
||||||
|
txtRt.offsetMax = Vector2.zero;
|
||||||
|
|
||||||
|
Text txt = txtGo.AddComponent<Text>();
|
||||||
|
txt.text = $"Leader {leaderId}";
|
||||||
|
txt.font = font;
|
||||||
|
txt.fontSize = 22;
|
||||||
|
txt.alignment = TextAnchor.MiddleCenter;
|
||||||
|
txt.color = Color.white;
|
||||||
|
|
||||||
|
btn.onClick.AddListener(() => OnLeaderSelected(leaderId));
|
||||||
|
}
|
||||||
|
|
||||||
|
float totalHeight = leaderIds.Length * (buttonHeight + spacing) + spacing;
|
||||||
|
overlayContent.sizeDelta = new Vector2(overlayContent.sizeDelta.x, totalHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopulateMockLeaders()
|
||||||
|
{
|
||||||
|
float buttonHeight = 60f;
|
||||||
|
float spacing = 10f;
|
||||||
|
Font font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (var kvp in TopologyBuilder.MockLeaders)
|
||||||
|
{
|
||||||
|
int id = kvp.Key;
|
||||||
|
string jsonName = kvp.Value;
|
||||||
|
int idx = i;
|
||||||
|
|
||||||
|
GameObject btnGo = new GameObject($"MockLeaderBtn_{id}", typeof(RectTransform));
|
||||||
|
btnGo.transform.SetParent(overlayContent, false);
|
||||||
|
|
||||||
|
RectTransform rt = btnGo.GetComponent<RectTransform>();
|
||||||
|
rt.anchorMin = new Vector2(0.05f, 1f);
|
||||||
|
rt.anchorMax = new Vector2(0.95f, 1f);
|
||||||
|
rt.pivot = new Vector2(0.5f, 1f);
|
||||||
|
rt.anchoredPosition = new Vector2(0f, -(spacing + idx * (buttonHeight + spacing)));
|
||||||
|
rt.sizeDelta = new Vector2(0f, buttonHeight);
|
||||||
|
|
||||||
|
Image bg = btnGo.AddComponent<Image>();
|
||||||
|
bg.color = new Color(0.2f, 0.2f, 0.2f, 1f);
|
||||||
|
|
||||||
|
Button btn = btnGo.AddComponent<Button>();
|
||||||
|
var colors = btn.colors;
|
||||||
|
colors.highlightedColor = new Color(0.35f, 0.35f, 0.35f, 1f);
|
||||||
|
colors.pressedColor = new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||||
|
btn.colors = colors;
|
||||||
|
|
||||||
|
GameObject txtGo = new GameObject("Text", typeof(RectTransform));
|
||||||
|
txtGo.transform.SetParent(btnGo.transform, false);
|
||||||
|
RectTransform txtRt = txtGo.GetComponent<RectTransform>();
|
||||||
|
txtRt.anchorMin = Vector2.zero;
|
||||||
|
txtRt.anchorMax = Vector2.one;
|
||||||
|
txtRt.offsetMin = Vector2.zero;
|
||||||
|
txtRt.offsetMax = Vector2.zero;
|
||||||
|
|
||||||
|
Text txt = txtGo.AddComponent<Text>();
|
||||||
|
txt.text = $"Mock Leader {id} ({jsonName})";
|
||||||
|
txt.font = font;
|
||||||
|
txt.fontSize = 22;
|
||||||
|
txt.alignment = TextAnchor.MiddleCenter;
|
||||||
|
txt.color = Color.white;
|
||||||
|
|
||||||
|
string capturedJsonName = jsonName;
|
||||||
|
btn.onClick.AddListener(() => OnMockLeaderSelected(capturedJsonName));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
float totalHeight = TopologyBuilder.MockLeaders.Count * (buttonHeight + spacing) + spacing;
|
||||||
|
overlayContent.sizeDelta = new Vector2(overlayContent.sizeDelta.x, totalHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnMockLeaderSelected(string jsonFileName)
|
||||||
|
{
|
||||||
|
Debug.Log($"[Discover] Mock leader selected, loading {jsonFileName}...");
|
||||||
|
|
||||||
|
if (topologyBuilder == null)
|
||||||
|
topologyBuilder = FindObjectOfType<TopologyBuilder>();
|
||||||
|
|
||||||
|
if (topologyBuilder == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[Discover] No TopologyBuilder found in scene!");
|
||||||
|
HideOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
topologyBuilder.BuildTopologyFromJsonFile(jsonFileName);
|
||||||
|
Debug.Log($"[Discover] Mock topology built from {jsonFileName}");
|
||||||
|
|
||||||
|
var cam = FindObjectOfType<UserCameraControl>();
|
||||||
|
if (cam != null) cam.FindModuleStructure();
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[Discover] Failed to build mock topology: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
HideOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnLeaderSelected(int leaderId)
|
||||||
|
{
|
||||||
|
Debug.Log($"[Discover] Selected leader {leaderId}, building topology...");
|
||||||
|
|
||||||
|
if (topologyBuilder == null)
|
||||||
|
{
|
||||||
|
topologyBuilder = FindObjectOfType<TopologyBuilder>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topologyBuilder == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[Discover] No TopologyBuilder found in scene!");
|
||||||
|
HideOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
topologyBuilder.BuildTopologyFromLeader(leaderId);
|
||||||
|
Debug.Log($"[Discover] Topology built successfully for leader {leaderId}");
|
||||||
|
|
||||||
|
var cam = FindObjectOfType<UserCameraControl>();
|
||||||
|
if (cam != null) cam.FindModuleStructure();
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[Discover] Failed to build topology for leader {leaderId}: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
HideOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateLabel(string message)
|
||||||
|
{
|
||||||
|
Font font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||||||
|
GameObject go = new GameObject("InfoLabel", typeof(RectTransform));
|
||||||
|
go.transform.SetParent(overlayContent, false);
|
||||||
|
RectTransform rt = go.GetComponent<RectTransform>();
|
||||||
|
rt.anchorMin = new Vector2(0.05f, 1f);
|
||||||
|
rt.anchorMax = new Vector2(0.95f, 1f);
|
||||||
|
rt.pivot = new Vector2(0.5f, 1f);
|
||||||
|
rt.anchoredPosition = new Vector2(0f, -20f);
|
||||||
|
rt.sizeDelta = new Vector2(0f, 50f);
|
||||||
|
|
||||||
|
Text txt = go.AddComponent<Text>();
|
||||||
|
txt.text = message;
|
||||||
|
txt.font = font;
|
||||||
|
txt.fontSize = 20;
|
||||||
|
txt.alignment = TextAnchor.MiddleCenter;
|
||||||
|
txt.color = new Color(1f, 0.6f, 0.6f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
void EnsureScrollRect()
|
void EnsureScrollRect()
|
||||||
{
|
{
|
||||||
if (overlayContent == null) return;
|
if (overlayContent == null) return;
|
||||||
|
|||||||
@@ -19,16 +19,7 @@ public class DiscoveryButton : MonoBehaviour
|
|||||||
|
|
||||||
public void OnDiscoveryPressed()
|
public void OnDiscoveryPressed()
|
||||||
{
|
{
|
||||||
if (topologyBuilder != null)
|
Debug.Log("Discovery button pressed – topology building deferred to overlay selection.");
|
||||||
{
|
|
||||||
Debug.Log("Discovery button pressed.");
|
|
||||||
topologyBuilder.BuildTopologyFromJson();
|
|
||||||
FocusCameraOnTopology();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError("TopologyBuilder is not assigned!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FocusCameraOnTopology()
|
void FocusCameraOnTopology()
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ public class TopologyBuilder : MonoBehaviour
|
|||||||
[Tooltip("JSON file name (without .json) in Resources folder. e.g. 'mockData' or 'mockDataSimple'")]
|
[Tooltip("JSON file name (without .json) in Resources folder. e.g. 'mockData' or 'mockDataSimple'")]
|
||||||
public string jsonFileName = "mockDataNewConfig";
|
public string jsonFileName = "mockDataNewConfig";
|
||||||
|
|
||||||
|
[Header("Mock Control Library")]
|
||||||
|
[Tooltip("When true, Discover overlay shows mock leaders backed by JSON files instead of calling the real native library.")]
|
||||||
|
public bool mockControlLibrary = false;
|
||||||
|
|
||||||
public static Dictionary<string, GameObject> idToInstance = new();
|
public static Dictionary<string, GameObject> idToInstance = new();
|
||||||
public static bool _skipControlLibraryCalls = true;
|
public static bool _skipControlLibraryCalls = true;
|
||||||
|
|
||||||
@@ -126,6 +130,96 @@ public class TopologyBuilder : MonoBehaviour
|
|||||||
BuildTopologyFromGraph(graph);
|
BuildTopologyFromGraph(graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly Dictionary<int, string> MockLeaders = new()
|
||||||
|
{
|
||||||
|
{ 1, "mockData" },
|
||||||
|
{ 2, "mockDataWithDC" }
|
||||||
|
};
|
||||||
|
|
||||||
|
public void BuildTopologyFromJsonFile(string fileName)
|
||||||
|
{
|
||||||
|
TopologyGraph graph = new TopologyGraph();
|
||||||
|
Dictionary<int, ModuleType> idToType = new();
|
||||||
|
|
||||||
|
TextAsset jsonAsset = Resources.Load<TextAsset>(fileName);
|
||||||
|
if (jsonAsset == null) { Debug.LogError($"JSON not found: Resources/{fileName}.json"); return; }
|
||||||
|
JsonTopologyGraph jsonGraph = JsonUtility.FromJson<JsonTopologyGraph>(jsonAsset.text);
|
||||||
|
if (jsonGraph?.Modules == null || jsonGraph.Modules.Count == 0) { Debug.LogError("JSON has no modules"); return; }
|
||||||
|
|
||||||
|
Dictionary<string, int> stringToIntId = new();
|
||||||
|
int nextIntId = 1;
|
||||||
|
foreach (var m in jsonGraph.Modules)
|
||||||
|
{
|
||||||
|
if (!stringToIntId.ContainsKey(m.Id)) stringToIntId[m.Id] = nextIntId++;
|
||||||
|
int intId = stringToIntId[m.Id];
|
||||||
|
ModuleType mt = ParseModuleType(m.Type);
|
||||||
|
idToType[intId] = mt;
|
||||||
|
graph.Modules.Add(new ModuleData { Id = intId, Type = mt.ToString(), Degree = m.Degree });
|
||||||
|
}
|
||||||
|
if (jsonGraph.Connections != null)
|
||||||
|
foreach (var c in jsonGraph.Connections)
|
||||||
|
{
|
||||||
|
if (!stringToIntId.TryGetValue(c.FromModuleId, out int fromId) || !stringToIntId.TryGetValue(c.ToModuleId, out int toId)) continue;
|
||||||
|
Orientation o = c.Orientation == 90 ? Orientation.Deg90 : c.Orientation == 180 ? Orientation.Deg180 : c.Orientation == 270 ? Orientation.Deg270 : Orientation.Deg0;
|
||||||
|
graph.Connections.Add(new Connection { FromModuleId = fromId, ToModuleId = toId, FromSocket = c.FromSocket ?? "MaleSocket", ToSocket = c.ToSocket ?? "FemaleSocket", Orientation = o });
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[TopologyBuilder] Built from JSON file: {fileName}");
|
||||||
|
_skipControlLibraryCalls = true;
|
||||||
|
BuildTopologyFromGraph(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BuildTopologyFromLeader(int leaderId)
|
||||||
|
{
|
||||||
|
TopologyGraph graph = new TopologyGraph();
|
||||||
|
Dictionary<int, ModuleType> idToType = new();
|
||||||
|
|
||||||
|
RobotConfiguration config = ControlLibrary.getRobotConfiguration(leaderId);
|
||||||
|
int moduleCount = config.ModulesLength;
|
||||||
|
for (int i = 0; i < moduleCount; i++)
|
||||||
|
{
|
||||||
|
var n_module = config.Modules(i);
|
||||||
|
if (n_module != null)
|
||||||
|
{
|
||||||
|
var module = n_module.Value;
|
||||||
|
ModuleType moduleType = Enum.Parse<ModuleType>(module.ModuleType.ToString());
|
||||||
|
float degree = module.ConfigurationAsMotorState().Angle;
|
||||||
|
if (moduleType == ModuleType.SERVO_1 || moduleType == ModuleType.SERVO_2)
|
||||||
|
degree = 90;
|
||||||
|
graph.Modules.Add(new ModuleData { Id = module.Id, Type = module.ModuleType.ToString(), Degree = degree });
|
||||||
|
idToType.Add(module.Id, module.ModuleType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int connectionCount = config.ConnectionsLength;
|
||||||
|
for (int i = 0; i < connectionCount; i++)
|
||||||
|
{
|
||||||
|
var n_connection = config.Connections(i);
|
||||||
|
if (n_connection != null)
|
||||||
|
{
|
||||||
|
var connection = n_connection.Value;
|
||||||
|
if (connection.FromSocket == 0) continue;
|
||||||
|
graph.Connections.Add(new Connection
|
||||||
|
{
|
||||||
|
FromModuleId = connection.FromModuleId,
|
||||||
|
FromSocket =
|
||||||
|
idToType[connection.FromModuleId] == ModuleType.SPLITTER ||
|
||||||
|
idToType[connection.FromModuleId] == ModuleType.SPLITTER_2 ||
|
||||||
|
idToType[connection.FromModuleId] == ModuleType.SPLITTER_3 ||
|
||||||
|
idToType[connection.FromModuleId] == ModuleType.SPLITTER_4
|
||||||
|
? "MaleSocket" + (connection.FromSocket == 0 ? "" : connection.FromSocket)
|
||||||
|
: "MaleSocket",
|
||||||
|
ToModuleId = connection.ToModuleId,
|
||||||
|
ToSocket = "FemaleSocket",
|
||||||
|
Orientation = connection.Orientation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_skipControlLibraryCalls = false;
|
||||||
|
|
||||||
|
Debug.Log($"[TopologyBuilder] Building from leader {leaderId}: {graph.Modules.Count} modules, {graph.Connections.Count} connections");
|
||||||
|
BuildTopologyFromGraph(graph);
|
||||||
|
}
|
||||||
|
|
||||||
private static ModuleType ParseModuleType(string typeString)
|
private static ModuleType ParseModuleType(string typeString)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(typeString)) return ModuleType.SPLITTER;
|
if (string.IsNullOrEmpty(typeString)) return ModuleType.SPLITTER;
|
||||||
|
|||||||
@@ -2667,6 +2667,7 @@ MonoBehaviour:
|
|||||||
useJsonFile: 0
|
useJsonFile: 0
|
||||||
skipControlLibraryCalls: 0
|
skipControlLibraryCalls: 0
|
||||||
jsonFileName: mockData
|
jsonFileName: mockData
|
||||||
|
mockControlLibrary: 0
|
||||||
--- !u!4 &387671720
|
--- !u!4 &387671720
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -21345,6 +21346,7 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
overlayPanel: {fileID: 8840000001}
|
overlayPanel: {fileID: 8840000001}
|
||||||
overlayContent: {fileID: 8840000042}
|
overlayContent: {fileID: 8840000042}
|
||||||
|
topologyBuilder: {fileID: 387671719}
|
||||||
--- !u!1 &168756400964845240
|
--- !u!1 &168756400964845240
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
Reference in New Issue
Block a user