From 7212f692a6136b1d464599b4ad3bb2b1d6fe57ad Mon Sep 17 00:00:00 2001 From: Leo Qu Date: Tue, 10 Mar 2026 18:13:32 -0400 Subject: [PATCH] Pipeline saving and View popup window added Made-with: Cursor --- Assets/DiscoverOverlayController.cs | 50 + Assets/DiscoverOverlayController.cs.meta | 11 + Assets/Module/DCActualFinal.prefab | 2 +- Assets/Module/TopologyBuilder.cs | 9 + Assets/ModuleBase.cs | 6 +- .../ProgrammedMovementsController.cs | 271 +- .../ProgrammedMovements/SavedTimelines.meta | 8 + Assets/Resources/mockDataMMMMHub.json | 39 + Assets/Resources/mockDataMMMMHub.json.meta | 7 + Assets/Scenes/SampleScene.unity | 4878 ++++++++++++++++- 10 files changed, 5260 insertions(+), 21 deletions(-) create mode 100644 Assets/DiscoverOverlayController.cs create mode 100644 Assets/DiscoverOverlayController.cs.meta create mode 100644 Assets/ProgrammedMovements/SavedTimelines.meta create mode 100644 Assets/Resources/mockDataMMMMHub.json create mode 100644 Assets/Resources/mockDataMMMMHub.json.meta diff --git a/Assets/DiscoverOverlayController.cs b/Assets/DiscoverOverlayController.cs new file mode 100644 index 0000000..456d74a --- /dev/null +++ b/Assets/DiscoverOverlayController.cs @@ -0,0 +1,50 @@ +using UnityEngine; +using UnityEngine.UI; + +public class DiscoverOverlayController : MonoBehaviour +{ + public GameObject overlayPanel; + public RectTransform overlayContent; + + public void ShowOverlay() + { + if (overlayPanel != null) + { + EnsureScrollRect(); + overlayPanel.SetActive(true); + } + } + + public void HideOverlay() + { + ClearContent(); + if (overlayPanel != null) + overlayPanel.SetActive(false); + } + + void EnsureScrollRect() + { + if (overlayContent == null) return; + var scrollArea = overlayContent.parent; + if (scrollArea == null) return; + var scrollGo = scrollArea.gameObject; + if (scrollGo.GetComponent() == null) + scrollGo.AddComponent().showMaskGraphic = false; + var sr = scrollGo.GetComponent(); + if (sr == null) + { + sr = scrollGo.AddComponent(); + sr.horizontal = false; + sr.vertical = true; + sr.content = overlayContent; + sr.movementType = ScrollRect.MovementType.Clamped; + } + } + + void ClearContent() + { + if (overlayContent == null) return; + for (int i = overlayContent.childCount - 1; i >= 0; i--) + Destroy(overlayContent.GetChild(i).gameObject); + } +} diff --git a/Assets/DiscoverOverlayController.cs.meta b/Assets/DiscoverOverlayController.cs.meta new file mode 100644 index 0000000..c31061f --- /dev/null +++ b/Assets/DiscoverOverlayController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1c2b3a4e5f6071829a0b1c2d3e4f5a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Module/DCActualFinal.prefab b/Assets/Module/DCActualFinal.prefab index ab9c0eb..7b89814 100644 --- a/Assets/Module/DCActualFinal.prefab +++ b/Assets/Module/DCActualFinal.prefab @@ -25,7 +25,7 @@ Transform: m_GameObject: {fileID: 3292419760055380793} serializedVersion: 2 m_LocalRotation: {x: 0.010896653, y: -0.010896653, z: 0.7070228, w: 0.70702285} - m_LocalPosition: {x: 0.0003, y: -0.0603, z: -0.0017} + m_LocalPosition: {x: 0.0003, y: -0.0518, z: -0.0014} m_LocalScale: {x: 0.049999997, y: 0.049999997, z: 0.05} m_ConstrainProportionsScale: 0 m_Children: [] diff --git a/Assets/Module/TopologyBuilder.cs b/Assets/Module/TopologyBuilder.cs index fa13008..b9ddf39 100644 --- a/Assets/Module/TopologyBuilder.cs +++ b/Assets/Module/TopologyBuilder.cs @@ -112,6 +112,15 @@ public class TopologyBuilder : MonoBehaviour } } _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); diff --git a/Assets/ModuleBase.cs b/Assets/ModuleBase.cs index 50eb6eb..8304443 100644 --- a/Assets/ModuleBase.cs +++ b/Assets/ModuleBase.cs @@ -45,9 +45,11 @@ public abstract class ModuleBase : MonoBehaviour } try { - if (0 != ControlLibrary.send_angle_control(Int32.Parse(moduleID), angleRounded)) + int result = ControlLibrary.send_angle_control(Int32.Parse(moduleID), angleRounded); + Debug.Log($"[ControlLibrary] send_angle_control(moduleId={moduleID}, angle={angleRounded}) returned {result}"); + if (result != 0) { - Debug.Log("Control library exited with error"); + Debug.LogWarning($"[ControlLibrary] send_angle_control returned error code: {result}"); } } catch (DllNotFoundException) diff --git a/Assets/ProgrammedMovements/ProgrammedMovementsController.cs b/Assets/ProgrammedMovements/ProgrammedMovementsController.cs index 2576ce4..1ad6d0e 100644 --- a/Assets/ProgrammedMovements/ProgrammedMovementsController.cs +++ b/Assets/ProgrammedMovements/ProgrammedMovementsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; @@ -11,7 +12,7 @@ using UnityEngine.EventSystems; /// public class ProgrammedMovementsController : MonoBehaviour { - public const int TracksCount = 3; + public const int TracksCount = 4; public const int TimelineDurationSeconds = 10; public static readonly Color HighlightBlue = new Color(0.2f, 0.5f, 1f, 1f); @@ -19,6 +20,10 @@ public class ProgrammedMovementsController : MonoBehaviour public Camera mainCamera; public RectTransform timelineRoot; public Button runButton; + public Button saveButton; + public Button loadButton; + public GameObject filePickerPanel; + public RectTransform filePickerContent; private RectTransform _bannerRoot; private Transform _timelineView; @@ -45,6 +50,7 @@ public class ProgrammedMovementsController : MonoBehaviour } EnsureTimelineViewShown(); WireUpButtons(); + WireSaveLoadButtons(); RefreshRunButton(); // Force layout rebuild so block positions align with timeline after view switch Canvas.ForceUpdateCanvases(); @@ -96,6 +102,18 @@ public class ProgrammedMovementsController : MonoBehaviour } } + private bool _saveLoadWired; + + void WireSaveLoadButtons() + { + if (_saveLoadWired) return; + _saveLoadWired = true; + if (saveButton != null) + saveButton.onClick.AddListener(SaveTimelineToFile); + if (loadButton != null) + loadButton.onClick.AddListener(LoadTimelineFromFile); + } + void WireUpButtons() { WireUpRunButton(); @@ -686,4 +704,255 @@ public class ProgrammedMovementsController : MonoBehaviour public int dcDirection = 1; // 1 = forward, -1 = backward public GameObject uiRoot; } + + [Serializable] + private class SavedMovement + { + public string ModuleId; + public string ModuleType; + public float Degree; + public int Direction; + public int Track; + } + + [Serializable] + private class SavedSecond + { + public int Second; + public List Movements = new List(); + } + + [Serializable] + private class SavedTimeline + { + public List Timeline = new List(); + } + + public void SaveTimelineToFile() + { + var bySecond = new Dictionary>(); + foreach (var b in _blocks) + { + if (!bySecond.ContainsKey(b.second)) + bySecond[b.second] = new List(); + bySecond[b.second].Add(b); + } + + var saved = new SavedTimeline(); + var sortedSeconds = new List(bySecond.Keys); + sortedSeconds.Sort(); + foreach (int sec in sortedSeconds) + { + var entry = new SavedSecond { Second = sec }; + foreach (var b in bySecond[sec]) + { + var m = new SavedMovement + { + Degree = b.targetAngle, + Direction = b.dcDirection, + Track = b.trackIndex + }; + if (b.moduleServo != null) + { + m.ModuleId = b.moduleServo.moduleID; + m.ModuleType = "Servo"; + } + else if (b.moduleDC != null) + { + m.ModuleId = b.moduleDC.moduleID; + m.ModuleType = "DC"; + } + entry.Movements.Add(m); + } + saved.Timeline.Add(entry); + } + + string json = JsonUtility.ToJson(saved, true); + string dir = Path.Combine(Application.dataPath, "ProgrammedMovements", "SavedTimelines"); + if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); + string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); + string filename = $"timeline_{timestamp}.json"; + string path = Path.Combine(dir, filename); + File.WriteAllText(path, json); + Debug.Log($"[ProgrammedMovements] Saved {_blocks.Count} movements to {path}"); + } + + public void LoadTimelineFromFile() + { + string dir = Path.Combine(Application.dataPath, "ProgrammedMovements", "SavedTimelines"); + if (!Directory.Exists(dir)) + { + Debug.LogWarning($"[ProgrammedMovements] No saved timelines directory: {dir}"); + return; + } + var files = Directory.GetFiles(dir, "timeline_*.json"); + if (files.Length == 0) + { + Debug.LogWarning($"[ProgrammedMovements] No saved timelines found in {dir}"); + return; + } + Array.Sort(files); + Array.Reverse(files); + ShowFilePicker(files); + } + + void ShowFilePicker(string[] filePaths) + { + if (filePickerPanel == null || filePickerContent == null) return; + + ClearFilePickerContent(); + EnsureScrollRect(); + + float itemHeight = 32f; + float gap = 4f; + float totalHeight = filePaths.Length * (itemHeight + gap); + filePickerContent.sizeDelta = new Vector2(0, totalHeight); + + for (int i = 0; i < filePaths.Length; i++) + { + string filePath = filePaths[i]; + string fileName = Path.GetFileNameWithoutExtension(filePath); + + var itemGo = new GameObject("Item_" + i); + itemGo.transform.SetParent(filePickerContent, false); + var itemRect = itemGo.AddComponent(); + itemRect.anchorMin = new Vector2(0, 1); + itemRect.anchorMax = new Vector2(1, 1); + itemRect.pivot = new Vector2(0.5f, 1); + itemRect.anchoredPosition = new Vector2(0, -(i * (itemHeight + gap))); + itemRect.sizeDelta = new Vector2(-16, itemHeight); + var itemImg = itemGo.AddComponent(); + itemImg.color = new Color(0.15f, 0.15f, 0.18f, 1f); + var itemBtn = itemGo.AddComponent