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 RectTransform overlayContent;
|
||||
public TopologyBuilder topologyBuilder;
|
||||
|
||||
public void ShowOverlay()
|
||||
{
|
||||
if (overlayPanel != null)
|
||||
if (overlayPanel == null) return;
|
||||
|
||||
EnsureScrollRect();
|
||||
ClearContent();
|
||||
|
||||
if (topologyBuilder == null)
|
||||
topologyBuilder = FindObjectOfType<TopologyBuilder>();
|
||||
|
||||
bool useMock = topologyBuilder != null && topologyBuilder.mockControlLibrary;
|
||||
|
||||
if (useMock)
|
||||
{
|
||||
EnsureScrollRect();
|
||||
overlayPanel.SetActive(true);
|
||||
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()
|
||||
@@ -22,6 +51,195 @@ public class DiscoverOverlayController : MonoBehaviour
|
||||
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()
|
||||
{
|
||||
if (overlayContent == null) return;
|
||||
|
||||
@@ -19,16 +19,7 @@ public class DiscoveryButton : MonoBehaviour
|
||||
|
||||
public void OnDiscoveryPressed()
|
||||
{
|
||||
if (topologyBuilder != null)
|
||||
{
|
||||
Debug.Log("Discovery button pressed.");
|
||||
topologyBuilder.BuildTopologyFromJson();
|
||||
FocusCameraOnTopology();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("TopologyBuilder is not assigned!");
|
||||
}
|
||||
Debug.Log("Discovery button pressed – topology building deferred to overlay selection.");
|
||||
}
|
||||
|
||||
void FocusCameraOnTopology()
|
||||
|
||||
@@ -23,6 +23,10 @@ public class TopologyBuilder : MonoBehaviour
|
||||
[Tooltip("JSON file name (without .json) in Resources folder. e.g. 'mockData' or 'mockDataSimple'")]
|
||||
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 bool _skipControlLibraryCalls = true;
|
||||
|
||||
@@ -126,6 +130,96 @@ public class TopologyBuilder : MonoBehaviour
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(typeString)) return ModuleType.SPLITTER;
|
||||
|
||||
@@ -2667,6 +2667,7 @@ MonoBehaviour:
|
||||
useJsonFile: 0
|
||||
skipControlLibraryCalls: 0
|
||||
jsonFileName: mockData
|
||||
mockControlLibrary: 0
|
||||
--- !u!4 &387671720
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -21345,6 +21346,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
overlayPanel: {fileID: 8840000001}
|
||||
overlayContent: {fileID: 8840000042}
|
||||
topologyBuilder: {fileID: 387671719}
|
||||
--- !u!1 &168756400964845240
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
Reference in New Issue
Block a user