diff --git a/Assets/Module/battery_module_mf_unity.fbx b/Assets/Module/battery_module_mf_unity.fbx index edd71de..71da25d 100644 Binary files a/Assets/Module/battery_module_mf_unity.fbx and b/Assets/Module/battery_module_mf_unity.fbx differ diff --git a/Assets/Module/dc_module.fbx b/Assets/Module/dc_module.fbx index f4a0a60..8e82ce5 100644 Binary files a/Assets/Module/dc_module.fbx and b/Assets/Module/dc_module.fbx differ diff --git a/Assets/Module/display_module.fbx b/Assets/Module/display_module.fbx index 6adcb21..9130f9e 100644 Binary files a/Assets/Module/display_module.fbx and b/Assets/Module/display_module.fbx differ diff --git a/Assets/Module/distance_sensor_module.fbx b/Assets/Module/distance_sensor_module.fbx index 505210d..d4c1ad8 100644 Binary files a/Assets/Module/distance_sensor_module.fbx and b/Assets/Module/distance_sensor_module.fbx differ diff --git a/Assets/Module/gripper_module_arm.fbx b/Assets/Module/gripper_module_arm.fbx index 9f146a2..f2668d1 100644 Binary files a/Assets/Module/gripper_module_arm.fbx and b/Assets/Module/gripper_module_arm.fbx differ diff --git a/Assets/Module/gripper_module_body.fbx b/Assets/Module/gripper_module_body.fbx index b087e65..6e756a1 100644 Binary files a/Assets/Module/gripper_module_body.fbx and b/Assets/Module/gripper_module_body.fbx differ diff --git a/Assets/Module/hub_module_eq_triangle_mmm.fbx b/Assets/Module/hub_module_eq_triangle_mmm.fbx index 8a01b05..dc2d649 100644 Binary files a/Assets/Module/hub_module_eq_triangle_mmm.fbx and b/Assets/Module/hub_module_eq_triangle_mmm.fbx differ diff --git a/Assets/Module/hub_module_mmmf.fbx b/Assets/Module/hub_module_mmmf.fbx index 9675d6e..89ef027 100644 Binary files a/Assets/Module/hub_module_mmmf.fbx and b/Assets/Module/hub_module_mmmf.fbx differ diff --git a/Assets/Module/hub_module_mmmf_unity.fbx b/Assets/Module/hub_module_mmmf_unity.fbx index 71a0330..b969b6c 100644 Binary files a/Assets/Module/hub_module_mmmf_unity.fbx and b/Assets/Module/hub_module_mmmf_unity.fbx differ diff --git a/Assets/Module/hub_module_mmmm.fbx b/Assets/Module/hub_module_mmmm.fbx index cf0380f..a3f3d62 100644 Binary files a/Assets/Module/hub_module_mmmm.fbx and b/Assets/Module/hub_module_mmmm.fbx differ diff --git a/Assets/Module/hub_module_right_triangle_mmf.fbx b/Assets/Module/hub_module_right_triangle_mmf.fbx index 40cc010..42eb0c2 100644 Binary files a/Assets/Module/hub_module_right_triangle_mmf.fbx and b/Assets/Module/hub_module_right_triangle_mmf.fbx differ diff --git a/Assets/Module/imu_module.fbx b/Assets/Module/imu_module.fbx index e83c976..006ec46 100644 Binary files a/Assets/Module/imu_module.fbx and b/Assets/Module/imu_module.fbx differ diff --git a/Assets/Module/power_module_rounded.fbx b/Assets/Module/power_module_rounded.fbx index f275e55..66e66b0 100644 Binary files a/Assets/Module/power_module_rounded.fbx and b/Assets/Module/power_module_rounded.fbx differ diff --git a/Assets/Module/servo_bend_module_arm.fbx b/Assets/Module/servo_bend_module_arm.fbx index 0c5d311..651bf1e 100644 Binary files a/Assets/Module/servo_bend_module_arm.fbx and b/Assets/Module/servo_bend_module_arm.fbx differ diff --git a/Assets/Module/servo_bend_module_body.fbx b/Assets/Module/servo_bend_module_body.fbx index 9964af9..ed37347 100644 Binary files a/Assets/Module/servo_bend_module_body.fbx and b/Assets/Module/servo_bend_module_body.fbx differ diff --git a/Assets/Module/servo_straight_module_arm.fbx b/Assets/Module/servo_straight_module_arm.fbx index 6fc1e29..ed33cf9 100644 Binary files a/Assets/Module/servo_straight_module_arm.fbx and b/Assets/Module/servo_straight_module_arm.fbx differ diff --git a/Assets/Module/servo_straight_module_body.fbx b/Assets/Module/servo_straight_module_body.fbx index df5f83e..b83ce39 100644 Binary files a/Assets/Module/servo_straight_module_body.fbx and b/Assets/Module/servo_straight_module_body.fbx differ diff --git a/Assets/Module/speaker2_module.fbx b/Assets/Module/speaker2_module.fbx index efe9def..7cfc737 100644 Binary files a/Assets/Module/speaker2_module.fbx and b/Assets/Module/speaker2_module.fbx differ diff --git a/Assets/ProgrammedMovements.meta b/Assets/ProgrammedMovements.meta new file mode 100644 index 0000000..5c63780 --- /dev/null +++ b/Assets/ProgrammedMovements.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 482781b67c03b448ba71ae05ddf4b82b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProgrammedMovements/Editor.meta b/Assets/ProgrammedMovements/Editor.meta new file mode 100644 index 0000000..f12739f --- /dev/null +++ b/Assets/ProgrammedMovements/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b37c4fea096a34f9dac835964cbdaec9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ProgrammedMovements/Editor/ProgrammedMovementsSetup.cs b/Assets/ProgrammedMovements/Editor/ProgrammedMovementsSetup.cs new file mode 100644 index 0000000..6051b73 --- /dev/null +++ b/Assets/ProgrammedMovements/Editor/ProgrammedMovementsSetup.cs @@ -0,0 +1,579 @@ +using UnityEngine; +using UnityEngine.UI; +using UnityEditor; +using TMPro; +using UnityEditor.SceneManagement; + +/// +/// Editor menu to build ProgrammedMovements UI hierarchy in the scene. +/// Run once via Tools > ProgrammedMovements > Setup UI Hierarchy. +/// The hierarchy is created in the scene (not at runtime) so you can modify it. +/// +public static class ProgrammedMovementsSetup +{ + [InitializeOnLoadMethod] + static void OnLoad() + { + EditorSceneManager.sceneOpened += (scene, mode) => + { + if (Application.isPlaying) return; + var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView"); + if (pmView == null) return; + var canvas = pmView.Find("ProgrammedMovementsCanvas"); + if (canvas == null) + { + SetupHierarchy(); + EditorSceneManager.MarkSceneDirty(scene); + return; + } + var banner = canvas.Find("TimelineBanner"); + if (banner != null && banner.Find("ConfigView") == null && banner.Find("TracksContainer") != null) + { + MigrateToNewLayout(); + EditorSceneManager.MarkSceneDirty(scene); + } + }; + } + const int TracksCount = 3; + const int TimelineDurationSeconds = 10; + + [MenuItem("Tools/ProgrammedMovements/Setup UI Hierarchy")] + public static void SetupHierarchy() + { + var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView"); + if (pmView == null) + { + Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found."); + return; + } + + var existing = pmView.Find("ProgrammedMovementsCanvas"); + if (existing != null) + { + Debug.Log("[ProgrammedMovementsSetup] Hierarchy already exists. Use Tools > ProgrammedMovements > Force Rebuild UI to replace."); + return; + } + DoSetup(pmView); + } + + [MenuItem("Tools/ProgrammedMovements/Force Rebuild UI")] + public static void ForceRebuildHierarchy() + { + var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView"); + if (pmView == null) + { + Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found."); + return; + } + var existing = pmView.Find("ProgrammedMovementsCanvas"); + if (existing != null) + { + Object.DestroyImmediate(existing.gameObject); + } + DoSetup(pmView); + } + + [MenuItem("Tools/ProgrammedMovements/Migrate to New Layout (add Config panel)")] + public static void MigrateToNewLayout() + { + var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView"); + if (pmView == null) + { + Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found."); + return; + } + var canvas = pmView.Find("ProgrammedMovementsCanvas"); + if (canvas == null) + { + Debug.Log("[ProgrammedMovementsSetup] No canvas found. Run Setup UI Hierarchy first."); + return; + } + var banner = canvas.Find("TimelineBanner"); + if (banner == null) + { + Debug.LogError("[ProgrammedMovementsSetup] TimelineBanner not found."); + return; + } + if (banner.Find("ConfigView") != null) + { + Debug.Log("[ProgrammedMovementsSetup] Already has ConfigView. No migration needed."); + return; + } + var bannerRect = banner.GetComponent(); + if (bannerRect == null) + { + Debug.LogError("[ProgrammedMovementsSetup] TimelineBanner has no RectTransform."); + return; + } + + Transform timeLabels = banner.Find("TimeLabels"); + Transform verticalLines = banner.Find("VerticalLines"); + Transform tracksContainer = banner.Find("TracksContainer"); + var timelineViewExisting = banner.Find("TimelineView"); + if (timeLabels == null && timelineViewExisting != null) + { + timeLabels = timelineViewExisting.Find("TimeLabels"); + verticalLines = timelineViewExisting.Find("VerticalLines"); + tracksContainer = timelineViewExisting.Find("TracksContainer"); + } + if (timeLabels == null || verticalLines == null || tracksContainer == null) + { + Debug.LogError("[ProgrammedMovementsSetup] Old structure not found. Use Force Rebuild UI."); + return; + } + + GameObject timelineViewGO; + if (timelineViewExisting != null) + { + timelineViewGO = timelineViewExisting.gameObject; + } + else + { + timelineViewGO = new GameObject("TimelineView"); + Undo.RegisterCreatedObjectUndo(timelineViewGO, "Migrate PM"); + timelineViewGO.transform.SetParent(banner, false); + var tr = timelineViewGO.AddComponent(); + tr.anchorMin = Vector2.zero; + tr.anchorMax = Vector2.one; + tr.offsetMin = Vector2.zero; + tr.offsetMax = Vector2.zero; + Undo.SetTransformParent(timeLabels, timelineViewGO.transform, "Migrate PM"); + Undo.SetTransformParent(verticalLines, timelineViewGO.transform, "Migrate PM"); + Undo.SetTransformParent(tracksContainer, timelineViewGO.transform, "Migrate PM"); + } + + var configViewGO = new GameObject("ConfigView"); + Undo.RegisterCreatedObjectUndo(configViewGO, "Migrate PM"); + configViewGO.transform.SetParent(banner, false); + configViewGO.SetActive(false); + var configViewRect = configViewGO.AddComponent(); + configViewRect.anchorMin = Vector2.zero; + configViewRect.anchorMax = Vector2.one; + configViewRect.offsetMin = Vector2.zero; + configViewRect.offsetMax = Vector2.zero; + + var configContent = new GameObject("ConfigContent"); + configContent.transform.SetParent(configViewRect, false); + var configContentRect = configContent.AddComponent(); + configContentRect.anchorMin = Vector2.zero; + configContentRect.anchorMax = Vector2.one; + configContentRect.offsetMin = new Vector2(12, 8); + configContentRect.offsetMax = new Vector2(-12, -8); + + var configVertical = configContent.AddComponent(); + configVertical.spacing = 8; + configVertical.padding = new RectOffset(8, 8, 8, 8); + configVertical.childAlignment = TextAnchor.MiddleLeft; + configVertical.childControlWidth = true; + configVertical.childControlHeight = true; + configVertical.childForceExpandWidth = true; + configVertical.childForceExpandHeight = false; + + var row1 = CreateConfigRow1(configContent.transform); + var row2 = CreateConfigRow2(configContent.transform); + + var tracksRect = tracksContainer.GetComponent(); + if (tracksRect == null) + { + Debug.LogError("[ProgrammedMovementsSetup] TracksContainer has no RectTransform."); + return; + } + for (int t = 0; t < TracksCount; t++) + { + if (t >= tracksRect.childCount) break; + var track = tracksRect.GetChild(t); + if (track == null) continue; + var blocks = track.Find("Blocks"); + if (blocks != null) + { + if (blocks.GetComponent() == null) + { + var img = blocks.gameObject.AddComponent(); + img.color = new Color(0, 0, 0, 0); + } + if (blocks.GetComponent() == null) + blocks.gameObject.AddComponent(); + } + } + + EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); + Debug.Log("[ProgrammedMovementsSetup] Migrated to new layout. Save the scene. Enter Play mode to see Config panel when adding/editing blocks."); + } + + static void DoSetup(Transform pmView) + { + var canvasGO = new GameObject("ProgrammedMovementsCanvas"); + Undo.RegisterCreatedObjectUndo(canvasGO, "Create PM Canvas"); + canvasGO.transform.SetParent(pmView, false); + + var canvasRect = canvasGO.AddComponent(); + canvasRect.anchorMin = Vector2.zero; + canvasRect.anchorMax = Vector2.one; + canvasRect.offsetMin = Vector2.zero; + canvasRect.offsetMax = Vector2.zero; + + canvasGO.AddComponent().renderMode = RenderMode.ScreenSpaceOverlay; + canvasGO.GetComponent().sortingOrder = 2; + var scaler = canvasGO.AddComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(800, 600); + canvasGO.AddComponent(); + + float bannerHeight = 100f; + float runButtonHeight = 36f; + + var runGO = CreateButton(canvasRect, "Run", 16); + runGO.name = "RunButton"; + var runRect = runGO.GetComponent(); + runRect.anchorMin = new Vector2(0, 0); + runRect.anchorMax = new Vector2(0, 0); + runRect.pivot = new Vector2(0, 0); + runRect.anchoredPosition = new Vector2(12, bannerHeight + 8); + runRect.sizeDelta = new Vector2(80, runButtonHeight); + + var bannerGO = new GameObject("TimelineBanner"); + Undo.RegisterCreatedObjectUndo(bannerGO, "Create PM Banner"); + bannerGO.transform.SetParent(canvasRect, false); + var bannerRect = bannerGO.AddComponent(); + bannerRect.anchorMin = new Vector2(0, 0); + bannerRect.anchorMax = new Vector2(1, 0); + bannerRect.pivot = new Vector2(0.5f, 0); + bannerRect.anchoredPosition = Vector2.zero; + bannerRect.sizeDelta = new Vector2(0, bannerHeight); + bannerGO.AddComponent().color = new Color(0.12f, 0.12f, 0.14f, 0.95f); + + var timelineViewGO = new GameObject("TimelineView"); + timelineViewGO.transform.SetParent(bannerRect, false); + var timelineViewRect = timelineViewGO.AddComponent(); + timelineViewRect.anchorMin = Vector2.zero; + timelineViewRect.anchorMax = Vector2.one; + timelineViewRect.offsetMin = Vector2.zero; + timelineViewRect.offsetMax = Vector2.zero; + + var configViewGO = new GameObject("ConfigView"); + configViewGO.transform.SetParent(bannerRect, false); + configViewGO.SetActive(false); + var configViewRect = configViewGO.AddComponent(); + configViewRect.anchorMin = Vector2.zero; + configViewRect.anchorMax = Vector2.one; + configViewRect.offsetMin = Vector2.zero; + configViewRect.offsetMax = Vector2.zero; + + var configContent = new GameObject("ConfigContent"); + configContent.transform.SetParent(configViewRect, false); + var configContentRect = configContent.AddComponent(); + configContentRect.anchorMin = Vector2.zero; + configContentRect.anchorMax = Vector2.one; + configContentRect.offsetMin = new Vector2(12, 8); + configContentRect.offsetMax = new Vector2(-12, -8); + + var configVertical = configContent.AddComponent(); + configVertical.spacing = 8; + configVertical.padding = new RectOffset(8, 8, 8, 8); + configVertical.childAlignment = TextAnchor.MiddleLeft; + configVertical.childControlWidth = true; + configVertical.childControlHeight = true; + configVertical.childForceExpandWidth = true; + configVertical.childForceExpandHeight = false; + + CreateConfigRow1(configContent.transform); + CreateConfigRow2(configContent.transform); + + var timeLabelsGO = new GameObject("TimeLabels"); + timeLabelsGO.transform.SetParent(timelineViewRect, false); + var timeLabelsRect = timeLabelsGO.AddComponent(); + timeLabelsRect.anchorMin = new Vector2(0, 1); + timeLabelsRect.anchorMax = new Vector2(1, 1); + timeLabelsRect.pivot = new Vector2(0.5f, 1); + timeLabelsRect.anchoredPosition = new Vector2(0, -8); + timeLabelsRect.sizeDelta = new Vector2(0, 20); + + for (int s = 0; s <= TimelineDurationSeconds; s++) + { + float x = (float)s / TimelineDurationSeconds; + var label = CreateText(timeLabelsRect.transform, $"{s}s", 12); + label.name = $"Label{s}s"; + var lr = label.GetComponent(); + lr.anchorMin = new Vector2(x, 0); + lr.anchorMax = new Vector2(x, 1); + lr.pivot = new Vector2(0.5f, 0.5f); + lr.anchoredPosition = Vector2.zero; + lr.sizeDelta = new Vector2(28, 0); + } + var linesGO = new GameObject("VerticalLines"); + linesGO.transform.SetParent(timelineViewRect, false); + var linesRect = linesGO.AddComponent(); + linesRect.anchorMin = new Vector2(0, 0); + linesRect.anchorMax = new Vector2(1, 1); + linesRect.offsetMin = new Vector2(4, 4); + linesRect.offsetMax = new Vector2(-4, -32); + for (int s = 0; s <= TimelineDurationSeconds; s++) + { + float x = (float)s / TimelineDurationSeconds; + var line = new GameObject($"Line{s}"); + line.transform.SetParent(linesRect, false); + var lineImg = line.AddComponent(); + lineImg.color = new Color(0.35f, 0.35f, 0.4f, 0.7f); + var lineRect = line.GetComponent(); + lineRect.anchorMin = new Vector2(x, 0); + lineRect.anchorMax = new Vector2(x, 1); + lineRect.pivot = new Vector2(0.5f, 0); + lineRect.anchoredPosition = Vector2.zero; + lineRect.sizeDelta = new Vector2(2, 0); + } + + var tracksGO = new GameObject("TracksContainer"); + tracksGO.transform.SetParent(timelineViewRect, false); + var tracksRect = tracksGO.AddComponent(); + tracksRect.anchorMin = new Vector2(0, 0); + tracksRect.anchorMax = new Vector2(1, 1); + tracksRect.offsetMin = new Vector2(4, 4); + tracksRect.offsetMax = new Vector2(-4, -32); + + for (int t = 0; t < TracksCount; t++) + { + var trackGO = new GameObject($"Track{t}"); + trackGO.transform.SetParent(tracksRect, false); + var tr = trackGO.AddComponent(); + tr.anchorMin = new Vector2(0, (float)(TracksCount - 1 - t) / TracksCount); + tr.anchorMax = new Vector2(1, (float)(TracksCount - t) / TracksCount); + tr.offsetMin = new Vector2(2, 2); + tr.offsetMax = new Vector2(-2, -2); + + var trackBg = trackGO.AddComponent(); + trackBg.color = new Color(0.18f, 0.18f, 0.2f, 0.9f); + + var blocksGO = new GameObject("Blocks"); + blocksGO.transform.SetParent(tr, false); + var blocksRect = blocksGO.AddComponent(); + blocksRect.anchorMin = Vector2.zero; + blocksRect.anchorMax = Vector2.one; + blocksRect.offsetMin = Vector2.zero; + blocksRect.offsetMax = Vector2.zero; // Full width so block coords (0-1) align with timeline seconds + blocksGO.AddComponent().color = new Color(0, 0, 0, 0); + blocksGO.AddComponent(); + + var addBtn = CreateButton(tr, "+"); + addBtn.name = "AddButton"; + var addRect = addBtn.GetComponent(); + addRect.anchorMin = new Vector2(1, 0); + addRect.anchorMax = new Vector2(1, 1); + addRect.pivot = new Vector2(1, 0.5f); + addRect.anchoredPosition = new Vector2(-4, 0); + addRect.sizeDelta = new Vector2(28, 0); + } + + var controller = pmView.GetComponentInChildren(true); + if (controller != null) + { + controller.timelineRoot = bannerRect; + controller.runButton = runGO.GetComponent