using UnityEngine; using System.Collections.Generic; using System; using System.Linq; using Frontend; [System.Serializable] public class JsonTopologyGraph { public List Modules; public List Connections; } [System.Serializable] public class JsonModuleData { public string Id; public string Type; public float Degree; } [System.Serializable] public class JsonConnection { public string FromModuleId; public string ToModuleId; public string FromSocket; public string ToSocket; public int Orientation; } public class TopologyBuilder : MonoBehaviour { public ModuleSpawner spawner; [Header("Topology Selection")] [Tooltip("Set to true to load from JSON file (for local testing). Set to false to use ControlLibrary.")] public bool useJsonFile = true; [Tooltip("When true, angle commands are not sent to native ControlLibrary - use only for local testing without hardware.")] public bool skipControlLibraryCalls = false; [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 idToInstance = new(); public static bool _skipControlLibraryCalls = true; public static bool SkipControlLibraryCalls => _skipControlLibraryCalls; public void BuildTopologyFromJson() { TopologyGraph graph = new TopologyGraph(); Dictionary idToType = new(); if (useJsonFile) { TextAsset jsonAsset = Resources.Load(jsonFileName); if (jsonAsset == null) { Debug.LogError($"JSON not found: Resources/{jsonFileName}.json"); return; } JsonTopologyGraph jsonGraph = JsonUtility.FromJson(jsonAsset.text); if (jsonGraph?.Modules == null || jsonGraph.Modules.Count == 0) { Debug.LogError("JSON has no modules"); return; } Dictionary stringToIntId = new Dictionary(); 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: {jsonFileName}"); _skipControlLibraryCalls = true; } else { RobotConfiguration config = ControlLibrary.getRobotConfiguration(100); // todo: change to actual leader id 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; Debug.Log("Adding module " + module.Id); ModuleType moduleType = Enum.Parse(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; var rawConns = new List(); for (int i = 0; i < connectionCount; i++) { var nc = config.Connections(i); if (nc != null) rawConns.Add(nc.Value); } var processedPairs = new HashSet(); // Pass 0: add connections where 'from' is a hub/splitter (named output ports). // Pass 1: add any remaining connections not yet covered. for (int pass = 0; pass < 2; pass++) { foreach (var connection in rawConns) { if (!idToType.ContainsKey(connection.FromModuleId) || !idToType.ContainsKey(connection.ToModuleId)) { Debug.LogWarning($"[TopologyBuilder] Skipping connection with unknown module id: {connection.FromModuleId} -> {connection.ToModuleId}"); continue; } bool fromIsHub = idToType[connection.FromModuleId] == ModuleType.SPLITTER || idToType[connection.FromModuleId] == ModuleType.SPLITTER_2 || idToType[connection.FromModuleId] == ModuleType.SPLITTER_3 || idToType[connection.FromModuleId] == ModuleType.SPLITTER_4; if (pass == 0 && !fromIsHub) continue; byte lo = Math.Min(connection.FromModuleId, connection.ToModuleId); byte hi = Math.Max(connection.FromModuleId, connection.ToModuleId); string pairKey = lo + "-" + hi; if (processedPairs.Contains(pairKey)) continue; processedPairs.Add(pairKey); Debug.Log("Connection: from (socket): " + connection.FromModuleId + " (" + connection.FromSocket + ")" + " to " + connection.ToModuleId + " (" + connection.ToSocket + ")" + " orientation: " + connection.Orientation); graph.Connections.Add(new Connection { FromModuleId = connection.FromModuleId, FromSocket = fromIsHub ? "MaleSocket" + (connection.FromSocket + 1).ToString() : "MaleSocket", ToModuleId = connection.ToModuleId, ToSocket = "FemaleSocket", Orientation = connection.Orientation }); } } _skipControlLibraryCalls = false; Debug.Log("=== Control Library Topology ==="); Debug.Log($"Modules ({graph.Modules.Count}):"); foreach (var m in graph.Modules) Debug.Log($" Id={m.Id}, Type={m.Type}, Degree={m.Degree}"); Debug.Log($"Connections ({graph.Connections.Count}):"); foreach (var c in graph.Connections) Debug.Log($" {c.FromModuleId} ({c.FromSocket}) -> {c.ToModuleId} ({c.ToSocket}), Orientation={c.Orientation}"); Debug.Log("================================"); } BuildTopologyFromGraph(graph); } public static readonly Dictionary MockLeaders = new() { { 1, "mockData" }, { 2, "mockDataWithDC" } }; public void BuildTopologyFromJsonFile(string fileName) { TopologyGraph graph = new TopologyGraph(); Dictionary idToType = new(); TextAsset jsonAsset = Resources.Load(fileName); if (jsonAsset == null) { Debug.LogError($"JSON not found: Resources/{fileName}.json"); return; } JsonTopologyGraph jsonGraph = JsonUtility.FromJson(jsonAsset.text); if (jsonGraph?.Modules == null || jsonGraph.Modules.Count == 0) { Debug.LogError("JSON has no modules"); return; } Dictionary 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 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(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; var rawConns = new List(); for (int i = 0; i < connectionCount; i++) { var nc = config.Connections(i); if (nc != null) rawConns.Add(nc.Value); } var processedPairs = new HashSet(); // Pass 0: add connections where 'from' is a hub/splitter (named output ports). // Pass 1: add any remaining connections not yet covered. for (int pass = 0; pass < 2; pass++) { foreach (var connection in rawConns) { if (!idToType.ContainsKey(connection.FromModuleId) || !idToType.ContainsKey(connection.ToModuleId)) { Debug.LogWarning($"[TopologyBuilder] Skipping connection with unknown module id: {connection.FromModuleId} -> {connection.ToModuleId}"); continue; } bool fromIsHub = idToType[connection.FromModuleId] == ModuleType.SPLITTER || idToType[connection.FromModuleId] == ModuleType.SPLITTER_2 || idToType[connection.FromModuleId] == ModuleType.SPLITTER_3 || idToType[connection.FromModuleId] == ModuleType.SPLITTER_4; if (pass == 0 && !fromIsHub) continue; byte lo = Math.Min(connection.FromModuleId, connection.ToModuleId); byte hi = Math.Max(connection.FromModuleId, connection.ToModuleId); string pairKey = lo + "-" + hi; if (processedPairs.Contains(pairKey)) continue; processedPairs.Add(pairKey); graph.Connections.Add(new Connection { FromModuleId = connection.FromModuleId, FromSocket = fromIsHub ? "MaleSocket" + (connection.FromSocket + 1).ToString() : "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; switch (typeString) { case "Servo1": return ModuleType.SERVO_1; case "Servo2": return ModuleType.SERVO_2; case "DC": return ModuleType.DC_MOTOR; case "Hub": return ModuleType.SPLITTER; case "Battery": return ModuleType.BATTERY; case "Power": return ModuleType.POWER; case "Gripper": return ModuleType.GRIPPER; case "Display": return ModuleType.DISPLAY; case "Distance": return ModuleType.DISTANCE_SENSOR; case "IMU": return ModuleType.IMU; case "Speaker": return ModuleType.SPEAKER; case "Splitter2": return ModuleType.SPLITTER_2; case "Splitter3": return ModuleType.SPLITTER_3; case "Splitter4": return ModuleType.SPLITTER_4; default: return ModuleType.SPLITTER; } } private void BuildTopologyFromGraph(TopologyGraph graph) { // Destroy previous topology root if it exists GameObject oldRoot = GameObject.Find("GeneratedTopology"); if (oldRoot != null) { DestroyImmediate(oldRoot); } // Create a new root GameObject to hold all modules GameObject topologyRoot = new GameObject("GeneratedTopology"); topologyRoot.transform.localScale = new Vector3(20f, 20f, 20f); // Clean up old instances foreach (var oldModule in idToInstance.Values) { Destroy(oldModule); } idToInstance.Clear(); // First spawn all modules foreach (var module in graph.Modules) { // Debug.Log($"Before error: {module.Type}."); ModuleType type = Enum.Parse(module.Type); GameObject prefab = spawner.GetPrefabForType(type); // GameObject prefab = spawner.GetPrefabForType(module.Type); if (prefab == null) { Debug.LogError($"No prefab found for type {module.Type}"); continue; } GameObject instance = Instantiate(prefab, Vector3.zero, Quaternion.identity, topologyRoot.transform); instance.name = module.Id.ToString(); // instance.transform.localScale = new Vector3(20f, 20f, 20f); instance.transform.SetParent(topologyRoot.transform, false); // attach to root instance.transform.localScale = Vector3.one; idToInstance[module.Id.ToString()] = instance; ModuleBase baseScript = instance.GetComponent(); if (baseScript != null) { baseScript.moduleID = module.Id.ToString(); ModuleType parsedType = Enum.Parse(module.Type); if ((parsedType == ModuleType.SERVO_1 || parsedType == ModuleType.SERVO_2)) { ServoMotorModule servo; if (parsedType == ModuleType.SERVO_1) { servo = instance.GetComponent(); } else { servo = instance.GetComponent(); } servo.InitialSetAngle(module.Degree); } else if (parsedType == ModuleType.GRIPPER) { var gripper = instance.GetComponent(); if (gripper != null) gripper.InitialSetAngle(module.Degree); } } } // Then connect them foreach (var connection in graph.Connections) { if (!idToInstance.ContainsKey(connection.FromModuleId.ToString()) || !idToInstance.ContainsKey(connection.ToModuleId.ToString())) { Debug.LogWarning($"Missing instance for {connection.FromModuleId} or {connection.ToModuleId}"); continue; } GameObject objFrom = idToInstance[connection.FromModuleId.ToString()]; GameObject objTo = idToInstance[connection.ToModuleId.ToString()]; string socketA = connection.FromSocket; string socketB = connection.ToSocket; // Debug.LogError("FromModuleId: " + connection.FromModuleId + " ToModuleId: " + connection.ToModuleId + " FromSocket: " + socketA + " ToSocket: " + socketB); ModuleData fromModule = graph.Modules.Find(m => m.Id == connection.FromModuleId); ModuleData toModule = graph.Modules.Find(m => m.Id == connection.ToModuleId); ModuleType fromType = Enum.Parse(fromModule.Type); ModuleType toType = Enum.Parse(toModule.Type); if ((fromType == ModuleType.SERVO_1 || fromType == ModuleType.SERVO_2) && socketA == "MaleSocket") { socketA = "ArmSocket"; // Debug.LogError("here"); } else if ((fromType == ModuleType.SERVO_1 || fromType == ModuleType.SERVO_2) && socketA == "FemaleSocket") { socketA = "BodySocket"; // Debug.LogError("here1"); } if ((toType == ModuleType.SERVO_1 || toType == ModuleType.SERVO_2) && socketB == "MaleSocket") { socketB = "ArmSocket"; // Debug.LogError("here2"); } else if ((toType == ModuleType.SERVO_1 || toType == ModuleType.SERVO_2) && socketB == "FemaleSocket") { socketB = "BodySocket"; // Debug.LogError("here3"); } float twistDegrees; switch (connection.Orientation) { case Orientation.Deg90: twistDegrees = 90f; break; case Orientation.Deg180: twistDegrees = 180f; break; case Orientation.Deg270: twistDegrees = 270f; break; default: twistDegrees = 0f; break; } Debug.Log("twist deg " + twistDegrees); twistDegrees -= 90; // todo: we should not need to do this, there is a bug further down where the rotation is not correct spawner.ConnectModules(objFrom, socketA, objTo, socketB, objFrom.transform.position, twistDegrees); } } }